226 lines · 6.3 KB · TypeScript
RawDownload
import type { Dispatch, ReactElement, SetStateAction } from "react";import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test";import {  type EnvironmentId,  ProviderDriverKind,  ProviderInstanceId,  type ServerProvider,} from "@t3tools/contracts";import { AsyncResult } from "effect/unstable/reactivity"; import type {  LocalEnvironmentUpdateGroup,  ProviderUpdateCandidate,  ProviderUpdateRowStatus,} from "./ProviderUpdateLaunchNotification.logic"; const testState = vi.hoisted(() => ({  groups: [] as LocalEnvironmentUpdateGroup[],  updateProvider: vi.fn(),})); const hooks = vi.hoisted(() => {  let cursor = 0;  let slots: unknown[] = [];   const nextIndex = () => cursor++;   return {    beginRender() {      cursor = 0;    },    reset() {      cursor = 0;      slots = [];    },    useCallback<T>(callback: T): T {      nextIndex();      return callback;    },    useMemo<T>(factory: () => T): T {      nextIndex();      return factory();    },    useMemoCache(size: number): unknown[] {      const index = nextIndex();      if (!slots[index]) {        slots[index] = Array.from({ length: size }, () => Symbol.for("react.memo_cache_sentinel"));      }      return slots[index] as unknown[];    },    useRef<T>(initialValue: T): { current: T } {      const index = nextIndex();      if (!slots[index]) {        slots[index] = { current: initialValue };      }      return slots[index] as { current: T };    },    useState<T>(initialValue: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {      const index = nextIndex();      if (index >= slots.length) {        slots[index] =          typeof initialValue === "function" ? (initialValue as () => T)() : initialValue;      }      const setValue: Dispatch<SetStateAction<T>> = (nextValue) => {        const previous = slots[index] as T;        slots[index] =          typeof nextValue === "function" ? (nextValue as (value: T) => T)(previous) : nextValue;      };      return [slots[index] as T, setValue];    },  };}); vi.mock("react", async (importOriginal) => {  const actual = await importOriginal<typeof import("react")>();  return {    ...actual,    useCallback: hooks.useCallback,    useMemo: hooks.useMemo,    useRef: hooks.useRef,    useState: hooks.useState,  };}); vi.mock("react/compiler-runtime", () => ({  c: hooks.useMemoCache,})); vi.mock("~/state/server", () => ({  serverEnvironment: { updateProvider: Symbol("updateProvider") },})); vi.mock("~/state/use-atom-command", () => ({  useAtomCommand: () => testState.updateProvider,})); vi.mock("./ProviderUpdateLaunchNotification.environments", () => ({  useLocalEnvironmentUpdateGroups: () => ({    groups: testState.groups,    isAnySettling: false,  }),})); import { ProviderUpdateEnvironmentRows } from "./ProviderUpdateEnvironmentRows"; const environmentId = "env-wsl" as EnvironmentId;const pendingExpiryMs = 6 * 60_000; function provider(updateStatus?: "succeeded"): ServerProvider {  const result: ServerProvider = {    instanceId: ProviderInstanceId.make("codex-wsl"),    driver: ProviderDriverKind.make("codex"),    enabled: true,    installed: true,    version: updateStatus ? "1.1.0" : "1.0.0",    status: "ready",    auth: { status: "authenticated" },    checkedAt: "2026-06-26T12:00:00.000Z",    models: [],    slashCommands: [],    skills: [],    versionAdvisory: {      status: updateStatus ? "current" : "behind_latest",      currentVersion: updateStatus ? "1.1.0" : "1.0.0",      latestVersion: "1.1.0",      updateCommand: "npm install -g @openai/codex@latest",      canUpdate: true,      checkedAt: "2026-06-26T12:00:00.000Z",      message: updateStatus ? "Up to date." : "Update available.",    },  };   return updateStatus    ? {        ...result,        updateState: {          status: updateStatus,          startedAt: "2026-06-26T12:00:00.000Z",          finishedAt: "2026-06-26T12:00:01.000Z",          message: "Provider updated.",          output: null,        },      }    : result;} function deferred<T>() {  let resolve!: (value: T) => void;  const promise = new Promise<T>((complete) => {    resolve = complete;  });  return { promise, resolve };} type RowElement = ReactElement<{  readonly status: ProviderUpdateRowStatus;  readonly onUpdate: () => void;}>; function renderRow(): RowElement {  hooks.beginRender();  const output = ProviderUpdateEnvironmentRows({}) as ReactElement<{    readonly children: RowElement | RowElement[];  }>;  const children = output.props.children;  return Array.isArray(children) ? children[0]! : children;} async function flushPromises(): Promise<void> {  await Promise.resolve();  await Promise.resolve();  await Promise.resolve();} describe("ProviderUpdateEnvironmentRows", () => {  beforeEach(() => {    vi.useFakeTimers();    hooks.reset();    testState.updateProvider.mockReset();    const candidate = provider() as ProviderUpdateCandidate;    testState.groups = [      {        environmentId,        label: "WSL",        isPrimary: false,        isSettling: false,        candidates: [candidate],        providers: [candidate],      },    ];  });   afterEach(() => {    vi.useRealTimers();  });   it("keeps a successor pending when an expired request resolves late, then shows its success", async () => {    const firstRequest =      deferred<ReturnType<typeof AsyncResult.success<{ providers: ServerProvider[] }>>>();    const successorRequest =      deferred<ReturnType<typeof AsyncResult.success<{ providers: ServerProvider[] }>>>();    testState.updateProvider      .mockReturnValueOnce(firstRequest.promise)      .mockReturnValueOnce(successorRequest.promise);     renderRow().props.onUpdate();    expect(renderRow().props.status.kind).toBe("loading");     await vi.advanceTimersByTimeAsync(pendingExpiryMs);    expect(renderRow().props.status.kind).toBe("failed");     renderRow().props.onUpdate();    expect(testState.updateProvider).toHaveBeenCalledTimes(2);    expect(renderRow().props.status.kind).toBe("loading");     firstRequest.resolve(AsyncResult.success({ providers: [provider("succeeded")] }));    await flushPromises();     expect(renderRow().props.status.kind).toBe("loading");     successorRequest.resolve(AsyncResult.success({ providers: [provider("succeeded")] }));    await flushPromises();     expect(renderRow().props.status.kind).toBe("success");  });});