diff options
| author | Adam Malczewski <[email protected]> | 2026-06-02 13:50:50 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-02 13:50:50 +0900 |
| commit | 3e5e93df13ff49833263a17841446c2fa037e3df (patch) | |
| tree | e915b35ad68ba125c05cc3c9f4de351346dbe424 | |
| parent | d635b7e95e7a0432d9e246f5f3f0eb1335c6adc2 (diff) | |
| download | dispatch-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.ts | 66 |
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 ─── |
