Files
fusion/packages/dashboard/app/hooks/__tests__/useUpdateCheck.test.ts
gsxdsm cc901c2b9d FN-7762: expand update-notification test coverage across CLI, dashboard, and desktop surfaces
Broadens regression coverage for the npm-release update-check invariant so it holds across every consuming surface, not just the reported repro.

- Replace the update-check route/service semver spot-checks with a parametrized case matrix (equal, newer, older, prerelease/build metadata, short/long version segments) to close false-positive/false-negative gaps.
- Add dedicated route-level tests asserting the update-check API route surfaces the same invariant.
- Add CLI update command tests covering notification rendering across version-comparison cases.
- Add desktop native update-check tests covering the same invariant on the desktop shell.
- Add dashboard useUpdateCheck hook tests verifying consistent notification behavior for the hook consumers.

Files changed:
 packages/cli/src/commands/__tests__/update.test.ts | 59 ++++++++++++++++++
 .../app/hooks/__tests__/useUpdateCheck.test.ts     | 25 ++++++++
 .../src/__tests__/update-check-route.test.ts       | 71 ++++++++++++++++++++++
 .../dashboard/src/__tests__/update-check.test.ts   | 42 +++++++------
 packages/desktop/src/__tests__/native.test.ts      | 27 ++++++++
 5 files changed, 206 insertions(+), 18 deletions(-)

Fusion-Task-Id: FN-7762

Fusion-Task-Lineage: 6ab34312-fb4e-487a-aefc-2ab133bf79af

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-09 23:46:21 -07:00

93 lines
3.3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, waitFor, act } from "@testing-library/react";
import { useUpdateCheck } from "../useUpdateCheck";
import * as api from "../../api";
vi.mock("../../api", () => ({
checkForUpdate: vi.fn(),
}));
const mockCheckForUpdate = vi.mocked(api.checkForUpdate);
describe("useUpdateCheck", () => {
beforeEach(() => {
vi.clearAllMocks();
sessionStorage.clear();
});
it("fetches update status on mount", async () => {
mockCheckForUpdate.mockResolvedValueOnce({
currentVersion: "0.6.0",
latestVersion: "0.7.0",
updateAvailable: true,
lastChecked: Date.now(),
});
const { result } = renderHook(() => useUpdateCheck());
await waitFor(() => expect(result.current.loading).toBe(false));
expect(mockCheckForUpdate).toHaveBeenCalledOnce();
expect(result.current.updateAvailable).toBe(true);
expect(result.current.latestVersion).toBe("0.7.0");
expect(result.current.currentVersion).toBe("0.6.0");
});
it("only exposes an update notification for a strictly newer release", async () => {
/*
* FNXC:UpdateNotifications 2026-07-09-00:00:
* The banner hook must be a pass-through notification gate: newer API results become visible banner state, while equal, older, disabled, and unresolved results remain silent.
*/
const cases = [
{ response: { currentVersion: "1.2.3", latestVersion: "1.2.4", updateAvailable: true }, expected: true },
{ response: { currentVersion: "1.2.3", latestVersion: "1.2.3", updateAvailable: false }, expected: false },
{ response: { currentVersion: "1.2.3", latestVersion: "1.2.2", updateAvailable: false }, expected: false },
{ response: { currentVersion: "0.0.0", latestVersion: null, updateAvailable: false, error: "Current Fusion version is unavailable" }, expected: false },
{ response: { currentVersion: "1.2.3", latestVersion: null, updateAvailable: false, disabled: true }, expected: false },
];
for (const testCase of cases) {
mockCheckForUpdate.mockResolvedValueOnce(testCase.response);
const { result, unmount } = renderHook(() => useUpdateCheck());
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.updateAvailable).toBe(testCase.expected);
unmount();
}
});
it("dismiss stores session flag", async () => {
mockCheckForUpdate.mockResolvedValueOnce({
currentVersion: "0.6.0",
latestVersion: "0.7.0",
updateAvailable: true,
});
const { result } = renderHook(() => useUpdateCheck());
await waitFor(() => expect(result.current.loading).toBe(false));
act(() => {
result.current.dismiss();
});
expect(result.current.dismissed).toBe(true);
expect(sessionStorage.getItem("kb-update-banner-dismissed")).toBe("true");
});
it("starts dismissed when sessionStorage already has dismissal key", async () => {
sessionStorage.setItem("kb-update-banner-dismissed", "true");
mockCheckForUpdate.mockResolvedValueOnce({
currentVersion: "0.6.0",
latestVersion: "0.7.0",
updateAvailable: true,
});
const { result } = renderHook(() => useUpdateCheck());
await waitFor(() => expect(result.current.loading).toBe(false));
expect(result.current.dismissed).toBe(true);
});
});