murashit_codex-panel/tests/features/chat/view-lifecycle.test.ts
2026-06-15 15:13:00 +09:00

97 lines
3.6 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ConnectionWorkTracker } from "../../../src/shared/lifecycle/connection-work";
import { ChatResumeWorkTracker, transitionRestoredThreadLifecycle } from "../../../src/features/chat/application/lifecycle";
import { createChatViewDeferredTasks } from "../../../src/features/chat/host/lifecycle";
describe("createChatViewDeferredTasks", () => {
beforeEach(() => {
vi.useFakeTimers();
});
it("clears scheduled deferred work", async () => {
const tasks = createChatViewDeferredTasks(() => window);
const diagnostics = vi.fn();
const hydration = vi.fn();
const warmup = vi.fn();
tasks.scheduleDiagnostics(diagnostics);
tasks.scheduleRestoredThreadHydration(hydration);
tasks.scheduleAppServerWarmup(warmup);
tasks.clearAll();
await vi.advanceTimersByTimeAsync(1_500);
expect(diagnostics).not.toHaveBeenCalled();
expect(hydration).not.toHaveBeenCalled();
expect(warmup).not.toHaveBeenCalled();
});
});
describe("chat view lifecycle transitions", () => {
it("tracks active connection work by identity", () => {
const tracker = new ConnectionWorkTracker();
const connection = tracker.begin();
const stale = { kind: "connecting" as const, promise: Promise.resolve() };
expect(tracker.active()).toBe(connection);
expect(tracker.isStale(connection)).toBe(false);
expect(tracker.isStale(stale)).toBe(true);
const promise = Promise.resolve();
connection.promise = promise;
tracker.finish(connection, Promise.resolve());
expect(tracker.active()).toBe(connection);
tracker.finish(connection, promise);
expect(tracker.active()).toBeNull();
});
it("tracks resume work by identity", () => {
const tracker = new ChatResumeWorkTracker();
const resume = tracker.begin("thread");
expect(tracker.isStale(resume)).toBe(false);
tracker.invalidate();
expect(tracker.isStale(resume)).toBe(true);
});
it("keeps stale connection completions from clearing the active connection", () => {
const firstPromise = Promise.resolve();
const secondPromise = Promise.resolve();
const tracker = new ConnectionWorkTracker();
const first = tracker.begin();
first.promise = firstPromise;
const second = tracker.begin();
second.promise = secondPromise;
tracker.finish(first, firstPromise);
expect(tracker.active()).toBe(second);
tracker.finish(second, firstPromise);
expect(tracker.active()).toBe(second);
tracker.finish(second, secondPromise);
expect(tracker.active()).toBeNull();
});
it("clears restored-thread loading only for the active loading promise", () => {
const firstLoading = Promise.resolve();
const secondLoading = Promise.resolve();
const placeholder = transitionRestoredThreadLifecycle(
{ kind: "idle" },
{ type: "placeholder-restored", restoredThread: { threadId: "thread", title: "Old", explicitName: null } },
);
const loading = transitionRestoredThreadLifecycle(placeholder, { type: "loading-started", loading: secondLoading });
expect(transitionRestoredThreadLifecycle(loading, { type: "loading-finished", loading: firstLoading })).toBe(loading);
expect(transitionRestoredThreadLifecycle(loading, { type: "renamed", threadId: "thread", name: "New" })).toMatchObject({
title: "New",
explicitName: "New",
loading: secondLoading,
});
expect(transitionRestoredThreadLifecycle(loading, { type: "loading-finished", loading: secondLoading })).toMatchObject({
kind: "placeholder",
loading: null,
});
});
});