import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ScreenPoller } from '../../src/bridge/screen-poller.js'; // Mock CmuxClient function createMockClient(screenContent = 'line1\nline2\nline3') { return { readScreen: vi.fn().mockResolvedValue(screenContent), sendText: vi.fn(), sendKey: vi.fn(), } as any; } describe('ScreenPoller', () => { beforeEach(() => { vi.useFakeTimers(); }); it('emits update on first poll', async () => { const client = createMockClient(); const poller = new ScreenPoller(client, 200); const updates: any[] = []; poller.on('update', (u: any) => updates.push(u)); poller.subscribe('client1', 'workspace:1', 'surface:1'); // Fast-forward past first poll await vi.advanceTimersByTimeAsync(50); expect(updates.length).toBe(1); expect(updates[0].full).toBe(true); expect(updates[0].lines).toEqual(['line1', 'line2', 'line3']); poller.destroy(); }); it('does not emit when content is unchanged', async () => { const client = createMockClient(); const poller = new ScreenPoller(client, 200); const updates: any[] = []; poller.on('update', (u: any) => updates.push(u)); poller.subscribe('client1', 'workspace:1', 'surface:1'); // First poll await vi.advanceTimersByTimeAsync(50); expect(updates.length).toBe(1); // Second poll — same content await vi.advanceTimersByTimeAsync(250); expect(updates.length).toBe(1); poller.destroy(); }); it('sends diff patches when content changes', async () => { const client = createMockClient('line1\nline2\nline3'); const poller = new ScreenPoller(client, 200); const updates: any[] = []; poller.on('update', (u: any) => updates.push(u)); poller.subscribe('client1', 'workspace:1', 'surface:1'); // First poll await vi.advanceTimersByTimeAsync(50); expect(updates.length).toBe(1); // Change content client.readScreen.mockResolvedValue('line1\nline2-changed\nline3'); // Second poll await vi.advanceTimersByTimeAsync(250); expect(updates.length).toBe(2); expect(updates[1].full).toBe(false); expect(updates[1].patches).toBeDefined(); poller.destroy(); }); it('stops polling when all subscribers unsubscribe', async () => { const client = createMockClient(); const poller = new ScreenPoller(client, 200); poller.subscribe('client1', 'workspace:1', 'surface:1'); await vi.advanceTimersByTimeAsync(50); poller.unsubscribe('client1', 'surface:1'); const callCount = client.readScreen.mock.calls.length; await vi.advanceTimersByTimeAsync(500); // Should not have polled again expect(client.readScreen.mock.calls.length).toBe(callCount); poller.destroy(); }); it('resets to fast polling on resetToFast', async () => { const client = createMockClient(); const poller = new ScreenPoller(client, 200); poller.subscribe('client1', 'workspace:1', 'surface:1'); await vi.advanceTimersByTimeAsync(50); // Simulate idle for (let i = 0; i < 5; i++) { await vi.advanceTimersByTimeAsync(250); } poller.resetToFast('surface:1'); // Should have reset idle count internally poller.destroy(); }); });