/** B01 A04: structural/CSS and DOM contracts, not browser geometry or pixel acceptance. */ import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { createRequire } from 'node:module' import React, { act } from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { createRoot } from 'react-dom/client' import type { AskUserQuestionTool } from '../src/renderer/src/workbuddy/conversation/AskUserQuestion' import { AskUserQuestion } from '../src/renderer/src/workbuddy/conversation/AskUserQuestion' const tool: AskUserQuestionTool = { name: 'AskUserQuestion', status: 'success', metadata: { questionAnswer: { questions: [{ id: 'q', question: 'Which runtime?', answers: ['Node.js'] }] } } } const markup = renderToStaticMarkup() assert.match(markup, /cr-tool-head/) assert.match(markup, /cr-tool-exp__content ask-user-question__content/) assert.doesNotMatch(markup, /ask-user-question__(?:icon|arrow|title)/) const css = readFileSync('src/renderer/src/workbuddy/conversation/conversation.css', 'utf8') const recovered = readFileSync('src/renderer/src/workbuddy/conversation/tool-result-source.css', 'utf8') assert.match(css, /\.ask-user-question--completed \{ background: transparent; border: 0; border-radius: 0; padding: 2px 0; \}/) assert.match(css, /\.ask-user-question--completed \.ask-user-question__content \{ border: 0; \}/) // Contract is sourced from the checked-in REA-recovered ToolExpandable, not the old local 360px card. const contentRule = [...recovered.matchAll(/(?:^|})\.cr-tool-exp__content\{([^}]+)\}/g)].map(match => match[1]).join(';') for (const declaration of ['background:var(--cr-bg-primary-default,#2a2c31)', 'border-radius:16px', 'max-height:300px', 'padding:12px 16px', 'overflow:auto']) assert.ok(contentRule.includes(declaration), declaration) for (const kind of ['question', 'answer']) assert.match(css, new RegExp(`\\.question-answer-transcript__${kind} \\{[^}]*font-size: var\\(--yc-font-size-5\\); font-weight: 400; line-height: 1\\.75;`)) console.log('question-answer-shell: structural and recovered CSS contracts passed') async function main() { if (!process.env.YCODE_TEST_DOM_MODULE) { console.log('question-answer-shell: DOM checks NOT RUN (set YCODE_TEST_DOM_MODULE to an available jsdom module)') return } const { JSDOM } = createRequire(process.cwd() + '/package.json')(process.env.YCODE_TEST_DOM_MODULE || 'jsdom') const dom = new JSDOM('
', { pretendToBeVisual: true }) for (const key of ['window', 'document', 'Node', 'Element', 'HTMLElement', 'MouseEvent', 'KeyboardEvent']) Object.defineProperty(globalThis, key, { value: dom.window[key], configurable: true }) Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }) const root = createRoot(document.querySelector('#root')!) await act(async () => root.render()) const header = document.querySelector('.ask-user-question__header [role="button"]')! const content = document.getElementById(header.getAttribute('aria-controls')!)! assert.ok(header) assert.equal(header.tabIndex, 0) assert.equal(header.getAttribute('aria-expanded'), 'false') assert.equal(content.getAttribute('aria-hidden'), 'true') assert.ok(content.hasAttribute('inert')) assert.equal(content.contains(header), false, 'title and toggle must be outside gray content') assert.equal(content.querySelectorAll('.question-answer-transcript__item').length, 1) for (const key of ['Enter', ' ']) { await act(async () => { header.dispatchEvent(new dom.window.KeyboardEvent('keydown', { key, bubbles: true, cancelable: true })) }) assert.equal(header.getAttribute('aria-expanded'), key === 'Enter' ? 'true' : 'false') assert.equal(content.hasAttribute('inert'), key !== 'Enter') } await act(async () => header.click()) assert.equal(header.getAttribute('aria-expanded'), 'true') assert.equal(content.getAttribute('aria-hidden'), 'false') assert.match(content.textContent!, /Which runtime\?Node.js/) await act(async () => root.unmount()) dom.window.close() console.log('question-answer-shell: DOM hierarchy, controls linkage, click, Enter, Space and inert contracts passed') } main().then(() => process.exit(0)).catch(error => { console.error(error); process.exit(1) })