Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ export default function CompressionSettingsTab() {
const savedRef = useRef(config);
const queuedRef = useRef<SettingsPatch<CompressionConfig>[]>([]);
const saveQueueRef = useRef(Promise.resolve());
const statusTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);

// The "saved" badge clears itself after 2s; a timer that outlives the tab fires into an
// unmounted (or torn-down) React tree.
useEffect(
() => () => {
if (statusTimerRef.current) clearTimeout(statusTimerRef.current);
},
[]
);

useEffect(() => {
fetch("/api/settings/compression")
Expand Down Expand Up @@ -301,7 +311,13 @@ export default function CompressionSettingsTab() {
// A failure stays on screen until the next edit, so a queued success or an earlier
// save's timeout cannot hide a field that just rolled back.
setStatus((shown) => (ok ? (shown === "error" ? shown : "saved") : "error"));
if (ok) setTimeout(() => setStatus((shown) => (shown === "saved" ? "" : shown)), 2000);
if (ok) {
if (statusTimerRef.current) clearTimeout(statusTimerRef.current);
statusTimerRef.current = setTimeout(
() => setStatus((shown) => (shown === "saved" ? "" : shown)),
2000
);
}
});
};

Expand Down
26 changes: 26 additions & 0 deletions tests/unit/ui/compression-settings-tab-partial-save.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,37 @@ async function renderTab() {
}

afterEach(() => {
vi.restoreAllMocks();
vi.useRealTimers();
vi.unstubAllGlobals();
});

describe("CompressionSettingsTab saves only what changed", () => {
it("clears the saved-badge timer when the tab unmounts", async () => {
startServer();
const nativeSetTimeout = globalThis.setTimeout;
const badgeTimers: unknown[] = [];
vi.spyOn(globalThis, "setTimeout").mockImplementation(((
fn: TimerHandler,
ms?: number,
...args: unknown[]
) => {
const id = nativeSetTimeout(fn as () => void, ms, ...(args as []));
if (ms === 2000) badgeTimers.push(id);
return id;
}) as typeof setTimeout);
const clearSpy = vi.spyOn(globalThis, "clearTimeout");

const view = render(<CompressionSettingsTab />);
await settle();
fireEvent.change(inputFor("compressionCacheTTL"), { target: { value: "10" } });
await settle();
expect(badgeTimers).toHaveLength(1);

view.unmount();
expect(clearSpy).toHaveBeenCalledWith(badgeTimers[0]);
});

// Rendering the whole caveman page takes over 5 seconds on a cold run.
it(
"keeps Auto-Clarity off on the caveman page when the embedded tab saves",
Expand Down
Loading