/** 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) })