Files
app/src/__tests__/app-version-store.test.ts
BOHA 4bb7de7247 feat(app): detect new deploy and prompt refresh (+ lazy-chunk recovery)
When a new version is deployed, open tabs no longer silently run stale code
until a manual F5.

- Server stamps every response with X-App-Version (src/middleware/version.ts,
  onSend hook in server.ts).
- Client bakes its build version via Vite define (__APP_VERSION__); apiFetch
  compares the header to it (no polling — piggybacks existing traffic) and
  latches a mismatch in a small store (appVersion.ts).
- UpdateBanner: a persistent bottom Snackbar "Je k dispozici nová verze
  aplikace. — Obnovit" → location.reload(). User-initiated, so an in-progress
  form is never interrupted.
- lazyWithReload: every React.lazy page reloads once if its hashed chunk 404s
  after a deploy (sessionStorage-guarded against loops), fixing the "old chunk
  gone" error screen.

Approach chosen after researching current SPA practice (version header + banner
beats polling / a service worker for a non-PWA admin app).

Tests: +4 (server header, client store latch/notify). Full suite 669 pass,
tsc -b clean, lint 0. Verified live via Playwright (header emitted; banner
appears on a simulated mismatch). Detection applies to deploys from the NEXT
release onward (current clients lack the detection code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 21:19:46 +02:00

47 lines
1.4 KiB
TypeScript

import { describe, it, expect, beforeEach } from "vitest";
import {
reportServerVersion,
subscribeNewVersion,
getNewVersion,
BUILD_VERSION,
__resetVersionStateForTest,
} from "../admin/utils/appVersion";
/**
* Client store backing the "new version available" banner. The server stamps
* X-App-Version on every response; apiFetch feeds it here. A version that
* differs from the one baked into THIS bundle (BUILD_VERSION) means a deploy
* happened → latch it and notify subscribers (the banner).
*/
describe("appVersion store", () => {
beforeEach(() => __resetVersionStateForTest());
it("ignores the same version, empty, or null", () => {
reportServerVersion(BUILD_VERSION);
reportServerVersion("");
reportServerVersion(null);
reportServerVersion(undefined);
expect(getNewVersion()).toBeNull();
});
it("latches + notifies once when the server reports a different version", () => {
let notified = 0;
const unsub = subscribeNewVersion(() => {
notified++;
});
reportServerVersion(`${BUILD_VERSION}-next`);
expect(getNewVersion()).toBe(`${BUILD_VERSION}-next`);
expect(notified).toBe(1);
// Idempotent: once a new version is known, further reports don't re-notify
// or overwrite (the banner is already showing).
reportServerVersion("99.0.0");
expect(getNewVersion()).toBe(`${BUILD_VERSION}-next`);
expect(notified).toBe(1);
unsub();
});
});