Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions client/modules/followUpQuestions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { generateFollowUpQuestion } from "./followUpQuestions";

vi.mock("./pubSub", () => ({
getSuppressNextFollowUp: vi.fn(() => false),
}));

vi.mock("./textGeneration", () => ({
generateChatResponse: vi.fn(),
}));

import { getSuppressNextFollowUp } from "./pubSub";
import { generateChatResponse } from "./textGeneration";

const mockedGenerateChatResponse = vi.mocked(generateChatResponse);
const mockedGetSuppressNextFollowUp = vi.mocked(getSuppressNextFollowUp);

beforeEach(() => {
vi.clearAllMocks();
mockedGetSuppressNextFollowUp.mockReturnValue(false);
});

describe("generateFollowUpQuestion", () => {
it("keeps an English question with leading numbering", async () => {
mockedGenerateChatResponse.mockResolvedValue(
"1. What about the alternatives?",
);

const question = await generateFollowUpQuestion({
topic: "cats",
currentContent: "Cats are independent.",
});

expect(question).toBe("What about the alternatives?");
});

it("keeps a Chinese question ending in a fullwidth question mark", async () => {
mockedGenerateChatResponse.mockResolvedValue("你觉得这个方案怎么样?");

const question = await generateFollowUpQuestion({
topic: "方案",
currentContent: "方案可行。",
});

expect(question).toBe("你觉得这个方案怎么样?");
});

it("strips a leading bullet from a Japanese question without touching the text", async () => {
mockedGenerateChatResponse.mockResolvedValue(
"- この案についてどう思いますか?",
);

const question = await generateFollowUpQuestion({
topic: "案",
currentContent: "案は実行可能です。",
});

expect(question).toBe("この案についてどう思いますか?");
});

it("keeps an Arabic question ending in an Arabic question mark", async () => {
mockedGenerateChatResponse.mockResolvedValue("ما رأيك في البدائل؟");

const question = await generateFollowUpQuestion({
topic: "البدائل",
currentContent: "البدائل متاحة.",
});

expect(question).toBe("ما رأيك في البدائل؟");
});

it("returns an empty string when no line ends with a question mark", async () => {
mockedGenerateChatResponse.mockResolvedValue("No questions here.");

const question = await generateFollowUpQuestion({
topic: "cats",
currentContent: "Cats are independent.",
});

expect(question).toBe("");
});

it("returns an empty string when follow-up questions are suppressed", async () => {
mockedGetSuppressNextFollowUp.mockReturnValue(true);

const question = await generateFollowUpQuestion({
topic: "cats",
currentContent: "Cats are independent.",
});

expect(mockedGenerateChatResponse).not.toHaveBeenCalled();
expect(question).toBe("");
});
});
12 changes: 10 additions & 2 deletions client/modules/followUpQuestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,22 @@ Respond with just the question, no additional text or explanations.`,
.split("\n")
.map((line) => line.trim())
.reverse()
.find((line) => line.endsWith("?"));
.find(
(line) =>
line.endsWith("?") || line.endsWith("?") || line.endsWith("؟"),
);

if (!lines) {
addLogEntry("No valid follow-up question generated");
return "";
}

let questionLine = lines.replace(/^[^a-zA-Z]+/, "");
// Strips leading bullets, numbering, and quotes without touching the
// question text itself. The old [^a-zA-Z] class matched every character
// of a non-Latin question (Chinese, Japanese, Korean, Arabic, ...) and
// wiped it to an empty string, even though the prompt above requires the
// follow-up question to be in the same language as the original.
let questionLine = lines.replace(/^[^\p{L}]+/u, "");

questionLine = questionLine.charAt(0).toUpperCase() + questionLine.slice(1);

Expand Down