Files
kandev/apps/web/lib/user-settings-sync.test.ts
T
Carlos Florêncio b9eb6ae058 refactor(web): remove legacy settings migration (#1704)
* refactor(web): remove legacy settings migration

* fix(web): hydrate portable settings from boot state

* fix(web): preserve sidebar sync contracts

* fix(web): retain portable settings filters

* fix(web): retry sidebar settings writes

* fix(web): retain sidebar boot settings

* fix(websocket): avoid shutdown teardown leaks

* fix(boot): normalize sidebar task preferences

* fix(settings): preserve saved views during hydration

* fix(jira): avoid duplicate saved-view syncs

* fix(jira): preserve saved views on fetch failure

* fix(jira): retry saved-view hydration failures

* refactor(jira): simplify hydration request cleanup

* fix(websocket): close late clients after hub shutdown
2026-07-16 00:06:41 +01:00

80 lines
2.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import { waitFor } from "@testing-library/react";
import { ApiError } from "@/lib/api/client";
import { updateUserSettings } from "@/lib/api/domains/settings-api";
import { createQueuedUserSettingsSync } from "./user-settings-sync";
vi.mock("@/lib/api/domains/settings-api", () => ({
updateUserSettings: vi.fn(),
}));
function deferred<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((res) => {
resolve = res;
});
return { promise, resolve };
}
describe("createQueuedUserSettingsSync", () => {
beforeEach(() => {
window.localStorage.clear();
vi.mocked(updateUserSettings).mockReset();
});
it("serializes backend writes so later payloads are sent after earlier ones finish", async () => {
const first = deferred<Awaited<ReturnType<typeof updateUserSettings>>>();
vi.mocked(updateUserSettings)
.mockReturnValueOnce(first.promise)
.mockResolvedValueOnce({ settings: {} } as Awaited<ReturnType<typeof updateUserSettings>>);
const sync = createQueuedUserSettingsSync<string>((value) => ({
preferred_shell: value,
}));
void sync("bash");
void sync("zsh");
await waitFor(() => {
expect(updateUserSettings).toHaveBeenCalledTimes(1);
});
expect(updateUserSettings).toHaveBeenCalledWith({ preferred_shell: "bash" });
first.resolve({ settings: {} } as Awaited<ReturnType<typeof updateUserSettings>>);
await waitFor(() => {
expect(updateUserSettings).toHaveBeenCalledTimes(2);
expect(updateUserSettings).toHaveBeenLastCalledWith({ preferred_shell: "zsh" });
});
});
it("retries a transient settings write before processing the next payload", async () => {
vi.mocked(updateUserSettings)
.mockRejectedValueOnce(new Error("temporary failure"))
.mockResolvedValueOnce({ settings: {} } as Awaited<ReturnType<typeof updateUserSettings>>)
.mockResolvedValueOnce({ settings: {} } as Awaited<ReturnType<typeof updateUserSettings>>);
const sync = createQueuedUserSettingsSync<string>((value) => ({
preferred_shell: value,
}));
void sync("bash");
void sync("zsh");
await waitFor(() => {
expect(updateUserSettings).toHaveBeenNthCalledWith(1, { preferred_shell: "bash" });
expect(updateUserSettings).toHaveBeenNthCalledWith(2, { preferred_shell: "bash" });
expect(updateUserSettings).toHaveBeenNthCalledWith(3, { preferred_shell: "zsh" });
});
});
it("does not retry an API error", async () => {
vi.mocked(updateUserSettings).mockRejectedValueOnce(new ApiError("invalid settings", 400, {}));
const sync = createQueuedUserSettingsSync<string>((value) => ({
preferred_shell: value,
}));
await sync("bash");
expect(updateUserSettings).toHaveBeenCalledTimes(1);
});
});