160 lines · 4.7 KB · TypeScript
RawDownload
import type { ReactElement } from "react";import { renderToStaticMarkup } from "react-dom/server";import type { EnvironmentId } from "@t3tools/contracts";import * as Cause from "effect/Cause";import { AsyncResult } from "effect/unstable/reactivity";import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; const testState = vi.hoisted(() => ({  updateServer: vi.fn(),  toast: vi.fn(),})); vi.mock("~/hooks/useCopyToClipboard", () => ({  useCopyToClipboard: () => ({ copyToClipboard: vi.fn() }),}));vi.mock("~/state/server", () => ({  serverEnvironment: { updateServer: Symbol("updateServer") },}));vi.mock("~/state/use-atom-command", () => ({  useAtomCommand: () => testState.updateServer,}));vi.mock("./ui/toast", () => ({  toastManager: { add: testState.toast },})); import { ServerUpdateAction, ServerUpdateProgress } from "./ServerUpdateAction"; type ActionElement = ReactElement<{  readonly onClick?: () => void;}>; function renderAction(): ActionElement {  return ServerUpdateAction({    environmentId: "env-test" as EnvironmentId,    serverLabel: "Test server",    selfUpdate: "boot-service",    targetVersion: "0.0.31",  }) as ActionElement;} async function flushPromises(): Promise<void> {  await Promise.resolve();  await Promise.resolve();} describe("ServerUpdateAction", () => {  beforeEach(() => {    testState.updateServer.mockReset();    testState.toast.mockReset();  });   it("reports success only after the shared update flow reconnects", async () => {    testState.updateServer.mockResolvedValue(      AsyncResult.success({ targetVersion: "0.0.31", method: "boot-service" as const }),    );     renderAction().props.onClick?.();    await flushPromises();     expect(testState.updateServer).toHaveBeenCalledWith({      environmentId: "env-test",      input: { targetVersion: "0.0.31" },    });    expect(testState.toast).toHaveBeenCalledWith({      type: "success",      title: "Test server updated",      description: "Reconnected on t3@0.0.31.",    });  });   it("reports one result when the update action is double-clicked", async () => {    let finishUpdate: (() => void) | undefined;    testState.updateServer.mockImplementation(      () =>        new Promise((resolve) => {          finishUpdate = () =>            resolve(              AsyncResult.success({ targetVersion: "0.0.31", method: "boot-service" as const }),            );        }),    );     const action = renderAction();    action.props.onClick?.();    action.props.onClick?.();     expect(testState.updateServer).toHaveBeenCalledTimes(1);    finishUpdate?.();    await flushPromises();    expect(testState.toast).toHaveBeenCalledTimes(1);  });   it("quietly releases the action when the operation is interrupted", async () => {    testState.updateServer.mockResolvedValue(AsyncResult.failure(Cause.interrupt()));     renderAction().props.onClick?.();    await flushPromises();     expect(testState.toast).not.toHaveBeenCalled();  });}); describe("ServerUpdateProgress", () => {  it("shows one calm status row for the restart wait", () => {    const markup = renderToStaticMarkup(      <ServerUpdateProgress        state={{          status: "running",          stage: "resuming",          fromVersion: "0.0.30",          targetVersion: "0.0.31",        }}      />,    );     expect(markup).toContain("Restarting…");    // The wait state is monochrome and calm: no versions, no step rail, no    // success/warning colors, one duty-cycled pulse on the dot.    expect(markup).not.toContain("0.0.30");    expect(markup).not.toContain("Resum");    expect(markup).not.toContain("text-success");    expect(markup).not.toContain("text-primary");    expect(markup).toContain("animate-status-pulse");    expect(markup).not.toContain("animate-spin");  });   it("folds the sub-second installing handoff into the download phase", () => {    const markup = renderToStaticMarkup(      <ServerUpdateProgress        state={{          status: "running",          stage: "installing",          fromVersion: "0.0.30",          targetVersion: "0.0.31",        }}      />,    );     expect(markup).toContain("Downloading…");    expect(markup).not.toContain("Install");  });   it("keeps the failure visible with its retryable error", () => {    const markup = renderToStaticMarkup(      <ServerUpdateProgress        state={{          status: "failed",          stage: "installing",          fromVersion: "0.0.30",          targetVersion: "0.0.31",          message: "The package could not be verified.",        }}      />,    );     expect(markup).toContain('role="alert"');    expect(markup).toContain("The package could not be verified.");    expect(markup).not.toContain("animate-status-pulse");  });});