Files
kandev/apps/web/components/github/my-github/use-default-query-presets.test.ts
T
2026-07-06 21:43:13 +01:00

207 lines
7.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { act, renderHook, waitFor } from "@testing-library/react";
import { fetchUserSettings, updateUserSettings } from "@/lib/api/domains/settings-api";
import {
fetchGitHubWorkspaceSettings,
updateGitHubWorkspaceSettings,
} from "@/lib/api/domains/github-api";
import {
__resetSnapshotForTests,
useDefaultQueryPresets,
type StoredQueryPreset,
} from "./use-default-query-presets";
const STORAGE_KEY = "kandev:github-default-queries:v1";
const SYNC_FAILED_KEY = "kandev:github-default-queries:sync-failed:v1";
const WORKSPACE_ID = "ws-1";
const SETTINGS_TIMESTAMP = "2026-01-01T00:00:00Z";
vi.mock("@/lib/api/domains/settings-api", () => ({
fetchUserSettings: vi.fn(),
updateUserSettings: vi.fn(),
}));
vi.mock("@/lib/api/domains/github-api", () => ({
fetchGitHubWorkspaceSettings: vi.fn(),
updateGitHubWorkspaceSettings: vi.fn(),
}));
function makeLocalStorageMock() {
const store = new Map<string, string>();
return {
getItem: (key: string) => store.get(key) ?? null,
setItem: (key: string, value: string) => store.set(key, value),
removeItem: (key: string) => store.delete(key),
clear: () => store.clear(),
get length() {
return store.size;
},
key: (index: number) => Array.from(store.keys())[index] ?? null,
};
}
const localStorageMock = makeLocalStorageMock();
vi.stubGlobal("localStorage", localStorageMock);
Object.defineProperty(window, "localStorage", { value: localStorageMock, configurable: true });
const preset: StoredQueryPreset = {
value: "mine",
label: "Mine",
filter: "author:@me is:open",
group: "created",
};
function workspaceSettings(
defaultQueryPresets: unknown = null,
): Awaited<ReturnType<typeof fetchGitHubWorkspaceSettings>> {
return {
workspace_id: WORKSPACE_ID,
repo_scope_mode: "all",
repo_scope_orgs: [],
repo_scope_repos: [],
saved_presets: [],
default_query_presets: defaultQueryPresets,
created_at: SETTINGS_TIMESTAMP,
updated_at: SETTINGS_TIMESTAMP,
} as Awaited<ReturnType<typeof fetchGitHubWorkspaceSettings>>;
}
function resetMocks() {
vi.clearAllMocks();
localStorageMock.clear();
__resetSnapshotForTests();
vi.mocked(fetchUserSettings).mockResolvedValue({
settings: { github_default_query_presets: null },
} as Awaited<ReturnType<typeof fetchUserSettings>>);
vi.mocked(updateUserSettings).mockResolvedValue({
settings: {},
} as Awaited<ReturnType<typeof updateUserSettings>>);
vi.mocked(fetchGitHubWorkspaceSettings).mockResolvedValue(workspaceSettings());
vi.mocked(updateGitHubWorkspaceSettings).mockResolvedValue(
{} as Awaited<ReturnType<typeof updateGitHubWorkspaceSettings>>,
);
}
describe("useDefaultQueryPresets legacy sync", () => {
beforeEach(resetMocks);
it("retries local defaults after a failed backend sync and clears the marker", async () => {
const local = { pr: [preset], issue: [] };
localStorageMock.setItem(STORAGE_KEY, JSON.stringify(local));
localStorageMock.setItem(SYNC_FAILED_KEY, "1");
renderHook(() => useDefaultQueryPresets());
await waitFor(() => {
expect(updateUserSettings).toHaveBeenCalledWith({
github_default_query_presets: local,
});
expect(localStorageMock.getItem(SYNC_FAILED_KEY)).toBeNull();
});
});
it("retries null local defaults after a failed reset sync", async () => {
localStorageMock.setItem(SYNC_FAILED_KEY, "1");
vi.mocked(fetchUserSettings).mockResolvedValue({
settings: { github_default_query_presets: { pr: [preset], issue: [] } },
} as Awaited<ReturnType<typeof fetchUserSettings>>);
renderHook(() => useDefaultQueryPresets());
await waitFor(() => {
expect(updateUserSettings).toHaveBeenCalledWith({
github_default_query_presets: null,
});
expect(localStorageMock.getItem(SYNC_FAILED_KEY)).toBeNull();
});
});
it("does not overwrite cross-tab local changes with a stale hydration response", async () => {
const initial = { pr: [preset], issue: [] };
const crossTab = { pr: [], issue: [preset] };
const server = {
pr: [{ ...preset, value: "server", label: "Server" }],
issue: [],
};
let resolveFetch: (value: Awaited<ReturnType<typeof fetchUserSettings>>) => void = () => {};
vi.mocked(fetchUserSettings).mockReturnValue(
new Promise((resolve) => {
resolveFetch = resolve;
}),
);
localStorageMock.setItem(STORAGE_KEY, JSON.stringify(initial));
renderHook(() => useDefaultQueryPresets());
await waitFor(() => expect(fetchUserSettings).toHaveBeenCalled());
localStorageMock.setItem(STORAGE_KEY, JSON.stringify(crossTab));
const event = new Event("storage");
Object.defineProperty(event, "key", { value: STORAGE_KEY });
window.dispatchEvent(event);
resolveFetch({
settings: { github_default_query_presets: server },
} as Awaited<ReturnType<typeof fetchUserSettings>>);
await Promise.resolve();
await Promise.resolve();
expect(localStorageMock.getItem(STORAGE_KEY)).toBe(JSON.stringify(crossTab));
expect(updateUserSettings).not.toHaveBeenCalled();
});
});
describe("useDefaultQueryPresets workspace sync", () => {
beforeEach(resetMocks);
it("migrates local defaults into a fresh workspace", async () => {
const local = { pr: [preset], issue: [] };
localStorageMock.setItem(STORAGE_KEY, JSON.stringify(local));
__resetSnapshotForTests();
const { result } = renderHook(() => useDefaultQueryPresets(WORKSPACE_ID));
await waitFor(() => expect(result.current.prPresets[0]?.value).toBe("mine"));
expect(updateGitHubWorkspaceSettings).toHaveBeenCalledWith({
workspace_id: WORKSPACE_ID,
default_query_presets: local,
});
});
it("does not migrate local defaults over existing workspace defaults", async () => {
const server = { pr: [{ ...preset, value: "server", label: "Server" }], issue: [] };
localStorageMock.setItem(STORAGE_KEY, JSON.stringify({ pr: [preset], issue: [] }));
__resetSnapshotForTests();
vi.mocked(fetchGitHubWorkspaceSettings).mockResolvedValue(workspaceSettings(server));
const { result } = renderHook(() => useDefaultQueryPresets(WORKSPACE_ID));
await waitFor(() => expect(result.current.prPresets[0]?.value).toBe("server"));
expect(updateGitHubWorkspaceSettings).not.toHaveBeenCalled();
});
it("does not save or reset while workspace defaults are still loading", () => {
vi.mocked(fetchGitHubWorkspaceSettings).mockReturnValue(new Promise(() => {}));
const { result } = renderHook(() => useDefaultQueryPresets(WORKSPACE_ID));
act(() => {
result.current.save({ pr: [preset], issue: [] });
result.current.reset();
});
expect(updateGitHubWorkspaceSettings).not.toHaveBeenCalled();
});
it("does not save or reset after workspace defaults fail to load", async () => {
vi.mocked(fetchGitHubWorkspaceSettings).mockRejectedValue(new Error("settings down"));
const { result } = renderHook(() => useDefaultQueryPresets(WORKSPACE_ID));
await waitFor(() => expect(fetchGitHubWorkspaceSettings).toHaveBeenCalled());
act(() => {
result.current.save({ pr: [preset], issue: [] });
result.current.reset();
});
expect(updateGitHubWorkspaceSettings).not.toHaveBeenCalled();
});
});