import { Injector, runInInjectionContext } from '@angular/core'; import { EventStatusStore } from '../../frontend/src/app/core/services/event-status.store'; import { EventStatePayload } from '../../frontend/src/app/core/services/event-status.pure'; describe('EventStatusStore auto-reload watcher (Job 905)', () => { let store: EventStatusStore; function payload(over: Partial): EventStatePayload { const start = new Date(Date.now() + 60_000).toISOString(); const end = new Date(Date.now() + 7_200_000).toISOString(); return { state: 'countdown', serverNowUtc: new Date().toISOString(), eventStartUtc: start, eventEndUtc: end, secondsToStart: 60, secondsToEnd: null, ...over, }; } beforeEach(() => { jest.useFakeTimers(); const injector = Injector.create({ providers: [] }); store = runInInjectionContext(injector, () => new EventStatusStore()); }); afterEach(() => { if (store) store.stop(); jest.useRealTimers(); }); it('fires the reload handler exactly once when secondsToStart crosses zero via the real setInterval path', () => { const handler = jest.fn(); store.reloadAtCountdownZero(handler); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 2, eventStartUtc: new Date(Date.now() + 2000).toISOString() }), ); jest.advanceTimersByTime(3000); expect(handler).toHaveBeenCalledTimes(1); }); it('re-arms the latch on a state transition so a later end-of-event zero reloads again', () => { const handler = jest.fn(); store.reloadAtCountdownZero(handler); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(handler).toHaveBeenCalledTimes(1); store.applyServerStatus( payload({ state: 'running', secondsToStart: null, secondsToEnd: 9001, eventStartUtc: new Date(Date.now() - 60_000).toISOString(), eventEndUtc: new Date(Date.now() + 9001_000).toISOString(), }), ); jest.advanceTimersByTime(1000); expect(handler).toHaveBeenCalledTimes(1); store.applyServerStatus( payload({ state: 'running', secondsToStart: null, secondsToEnd: 0, eventStartUtc: new Date(Date.now() - 60_000).toISOString(), eventEndUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(handler).toHaveBeenCalledTimes(2); }); it('registering a new handler replaces the previous one without double-firing', () => { const first = jest.fn(); const second = jest.fn(); store.reloadAtCountdownZero(first); store.reloadAtCountdownZero(second); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(first).not.toHaveBeenCalled(); expect(second).toHaveBeenCalledTimes(1); }); it('stop() resets the latch so a fresh handler can fire on the next zero', () => { const first = jest.fn(); store.reloadAtCountdownZero(first); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(first).toHaveBeenCalledTimes(1); store.stop(); const second = jest.fn(); store.reloadAtCountdownZero(second); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(second).toHaveBeenCalledTimes(1); }); it('does not disarm the latch when the same zero-countdown payload is delivered repeatedly', () => { const handler = jest.fn(); store.reloadAtCountdownZero(handler); const frame = payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }); store.applyServerStatus(frame); store.applyServerStatus(frame); store.applyServerStatus(frame); jest.advanceTimersByTime(2000); expect(handler).toHaveBeenCalledTimes(1); }); it('subscribeReloadAtCountdownZero unregister prevents the handler from firing', () => { const handler = jest.fn(); const unsubscribe = store.subscribeReloadAtCountdownZero(handler); unsubscribe(); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(handler).not.toHaveBeenCalled(); }); it('start() preserves a previously registered reload handler (Job 905 handler ownership)', () => { const handler = jest.fn(); store.subscribeReloadAtCountdownZero(handler); const fakeSource = { addEventListener: jest.fn(), close: jest.fn(), }; store.start(() => fakeSource as any); expect((store as any).reloadOnZero).toBe(handler); store.applyServerStatus( payload({ state: 'countdown', secondsToStart: 0, eventStartUtc: new Date(Date.now() - 1000).toISOString(), }), ); jest.advanceTimersByTime(2000); expect(handler).toHaveBeenCalledTimes(1); }); });