Files
HIPCTF2/tests/frontend/event-status.store.watcher.spec.ts
T
2026-07-23 01:19:58 +00:00

193 lines
5.5 KiB
TypeScript

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>): 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);
});
});