193 lines
5.5 KiB
TypeScript
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);
|
|
});
|
|
}); |