dom-helpers.test.ts
1 import { describe, it, expect } from 'vitest'; 2 import { autoScrollJs, waitForCaptureJs, waitForSelectorJs } from './dom-helpers.js'; 3 4 describe('autoScrollJs', () => { 5 it('returns early without error when document.body is null', async () => { 6 const g = globalThis as unknown as Record<string, unknown>; 7 const origDoc = g.document; 8 g.document = { body: null, documentElement: {} }; 9 g.window = g; 10 const code = autoScrollJs(3, 500); 11 // Should resolve without throwing 12 await expect(eval(code)).resolves.not.toThrow(); 13 g.document = origDoc; 14 delete g.window; 15 }); 16 }); 17 18 describe('waitForCaptureJs', () => { 19 it('returns a non-empty string', () => { 20 const code = waitForCaptureJs(1000); 21 expect(typeof code).toBe('string'); 22 expect(code.length).toBeGreaterThan(0); 23 expect(code).toContain('__opencli_xhr'); 24 expect(code).toContain('resolve'); 25 expect(code).toContain('reject'); 26 }); 27 28 it('resolves "captured" when __opencli_xhr is populated before deadline', async () => { 29 const g = globalThis as unknown as Record<string, unknown>; 30 const captured: unknown[] = []; 31 g.__opencli_xhr = captured; 32 g.window = g; // stub window for Node eval 33 const code = waitForCaptureJs(1000); 34 const promise = eval(code) as Promise<string>; 35 captured.push({ data: 'test' }); 36 await expect(promise).resolves.toBe('captured'); 37 delete g.__opencli_xhr; 38 delete g.window; 39 }); 40 41 it('rejects when __opencli_xhr stays empty past deadline', async () => { 42 const g = globalThis as unknown as Record<string, unknown>; 43 g.__opencli_xhr = []; 44 g.window = g; 45 const code = waitForCaptureJs(50); // 50ms timeout 46 const promise = eval(code) as Promise<string>; 47 await expect(promise).rejects.toThrow('No network capture within 0.05s'); 48 delete g.__opencli_xhr; 49 delete g.window; 50 }); 51 52 it('resolves immediately when __opencli_xhr already has data', async () => { 53 const g = globalThis as unknown as Record<string, unknown>; 54 g.__opencli_xhr = [{ data: 'already here' }]; 55 g.window = g; 56 const code = waitForCaptureJs(1000); 57 await expect(eval(code) as Promise<string>).resolves.toBe('captured'); 58 delete g.__opencli_xhr; 59 delete g.window; 60 }); 61 }); 62 63 describe('waitForSelectorJs', () => { 64 it('returns a non-empty string', () => { 65 const code = waitForSelectorJs('#app', 1000); 66 expect(typeof code).toBe('string'); 67 expect(code).toContain('#app'); 68 expect(code).toContain('querySelector'); 69 expect(code).toContain('MutationObserver'); 70 }); 71 72 it('resolves "found" immediately when selector already present', async () => { 73 const g = globalThis as unknown as Record<string, unknown>; 74 const fakeEl = { tagName: 'DIV' }; 75 g.document = { querySelector: (_: string) => fakeEl }; 76 const code = waitForSelectorJs('[data-testid="primaryColumn"]', 1000); 77 await expect(eval(code) as Promise<string>).resolves.toBe('found'); 78 delete g.document; 79 }); 80 81 it('resolves "found" when selector appears after DOM mutation', async () => { 82 const g = globalThis as unknown as Record<string, unknown>; 83 let mutationCallback!: () => void; 84 g.MutationObserver = class { 85 constructor(cb: () => void) { mutationCallback = cb; } 86 observe() {} 87 disconnect() {} 88 }; 89 let calls = 0; 90 g.document = { 91 querySelector: (_: string) => (calls++ > 0 ? { tagName: 'DIV' } : null), 92 body: {}, 93 }; 94 const code = waitForSelectorJs('#app', 1000); 95 const promise = eval(code) as Promise<string>; 96 mutationCallback(); // simulate DOM mutation 97 await expect(promise).resolves.toBe('found'); 98 delete g.document; 99 delete g.MutationObserver; 100 }); 101 102 it('rejects when selector never appears within timeout', async () => { 103 const g = globalThis as unknown as Record<string, unknown>; 104 g.MutationObserver = class { 105 constructor(_cb: () => void) {} 106 observe() {} 107 disconnect() {} 108 }; 109 g.document = { querySelector: (_: string) => null, body: {} }; 110 const code = waitForSelectorJs('#missing', 50); 111 await expect(eval(code) as Promise<string>).rejects.toThrow('Selector not found: #missing'); 112 delete g.document; 113 delete g.MutationObserver; 114 }); 115 });