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>
93 lines
3.3 KiB
TypeScript
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);
|
|
});
|
|
});
|