import { JSDOM } from 'jsdom'; import { describe, expect, it, vi } from 'vitest'; import { ArgumentError, TimeoutError } from '@jackwener/opencli/errors'; import { buildChatwiseInjectTextJs, buildChatwiseMessageCountJs, buildChatwiseResponseAfterJs, requirePositiveTimeout, scoreChatwiseComposerCandidate, selectBestChatwiseComposer, } from './utils.js'; import { askCommand } from './ask.js'; function candidate(overrides = {}) { return { index: 0, hidden: false, role: 'textbox', classes: 'cm-content cm-lineWrapping', editorClasses: 'cm-editor', ariaLabel: '', placeholder: '', text: '', rect: { y: 0, h: 30 }, ...overrides, }; } function runBrowserScript(html, script) { const dom = new JSDOM(html, { url: 'app://chatwise.local/', runScripts: 'outside-only' }); Object.defineProperty(dom.window.HTMLElement.prototype, 'offsetWidth', { configurable: true, get: () => 400 }); Object.defineProperty(dom.window.HTMLElement.prototype, 'offsetHeight', { configurable: true, get: () => 32 }); dom.window.HTMLElement.prototype.getClientRects = () => [{ length: 1 }]; dom.window.document.execCommand = () => false; return { dom, result: dom.window.eval(script) }; } function makePage(evaluateResults = []) { const evaluate = vi.fn(); for (const result of evaluateResults) evaluate.mockResolvedValueOnce(result); evaluate.mockResolvedValue(null); return { evaluate, wait: vi.fn().mockResolvedValue(undefined), pressKey: vi.fn().mockResolvedValue(undefined), }; } describe('chatwise composer selection', () => { it('prefers the main composer over auxiliary contenteditable editors', () => { const mainComposer = candidate({ index: 0, placeholder: 'placeholder Enter a message here, press ⏎ to send', rect: { y: 860, h: 32 }, }); const optionalDescription = candidate({ index: 1, placeholder: 'placeholder Optional description', editorClasses: 'cm-editor simple-editor', rect: { y: 400, h: 32 }, }); const userContext = candidate({ index: 2, text: '# User Context Document', editorClasses: 'cm-editor simple-editor', rect: { y: 460, h: 1200 }, }); expect(scoreChatwiseComposerCandidate(mainComposer, 900)).toBeGreaterThan( scoreChatwiseComposerCandidate(optionalDescription, 900), ); expect(scoreChatwiseComposerCandidate(mainComposer, 900)).toBeGreaterThan( scoreChatwiseComposerCandidate(userContext, 900), ); expect(selectBestChatwiseComposer([ optionalDescription, userContext, mainComposer, ], 900)?.index).toBe(0); }); it('rejects hidden or low-confidence candidates instead of injecting into the wrong editor', () => { expect(selectBestChatwiseComposer([ candidate({ index: 0, hidden: true, placeholder: 'Enter a message here, press ⏎ to send', rect: { y: 860, h: 32 }, }), ], 900)).toBeNull(); expect(selectBestChatwiseComposer([ candidate({ index: 1, placeholder: 'Optional description', editorClasses: 'cm-editor simple-editor', rect: { y: 860, h: 32 }, }), candidate({ index: 2, text: '# User Context Document', editorClasses: 'cm-editor simple-editor', rect: { y: 870, h: 32 }, }), ], 900)).toBeNull(); }); it('injects text into the scored main composer instead of the last contenteditable', () => { const html = `