summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-02 13:50:50 +0900
committerAdam Malczewski <[email protected]>2026-06-02 13:50:50 +0900
commit3e5e93df13ff49833263a17841446c2fa037e3df (patch)
treee915b35ad68ba125c05cc3c9f4de351346dbe424
parentd635b7e95e7a0432d9e246f5f3f0eb1335c6adc2 (diff)
downloaddispatch-3e5e93df13ff49833263a17841446c2fa037e3df.tar.gz
dispatch-3e5e93df13ff49833263a17841446c2fa037e3df.zip
test: prove Context Window view gets hydrated cacheStats.last after reload
Cross-branch contract test (u2/context-window-view merged from dev): the Context Window panel derives current context from cacheStats.last via computeContextUsage. This drives the full path — persisted usage aggregate -> hydrateFromBackend -> cacheStats.last -> computeContextUsage -> '48,200 / 200,000' — proving the view shows real context size immediately after a reload on a new device (not 'No context data yet'). Guards the contract so neither persistence nor the view can silently break it.
-rw-r--r--packages/frontend/tests/chat-store.test.ts66
1 files changed, 66 insertions, 0 deletions
diff --git a/packages/frontend/tests/chat-store.test.ts b/packages/frontend/tests/chat-store.test.ts
index dc2783d..c0763cd 100644
--- a/packages/frontend/tests/chat-store.test.ts
+++ b/packages/frontend/tests/chat-store.test.ts
@@ -71,6 +71,7 @@ beforeEach(() => {
);
});
+import { computeContextUsage } from "../src/lib/context-window.js";
import { appSettings } from "../src/lib/settings.svelte.js";
import { createTabStore } from "../src/lib/tabs.svelte.js";
import type { Chunk, PermissionPrompt } from "../src/lib/types.js";
@@ -1191,6 +1192,71 @@ describe("hydrateFromBackend", () => {
cacheWriteTokens: 0,
});
});
+
+ // Cross-feature contract (Context Window view, branch u2): the panel derives
+ // current context size from `cacheStats.last` via computeContextUsage. This
+ // test proves persistence restores that field on hydrate, so the view shows a
+ // real "x / max" immediately after a reload on a NEW DEVICE — not "No context
+ // data yet". Guards the contract so neither side can silently break it.
+ it("restores cacheStats.last on hydrate so the Context Window view has data after reload", async () => {
+ const usageStats = {
+ inputTokens: 90000,
+ outputTokens: 3000,
+ cacheReadTokens: 40000,
+ cacheWriteTokens: 5000,
+ requests: 3,
+ // Most recent request's snapshot — the numerator the view reads.
+ last: { inputTokens: 47000, outputTokens: 1200, cacheReadTokens: 30000, cacheWriteTokens: 0 },
+ };
+ vi.stubGlobal(
+ "fetch",
+ vi.fn((url: string) => {
+ if (url.endsWith("/tabs")) {
+ return Promise.resolve({
+ ok: true,
+ json: () =>
+ Promise.resolve({
+ tabs: [
+ {
+ id: "tc",
+ title: "Context after reload",
+ keyId: null,
+ modelId: null,
+ parentTabId: null,
+ usageStats,
+ },
+ ],
+ }),
+ });
+ }
+ if (url.endsWith("/status")) {
+ return Promise.resolve({ ok: true, json: () => Promise.resolve({ statuses: {} }) });
+ }
+ if (url.split("?")[0]?.endsWith("/tabs/tc/chunks")) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve({ chunks: [], total: 0, oldestSeq: null }),
+ });
+ }
+ return Promise.reject(new Error(`unexpected fetch ${url}`));
+ }),
+ );
+
+ const store = createTabStore();
+ await store.hydrateFromBackend();
+
+ // What App.svelte passes into ContextWindowPanel: the active tab's cacheStats
+ // plus the model's max (re-resolved from models.dev on load — here 200k).
+ const cacheStats = store.tabs.find((t) => t.id === "tc")?.cacheStats ?? null;
+ expect(cacheStats?.last).not.toBeNull();
+
+ const usage = computeContextUsage(cacheStats, 200000);
+ // current = last.inputTokens + last.outputTokens (47000 + 1200), NOT the
+ // cumulative session totals (which would double-count history).
+ expect(usage.current).toBe(48200);
+ expect(usage.max).toBe(200000);
+ expect(usage.percent).toBeCloseTo(24.1, 5);
+ });
});
// ─── statuses WS event with the wider TabStatusSnapshot shape ───