import type {
EnvironmentApi,
ProjectListEntriesResult,
ProjectReadFileResult,
} from "@t3tools/contracts";
import { EnvironmentId } from "@t3tools/contracts";
import * as Option from "effect/Option";
import { AsyncResult, AtomRegistry } from "effect/unstable/reactivity";
import { afterEach, describe, expect, it, vi } from "vite-plus/test";
import {
__resetEnvironmentApiOverridesForTests,
__setEnvironmentApiOverrideForTests,
} from "~/environmentApi";
import { appAtomRegistry } from "~/rpc/atomRegistry";
import {
__resetProjectFileQueryDataForTests,
confirmProjectFileQueryData,
getProjectEntriesQueryAtom,
getProjectFileQueryAtom,
resolveProjectFileQueryData,
setProjectFileQueryData,
} from "./projectFilesQueryState";
const environmentId = EnvironmentId.make("environment-project-files-query-test");
function deferred() {
let resolve!: (value: A) => void;
const promise = new Promise((resolvePromise) => {
resolve = resolvePromise;
});
return { promise, resolve };
}
describe("project files queries", () => {
afterEach(() => {
__resetProjectFileQueryDataForTests();
__resetEnvironmentApiOverridesForTests();
vi.unstubAllGlobals();
});
it("retains cached entries while explicitly revalidating", async () => {
vi.stubGlobal("window", {});
const first = {
entries: [{ path: "README.md", kind: "file" }],
truncated: false,
} satisfies ProjectListEntriesResult;
const second = {
entries: [
{ path: "README.md", kind: "file" },
{ path: "src", kind: "directory" },
],
truncated: false,
} satisfies ProjectListEntriesResult;
const revalidation = deferred();
const listEntries = vi
.fn()
.mockResolvedValueOnce(first)
.mockReturnValueOnce(revalidation.promise);
__setEnvironmentApiOverrideForTests(environmentId, {
projects: { listEntries },
} as unknown as EnvironmentApi);
const registry = AtomRegistry.make();
const atom = getProjectEntriesQueryAtom(environmentId, "/repo");
registry.get(atom);
await vi.waitFor(() => {
expect(Option.getOrNull(AsyncResult.value(registry.get(atom)))).toEqual(first);
});
registry.refresh(atom);
await vi.waitFor(() => expect(listEntries).toHaveBeenCalledTimes(2));
const refreshing = registry.get(atom);
expect(refreshing.waiting).toBe(true);
expect(Option.getOrNull(AsyncResult.value(refreshing))).toEqual(first);
revalidation.resolve(second);
await vi.waitFor(() => {
expect(Option.getOrNull(AsyncResult.value(registry.get(atom)))).toEqual(second);
});
registry.dispose();
});
it("keeps the latest optimistic draft when an older write finishes", async () => {
vi.stubGlobal("window", {});
const initial = {
relativePath: "convex.json",
contents: '{"nodeVersion":"20"}',
byteLength: 20,
truncated: false,
} satisfies ProjectReadFileResult;
const readFile = vi.fn().mockResolvedValue(initial);
__setEnvironmentApiOverrideForTests(environmentId, {
projects: { readFile },
} as unknown as EnvironmentApi);
const atom = getProjectFileQueryAtom(environmentId, "/repo", "convex.json");
appAtomRegistry.get(atom);
await vi.waitFor(() => {
expect(Option.getOrNull(AsyncResult.value(appAtomRegistry.get(atom)))).toEqual(initial);
});
setProjectFileQueryData(environmentId, "/repo", "convex.json", '{"nodeVersion":"220"}');
setProjectFileQueryData(environmentId, "/repo", "convex.json", '{"nodeVersion":"22"}');
expect(
confirmProjectFileQueryData(environmentId, "/repo", "convex.json", '{"nodeVersion":"220"}'),
).toBe(false);
expect(
resolveProjectFileQueryData(
environmentId,
"/repo",
"convex.json",
Option.getOrNull(AsyncResult.value(appAtomRegistry.get(atom))),
),
).toEqual({
relativePath: "convex.json",
contents: '{"nodeVersion":"22"}',
byteLength: 20,
truncated: false,
});
expect(
confirmProjectFileQueryData(environmentId, "/repo", "convex.json", '{"nodeVersion":"22"}'),
).toBe(true);
});
});