Download packages/app/test-browser/session-lineage.test.ts from SaylorTwift/opencode: direct link, hf CLI and curl.
- Browser
- Download file 7.05 kB
-
https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/test-browser/session-lineage.test.ts
- Command line
-
hf download hf://SaylorTwift/opencode/packages/app/test-browser/session-lineage.test.ts
-
curl -L -o session-lineage.test.ts https://huggingface.co/SaylorTwift/opencode/resolve/main/packages/app/test-browser/session-lineage.test.ts
7.05 kB
| import { expect, test } from "bun:test" | |
| import { createRoot, createSignal } from "solid-js" | |
| import { createSessionLineage } from "@/pages/session/session-lineage" | |
| type Lineage = { session: { id: string; directory: string } } | |
| const lineageOf = (id: string): Lineage => ({ session: { id, directory: `/dir/${id}` } }) | |
| // Fake sync lineage store: peek reads a reactive cache, resolve returns a | |
| // deferred promise the test settles or fails explicitly. The lineage memo is | |
| // live (read below), so it recomputes eagerly on cache/status writes — throws | |
| // surface at the write site, which is also where the enclosing ErrorBoundary | |
| // would see them in the app. Assertions wrap write + read to cover both. | |
| function createFixture(initial: Record<string, Lineage> = {}) { | |
| const [cache, setCache] = createSignal(initial) | |
| const deferred = new Map<string, PromiseWithResolvers<unknown>>() | |
| const resolves: string[] = [] | |
| return { | |
| resolves, | |
| lineage: { | |
| peek: (id: string) => cache()[id], | |
| resolve: (id: string) => { | |
| resolves.push(id) | |
| const entry = deferred.get(id) ?? Promise.withResolvers<unknown>() | |
| deferred.set(id, entry) | |
| return entry.promise | |
| }, | |
| }, | |
| settle(id: string) { | |
| setCache({ ...cache(), [id]: lineageOf(id) }) | |
| deferred.get(id)?.resolve(undefined) | |
| }, | |
| fail(id: string, error: unknown) { | |
| deferred.get(id)?.reject(error) | |
| // The real store does not cache failures: the inflight request entry is | |
| // dropped on rejection so the next resolve retries (server-session.ts). | |
| deferred.delete(id) | |
| }, | |
| remove(id: string) { | |
| const next = { ...cache() } | |
| delete next[id] | |
| setCache(next) | |
| }, | |
| } | |
| } | |
| // Two microtask ticks: one for the resolve promise handed back by the fixture, | |
| // one for the .then/.catch chain inside createSessionLineage. | |
| const flush = async () => { | |
| await Promise.resolve() | |
| await Promise.resolve() | |
| } | |
| test("resolves an uncached session and exposes its lineage", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture() | |
| const current = createSessionLineage( | |
| () => "ses_a", | |
| () => fixture.lineage, | |
| ) | |
| expect(current()).toBeUndefined() | |
| await flush() | |
| expect(fixture.resolves).toEqual(["ses_a"]) | |
| fixture.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| dispose() | |
| }) | |
| }) | |
| // Session tabs on the same server share one route instance, so navigating to | |
| // another session changes the id in place; resolution must follow it instead | |
| // of reporting the new session as missing. | |
| test("re-resolves when navigating to an uncached session without a remount", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture({ ses_a: lineageOf("ses_a") }) | |
| const [id, setId] = createSignal("ses_a") | |
| const current = createSessionLineage(id, () => fixture.lineage) | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| expect(() => { | |
| setId("ses_b") | |
| current() | |
| }).not.toThrow() | |
| expect(fixture.resolves).toEqual(["ses_b"]) | |
| fixture.settle("ses_b") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_b") | |
| dispose() | |
| }) | |
| }) | |
| // A late failure from a session the user already navigated away from must not | |
| // poison the currently viewed session. | |
| test("ignores a stale resolution failure after the target changes", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture() | |
| const [id, setId] = createSignal("ses_a") | |
| const current = createSessionLineage(id, () => fixture.lineage) | |
| await flush() | |
| setId("ses_b") | |
| fixture.fail("ses_a", new Error("Session not found: ses_a")) | |
| await flush() | |
| expect(() => current()).not.toThrow() | |
| fixture.settle("ses_b") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_b") | |
| dispose() | |
| }) | |
| }) | |
| test("returning to a pruned session re-resolves instead of throwing not found", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture() | |
| const [id, setId] = createSignal("ses_a") | |
| const current = createSessionLineage(id, () => fixture.lineage) | |
| await flush() | |
| fixture.settle("ses_a") | |
| await flush() | |
| setId("ses_b") | |
| fixture.settle("ses_b") | |
| await flush() | |
| fixture.remove("ses_a") | |
| expect(() => { | |
| setId("ses_a") | |
| current() | |
| }).not.toThrow() | |
| expect(fixture.resolves).toEqual(["ses_a", "ses_b", "ses_a"]) | |
| fixture.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| dispose() | |
| }) | |
| }) | |
| // A resolution that fails while its session is unfocused must not leave a | |
| // poisoned status behind: revisiting that session retries cleanly instead of | |
| // rethrowing the stale failure before the retry can start. | |
| test("revisiting a session whose resolution failed while unfocused retries cleanly", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture() | |
| const [id, setId] = createSignal("ses_a") | |
| const current = createSessionLineage(id, () => fixture.lineage) | |
| await flush() | |
| setId("ses_b") | |
| fixture.fail("ses_a", new Error("resolve failed")) | |
| await flush() | |
| expect(() => { | |
| setId("ses_a") | |
| current() | |
| }).not.toThrow() | |
| expect(fixture.resolves).toEqual(["ses_a", "ses_b", "ses_a"]) | |
| fixture.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| dispose() | |
| }) | |
| }) | |
| // The lineage accessor is reactive: replacing the sync store (for example after | |
| // the server context is rebuilt) must gate out the old store's status and | |
| // re-resolve against the new one instead of fabricating a not-found. | |
| test("re-resolves against a replaced lineage store", async () => { | |
| await createRoot(async (dispose) => { | |
| const first = createFixture() | |
| const second = createFixture() | |
| const [store, setStore] = createSignal(first.lineage) | |
| const current = createSessionLineage(() => "ses_a", store) | |
| await flush() | |
| first.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| expect(() => { | |
| setStore(second.lineage) | |
| current() | |
| }).not.toThrow() | |
| await flush() | |
| expect(second.resolves).toEqual(["ses_a"]) | |
| second.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| dispose() | |
| }) | |
| }) | |
| // The viewed session is pinned in the cache, so disappearing after settlement | |
| // means it was deleted; the boundary must show the not found fallback. | |
| test("throws not found when the settled session is deleted", async () => { | |
| await createRoot(async (dispose) => { | |
| const fixture = createFixture() | |
| const current = createSessionLineage( | |
| () => "ses_a", | |
| () => fixture.lineage, | |
| ) | |
| await flush() | |
| fixture.settle("ses_a") | |
| await flush() | |
| expect(current()?.session.id).toBe("ses_a") | |
| expect(() => { | |
| fixture.remove("ses_a") | |
| current() | |
| }).toThrow("Session not found: ses_a") | |
| dispose() | |
| }) | |
| }) | |