import { describe, it, expect } from 'vitest'; import { OverlaySyncService } from 'packages/automation/application/services/OverlaySyncService'; import type { AutomationEvent } from 'packages/automation/application/ports/IAutomationEventPublisher'; import type { IAutomationLifecycleEmitter, LifecycleCallback, } from 'packages/automation/infrastructure/adapters/IAutomationLifecycleEmitter'; import type { OverlayAction, ActionAck, } from 'packages/automation/application/ports/IOverlaySyncPort'; class TestLifecycleEmitter implements IAutomationLifecycleEmitter { private callbacks: Set = new Set(); onLifecycle(cb: LifecycleCallback): void { this.callbacks.add(cb); } offLifecycle(cb: LifecycleCallback): void { this.callbacks.delete(cb); } async emit(event: AutomationEvent): Promise { for (const cb of Array.from(this.callbacks)) { await cb(event); } } } class RecordingPublisher { public events: AutomationEvent[] = []; async publish(event: AutomationEvent): Promise { this.events.push(event); } } describe('Overlay lifecycle (integration)', () => { it('emits modal-opened and confirms after action-started in sane order', async () => { const lifecycleEmitter = new TestLifecycleEmitter(); const publisher = new RecordingPublisher(); type LoggerLike = { debug: (...args: unknown[]) => void; info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; }; const logger = console as unknown as LoggerLike; const service = new OverlaySyncService({ lifecycleEmitter, publisher, logger, defaultTimeoutMs: 1_000, }); const action: OverlayAction = { id: 'hosted-session', label: 'Starting hosted session', }; const ackPromise: Promise = service.startAction(action); expect(publisher.events.length).toBe(1); const first = publisher.events[0]; expect(first.type).toBe('modal-opened'); expect(first.actionId).toBe('hosted-session'); await lifecycleEmitter.emit({ type: 'panel-attached', actionId: 'hosted-session', timestamp: Date.now(), payload: { selector: '#gridpilot-overlay' }, }); await lifecycleEmitter.emit({ type: 'action-started', actionId: 'hosted-session', timestamp: Date.now(), }); const ack = await ackPromise; expect(ack.id).toBe('hosted-session'); expect(ack.status).toBe('confirmed'); expect(publisher.events[0].type).toBe('modal-opened'); expect(publisher.events[0].actionId).toBe('hosted-session'); }); it('emits panel-missing when cancelAction is called', async () => { const lifecycleEmitter = new TestLifecycleEmitter(); const publisher = new RecordingPublisher(); type LoggerLike = { debug: (...args: unknown[]) => void; info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; }; const logger = console as unknown as LoggerLike; const service = new OverlaySyncService({ lifecycleEmitter, publisher, logger, }); await service.cancelAction('hosted-session-cancel'); expect(publisher.events.length).toBe(1); const ev = publisher.events[0]; expect(ev.type).toBe('panel-missing'); expect(ev.actionId).toBe('hosted-session-cancel'); }); });