81 lines
3.6 KiB
JavaScript
81 lines
3.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
import { scheduleWorkspaceSearch } from "../src/lib/workspace-search.ts";
|
|
import { retainSelectedHistoryItem, resolveSidebarSelectionAfterRefresh } from "../src/lib/sidebar-selection.ts";
|
|
|
|
const flush = async () => { await Promise.resolve(); await Promise.resolve(); };
|
|
|
|
test("typing is debounced and superseded requests never start", async (t) => {
|
|
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
const queries = [];
|
|
const results = [];
|
|
const request = async (query) => { queries.push(query); return { items: [], hasMore: false }; };
|
|
const reportError = (error) => assert.fail(String(error));
|
|
const cancelFirst = scheduleWorkspaceSearch("ne", request, (result) => results.push(result), reportError);
|
|
t.mock.timers.tick(299);
|
|
assert.deepEqual(queries, []);
|
|
cancelFirst();
|
|
const cancelLast = scheduleWorkspaceSearch("nebula", request, (result) => results.push(result), reportError);
|
|
t.mock.timers.tick(300);
|
|
await flush();
|
|
assert.deepEqual(queries, ["nebula"]);
|
|
assert.deepEqual(results, [{ items: [], hasMore: false }]);
|
|
cancelLast();
|
|
});
|
|
|
|
test("cancellation aborts the transport and discards stale results and errors", async (t) => {
|
|
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
let resolveFirst;
|
|
let rejectSecond;
|
|
let signal;
|
|
const results = [];
|
|
const errors = [];
|
|
const first = scheduleWorkspaceSearch("first", (_query, value) => {
|
|
signal = value;
|
|
return new Promise((resolve) => { resolveFirst = resolve; });
|
|
}, (result) => results.push(result), (error) => errors.push(error));
|
|
t.mock.timers.tick(300);
|
|
first();
|
|
assert.equal(signal.aborted, true);
|
|
const second = scheduleWorkspaceSearch("second", () => new Promise((_resolve, reject) => { rejectSecond = reject; }),
|
|
(result) => results.push(result), (error) => errors.push(error));
|
|
t.mock.timers.tick(300);
|
|
second();
|
|
resolveFirst({ items: [{ id: "stale" }], hasMore: true });
|
|
rejectSecond(new Error("stale failure"));
|
|
await flush();
|
|
assert.deepEqual(results, []);
|
|
assert.deepEqual(errors, []);
|
|
});
|
|
|
|
test("search results retain excerpts and truncation info; failures reach the UI", async (t) => {
|
|
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
const expected = { items: [{ type: "chat", id: "old", searchSnippet: "A nebula in the sky" }], hasMore: true };
|
|
let received;
|
|
let error;
|
|
const cancel = scheduleWorkspaceSearch("nebula", async () => expected, (result) => { received = result; }, assert.fail);
|
|
t.mock.timers.tick(300);
|
|
await flush();
|
|
assert.deepEqual(received, expected);
|
|
cancel();
|
|
const cancelFailure = scheduleWorkspaceSearch("nebula", async () => { throw new Error("Offline"); }, assert.fail, (result) => { error = result; });
|
|
t.mock.timers.tick(300);
|
|
await flush();
|
|
assert.equal(error.message, "Offline");
|
|
cancelFailure();
|
|
});
|
|
|
|
test("opening older chat/search results survives collection refresh without restoring deleted or unrelated items", () => {
|
|
const recent = [{ type: "chat", id: "recent" }];
|
|
for (const kind of ["chat", "search"]) {
|
|
const selection = { kind, id: "old" };
|
|
const retained = { type: kind, id: "old" };
|
|
const refreshed = retainSelectedHistoryItem(recent, selection, retained);
|
|
assert.deepEqual(resolveSidebarSelectionAfterRefresh(selection, refreshed), selection);
|
|
assert.equal(retainSelectedHistoryItem(refreshed, selection, retained), refreshed);
|
|
assert.equal(retainSelectedHistoryItem(recent, { kind, id: "different" }, retained), recent);
|
|
assert.equal(retainSelectedHistoryItem(recent, selection, null), recent);
|
|
assert.equal(retainSelectedHistoryItem(recent, null, retained), recent);
|
|
}
|
|
});
|