mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
47a4276e84
logout() unregistered every sync trigger, and the only place that registers them is App's mount effect - which never runs again, because logout navigates through the router rather than reloading. Log out, log back in, go offline, edit something: it queues and nothing ever flushes it. No online listener, no visibilitychange, no 30s heartbeat, no WS reconnect hook. Register on auth success instead; registerSyncTriggers is already idempotent. The background sync loops only checked the auth gate on entry. Logout swaps the Dexie handle to the shared anonymous database, so a trip loop or a blob download still in flight afterwards wrote the logged-out account's itinerary and documents into it - the database that survives logout and greets the next person on a shared device. Check the gate per iteration. And the remote place:updated applier replaced the whole embedded place, while the local reducer right next to it preserves the assignment's own times. Same event, two answers, and the remote one wipes a scheduled 09:00-10:30 off the itinerary when someone else renames the place. One merge helper now, used by both, so they cannot drift again. The test that should have caught it seeded null times and asserted only the name. Also: moving a day note deleted before it created, and restored the note only locally when the create failed; t() interpolation was corrupted by a '$' in a parameter value; a 429 upload was treated as permanently failed; and the geolocation watch was started inside a state updater.
103 lines
4.3 KiB
TypeScript
103 lines
4.3 KiB
TypeScript
import '@testing-library/jest-dom/vitest';
|
|
import 'fake-indexeddb/auto';
|
|
import { cleanup, configure } from '@testing-library/react';
|
|
import { afterAll, afterEach, beforeAll, vi } from 'vitest';
|
|
import { server } from './helpers/msw/server';
|
|
|
|
// waitFor/findBy* default to 1s, which is enough on a dev machine but not on a
|
|
// 4-core CI runner running the whole suite in parallel forks — a multipart POST
|
|
// through MSW plus an IndexedDB write can exceed it. Still well inside the 15s
|
|
// testTimeout, so a genuinely broken assertion fails, it just takes longer.
|
|
configure({ asyncUtilTimeout: 5000 });
|
|
|
|
// Mock the websocket module so stores don't try to open real connections
|
|
vi.mock('../src/api/websocket', () => ({
|
|
connect: vi.fn(),
|
|
disconnect: vi.fn(),
|
|
getSocketId: vi.fn(() => null),
|
|
setRefetchCallback: vi.fn(),
|
|
setPreReconnectHook: vi.fn(),
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
}));
|
|
|
|
// MSW lifecycle. A cross-origin request nobody mocked is the dangerous kind: 'warn'
|
|
// lets it through, so the runner really talks to frankfurter or a tile server and settles
|
|
// a promise after the test environment is gone (see handlers/external.ts). Those fail now.
|
|
// An unhandled same-origin call stays a warning: that is a missing handler, not egress.
|
|
beforeAll(() => server.listen({
|
|
onUnhandledRequest: (request, print) => {
|
|
if (new URL(request.url).origin === location.origin) print.warning();
|
|
else print.error();
|
|
},
|
|
}));
|
|
afterEach(() => {
|
|
server.resetHandlers();
|
|
cleanup();
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
});
|
|
afterAll(() => server.close());
|
|
|
|
// ── jsdom stubs ────────────────────────────────────────────────────────────────
|
|
|
|
// Force en-US locale for toLocaleDateString so tests are deterministic on
|
|
// non-US dev machines (Windows-de-DE returns "Sonntag" instead of "Sunday").
|
|
// Only affects calls without an explicit locale — callers that pass a locale
|
|
// keep their behavior.
|
|
const _origToLocaleDateString = Date.prototype.toLocaleDateString
|
|
Date.prototype.toLocaleDateString = function (locales?: Intl.LocalesArgument, options?: Intl.DateTimeFormatOptions) {
|
|
return _origToLocaleDateString.call(this, locales ?? 'en-US', options)
|
|
}
|
|
|
|
// window.matchMedia — used by dark mode / responsive components
|
|
Object.defineProperty(window, 'matchMedia', {
|
|
writable: true,
|
|
value: vi.fn().mockImplementation((query: string) => ({
|
|
matches: false,
|
|
media: query,
|
|
onchange: null,
|
|
addListener: vi.fn(),
|
|
removeListener: vi.fn(),
|
|
addEventListener: vi.fn(),
|
|
removeEventListener: vi.fn(),
|
|
dispatchEvent: vi.fn(),
|
|
})),
|
|
});
|
|
|
|
// IntersectionObserver — used by lazy loading
|
|
// Must use a class or regular function (not arrow function) so 'new IntersectionObserver()' works
|
|
class _MockIntersectionObserver {
|
|
observe = vi.fn()
|
|
unobserve = vi.fn()
|
|
disconnect = vi.fn()
|
|
root = null
|
|
rootMargin = ''
|
|
thresholds: ReadonlyArray<number> = []
|
|
takeRecords = vi.fn(() => [])
|
|
constructor(_callback: IntersectionObserverCallback, _options?: IntersectionObserverInit) {}
|
|
}
|
|
globalThis.IntersectionObserver = _MockIntersectionObserver as unknown as typeof IntersectionObserver;
|
|
|
|
// ResizeObserver — used by resizable panels
|
|
class _MockResizeObserver {
|
|
observe = vi.fn()
|
|
unobserve = vi.fn()
|
|
disconnect = vi.fn()
|
|
constructor(_callback: ResizeObserverCallback) {}
|
|
}
|
|
globalThis.ResizeObserver = _MockResizeObserver as unknown as typeof ResizeObserver;
|
|
|
|
// URL.createObjectURL / revokeObjectURL — Node 22 URL.createObjectURL requires
|
|
// a native node:buffer Blob; passing a jsdom Blob throws ERR_INVALID_ARG_TYPE.
|
|
// Tests that need blob URLs should mock fetch to return node:buffer Blobs so
|
|
// the real URL.createObjectURL works. For tests that only need the method to
|
|
// exist without returning a real URL, stub it here as a vi.fn fallback.
|
|
if (typeof URL.createObjectURL === 'undefined') {
|
|
Object.defineProperty(URL, 'createObjectURL', { writable: true, configurable: true, value: vi.fn(() => 'blob:mock') });
|
|
Object.defineProperty(URL, 'revokeObjectURL', { writable: true, configurable: true, value: vi.fn() });
|
|
}
|
|
|
|
// Element.prototype.scrollIntoView — jsdom doesn't implement it
|
|
Element.prototype.scrollIntoView = vi.fn();
|