mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 06:11:45 +00:00
fix(sync): re-hydrate active trip store on reconnect/online (H1) (#1181)
setRefetchCallback was dead code, so on reconnect the queue flushed and Dexie re-seeded but the open trip's Zustand store was never refreshed — a collaborator's edits made while we were offline didn't appear until navigating away and back. - new tripStore.hydrateActiveTrip(): silent refresh of the active trip's collaborative state (days/places/packing/todo/budget/reservations/files), no resetTrip and no isLoading toggle so there's no splash on reconnect - syncTriggers wires setRefetchCallback to it (WS layer awaits the flush hook first) and re-hydrates open trips after the online-event syncAll; cleared on unregister - websocket exposes getActiveTrips() for the online-event path - tests: refetch wiring + ordering, silent hydrate without reset/splash
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* syncTriggers — reconnect/online wiring (H1).
|
||||
*
|
||||
* Verifies the previously-dead refetch path is wired: on WS reconnect and on the
|
||||
* `online` event the active trip's store is re-hydrated (after the queue flush).
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
|
||||
const flush = vi.fn(() => Promise.resolve());
|
||||
const syncAll = vi.fn(() => Promise.resolve());
|
||||
const hydrate = vi.fn(() => Promise.resolve());
|
||||
|
||||
let refetchCb: ((tripId: string) => void) | null = null;
|
||||
let preReconnect: (() => Promise<void>) | null = null;
|
||||
|
||||
vi.mock('../../../src/sync/mutationQueue', () => ({
|
||||
mutationQueue: { flush: () => flush() },
|
||||
}));
|
||||
vi.mock('../../../src/sync/tripSyncManager', () => ({
|
||||
tripSyncManager: { syncAll: () => syncAll() },
|
||||
}));
|
||||
vi.mock('../../../src/api/websocket', () => ({
|
||||
setPreReconnectHook: (fn: (() => Promise<void>) | null) => { preReconnect = fn; },
|
||||
setRefetchCallback: (fn: ((tripId: string) => void) | null) => { refetchCb = fn; },
|
||||
getActiveTrips: () => ['7'],
|
||||
}));
|
||||
vi.mock('../../../src/store/tripStore', () => ({
|
||||
useTripStore: { getState: () => ({ hydrateActiveTrip: hydrate }) },
|
||||
}));
|
||||
|
||||
import { registerSyncTriggers, unregisterSyncTriggers } from '../../../src/sync/syncTriggers';
|
||||
|
||||
const flushMicrotasks = async () => {
|
||||
for (let i = 0; i < 5; i++) await Promise.resolve();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
flush.mockClear(); syncAll.mockClear(); hydrate.mockClear();
|
||||
refetchCb = null; preReconnect = null;
|
||||
Object.defineProperty(navigator, 'onLine', { value: true, writable: true, configurable: true });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
unregisterSyncTriggers();
|
||||
});
|
||||
|
||||
describe('syncTriggers', () => {
|
||||
it('registers a refetch callback that hydrates the active trip', () => {
|
||||
registerSyncTriggers();
|
||||
expect(refetchCb).toBeTypeOf('function');
|
||||
refetchCb!('7');
|
||||
expect(hydrate).toHaveBeenCalledWith('7');
|
||||
});
|
||||
|
||||
it('also registers the pre-reconnect flush hook', () => {
|
||||
registerSyncTriggers();
|
||||
expect(preReconnect).toBeTypeOf('function');
|
||||
});
|
||||
|
||||
it('clears both reconnect hooks on unregister', () => {
|
||||
registerSyncTriggers();
|
||||
unregisterSyncTriggers();
|
||||
expect(refetchCb).toBeNull();
|
||||
expect(preReconnect).toBeNull();
|
||||
});
|
||||
|
||||
it('online event flushes, then re-seeds Dexie and re-hydrates active trips', async () => {
|
||||
registerSyncTriggers();
|
||||
window.dispatchEvent(new Event('online'));
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(flush).toHaveBeenCalled();
|
||||
expect(syncAll).toHaveBeenCalled();
|
||||
expect(hydrate).toHaveBeenCalledWith('7');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user