Open History to see the last commit
1import { render, fakeTimers } from "../../../test/render";
2import { type AuthSessionState, EnvironmentId } from "@t3tools/contracts";
3import { AsyncResult } from "effect/reactivity";
4import { StrictMode } from "react";
5import { describe, expect, it, vi } from "vite-plus/test";
6
7const { runAtomCommand, getUnsavedFile } = vi.hoisted(() => ({
8 runAtomCommand: vi.fn(),
9 getUnsavedFile: vi.fn(),
10}));
11vi.mock(import("@t3tools/client-runtime/state/runtime"), async (original) => ({
12 ...(await original()),
13 runAtomCommand: runAtomCommand as never,
14}));
15// The real session atom boots the connection runtime, whose endless discovery poll
16// would keep vi.runAllTimersAsync from ever draining once its timers are faked.
17vi.mock(import("~/state/session"), async (original) => {
18 const actual = await original();
19 const reactivity = await import("effect/reactivity");
20 const signedOut = reactivity.Atom.make(reactivity.AsyncResult.initial<AuthSessionState>());
21 return {
22 ...actual,
23 environmentSession: { ...actual.environmentSession, sessionStateAtom: () => signedOut },
24 readEnvironmentScope: () => true,
25 };
26});
27vi.mock(import("./projectFilesQueryState"), async (original) => ({
28 ...(await original()),
29 confirmProjectFileQueryData: () => true,
30 getUnsavedProjectFileQueryData: getUnsavedFile,
31}));
32
33import { changeFileSaveSession, useFileSaveSession } from "./fileSaveSessions";
34
35const target = {
36 environmentId: EnvironmentId.make("file-save-sessions-browser"),
37 cwd: "/workspace",
38 relativePath: "README.md",
39};
40const onPendingChange = vi.fn();
41
42// The editor and rendered-markdown surfaces are different component types, so
43// switching between them unmounts one session holder and mounts another.
44function EditorSurface() {
45 useFileSaveSession({ ...target }, onPendingChange);
46 return <div>editor</div>;
47}
48function MarkdownSurface() {
49 useFileSaveSession({ ...target }, onPendingChange);
50 return <div>markdown</div>;
51}
52
53describe("file save sessions in React", () => {
54 it("writes a pending draft once when the file switches surfaces under StrictMode", async () => {
55 let finishWrite = () => {};
56 runAtomCommand.mockReset().mockImplementation(
57 () =>
58 new Promise((resolve) => {
59 finishWrite = () => resolve(AsyncResult.success(undefined));
60 }),
61 );
62 getUnsavedFile.mockReset().mockReturnValue(null);
63 await using screen = await render(
64 <StrictMode>
65 <EditorSurface />
66 </StrictMode>,
67 { router: false }, // no routes involved; the surfaces are bare effect hosts
68 );
69 using _timers = fakeTimers();
70 // An edit lands in the query cache, unconfirmed until its write settles.
71 getUnsavedFile.mockReturnValue({ contents: "draft" });
72 changeFileSaveSession(target, "draft");
73
74 await screen.rerender(
75 <StrictMode>
76 <MarkdownSurface />
77 </StrictMode>,
78 );
79 finishWrite();
80 getUnsavedFile.mockReturnValue(null);
81 await vi.runAllTimersAsync();
82
83 expect(runAtomCommand.mock.calls.map(([, , request]) => request)).toEqual([
84 {
85 environmentId: target.environmentId,
86 input: { cwd: "/workspace", relativePath: "README.md", contents: "draft" },
87 },
88 ]);
89 expect(onPendingChange).toHaveBeenLastCalledWith("README.md", false);
90 });
91});