Skip to content

Commit f0ce1d7

Browse files
committed
Address review: nullish-coalesce, rename test, pin debounce assertion
1 parent 902a4ba commit f0ce1d7

2 files changed

Lines changed: 75 additions & 20 deletions

File tree

frontend/src/components/corpuses/CorpusChat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ export const CorpusChat: React.FC<CorpusChatProps> = ({
471471
setChat((prev) => [
472472
...prev,
473473
{
474-
messageId: data?.message_id || crypto.randomUUID(),
474+
messageId: data?.message_id ?? crypto.randomUUID(),
475475
user: "Assistant",
476476
content,
477477
timestamp: new Date().toLocaleString(),

frontend/tests/CorpusChat.ct.tsx

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1032,7 +1032,7 @@ test.describe("CorpusChat", () => {
10321032
await component.unmount();
10331033
});
10341034

1035-
test("ASYNC_RESUME keeps the processing indicator visible", async ({
1035+
test("ASYNC_RESUME sequence completes with final message", async ({
10361036
mount,
10371037
page,
10381038
}) => {
@@ -1235,34 +1235,89 @@ test.describe("CorpusChat", () => {
12351235
mount,
12361236
page,
12371237
}) => {
1238+
// Mock for the debounced refetch with `title_Contains: "First"`. Returns
1239+
// only the "First Conversation" edge so we can positively assert that the
1240+
// debounced value flowed into the Apollo variables and produced a
1241+
// filter-aware result.
1242+
const filteredConversationsMock: MockedResponse = {
1243+
request: {
1244+
query: GET_CORPUS_CONVERSATIONS,
1245+
variables: {
1246+
corpusId: TEST_CORPUS_ID,
1247+
conversationType: "CHAT",
1248+
title_Contains: "First",
1249+
},
1250+
},
1251+
result: {
1252+
data: {
1253+
conversations: {
1254+
__typename: "ConversationTypeConnection",
1255+
pageInfo: {
1256+
__typename: "PageInfo",
1257+
hasNextPage: false,
1258+
endCursor: null,
1259+
},
1260+
edges: [
1261+
{
1262+
__typename: "ConversationTypeEdge",
1263+
node: {
1264+
__typename: "ConversationType",
1265+
id: TEST_CONVERSATION_ID,
1266+
title: "First Conversation",
1267+
createdAt: new Date(Date.now() - 86400000).toISOString(),
1268+
updatedAt: new Date(Date.now() - 86400000).toISOString(),
1269+
chatMessages: {
1270+
__typename: "ChatMessageTypeConnection",
1271+
totalCount: 5,
1272+
},
1273+
creator: {
1274+
__typename: "UserType",
1275+
email: "user@example.com",
1276+
},
1277+
},
1278+
},
1279+
],
1280+
},
1281+
},
1282+
},
1283+
};
1284+
12381285
const component = await mount(
12391286
<CorpusChatTestWrapper
1240-
mocks={[conversationsWithDataMock, conversationsWithDataMock]}
1287+
mocks={[
1288+
conversationsWithDataMock,
1289+
conversationsWithDataMock,
1290+
filteredConversationsMock,
1291+
]}
12411292
corpusId={TEST_CORPUS_ID}
12421293
/>
12431294
);
12441295

12451296
await expect(page.getByText("First Conversation")).toBeVisible({
12461297
timeout: 20000,
12471298
});
1299+
// "Second Conversation" is present in the unfiltered mock — we will later
1300+
// assert it disappears after the debounce fires with the filter.
1301+
await expect(page.getByText("Second Conversation")).toBeVisible();
12481302

1249-
// The conversation list has a title filter input. Any input that accepts
1250-
// text can drive the title debounce state — we just need to cover the
1251-
// debounce timer useEffect.
1252-
//
1253-
// NOTE: this is coverage-only; it does NOT assert filter semantics.
1254-
// If the debounce timer is broken (e.g. the effect no longer fires),
1255-
// this test will still pass — it exists to exercise the code path,
1256-
// not to pin filter behaviour. Any behavioural regression should be
1257-
// caught by a dedicated filter test added alongside that feature.
1258-
const filterInputs = page.locator('input[type="text"], input:not([type])');
1259-
if ((await filterInputs.count()) > 0) {
1260-
const first = filterInputs.first();
1261-
await first.fill("First");
1262-
// Wait long enough for the 500ms debounce to elapse
1263-
await page.waitForTimeout(700);
1264-
}
1265-
1303+
// The conversation list has a collapsed search icon that expands to a
1304+
// text input. Click it to reveal the title filter input, then type to
1305+
// drive the 500ms debounce timer useEffect and refetch.
1306+
const searchButton = page.locator('button[title="Search"]');
1307+
await expect(searchButton).toBeVisible({ timeout: 5000 });
1308+
await searchButton.click();
1309+
1310+
const filterInput = page.locator('input[placeholder="Search chats..."]');
1311+
await expect(filterInput).toBeVisible({ timeout: 5000 });
1312+
await filterInput.fill("First");
1313+
1314+
// After the 500ms debounce fires, the filtered mock takes effect: "Second
1315+
// Conversation" is dropped from the result. Asserting its disappearance
1316+
// proves the debounced value reached Apollo's variables — previously the
1317+
// test silently skipped entirely when the filter input was absent.
1318+
await expect(page.getByText("Second Conversation")).not.toBeVisible({
1319+
timeout: 5000,
1320+
});
12661321
await expect(page.getByText("First Conversation")).toBeVisible();
12671322
await component.unmount();
12681323
});

0 commit comments

Comments
 (0)