fix(B01): preserve question routing and nested disclosure state
This commit is contained in:
1 parent
21ca0e0fc3
commit
51531dcae8
15 files changed
+341
-55
No files matched your search
@@ -0,0 +1,64 @@
|
||||
/** A01 real component DOM regression. Offline only; does not verify browser geometry or SDK execution. */
|
||||
import assert from 'node:assert/strict'
|
||||
import { createRequire } from 'node:module'
|
||||
import type { ChatMessage } from '../src/shared/types'
|
||||
const { JSDOM } = createRequire(process.cwd() + '/package.json')(process.env.YCODE_TEST_DOM_MODULE || 'jsdom')
|
||||
const dom = new JSDOM('<!doctype html><html><body><main id="root"></main></body></html>', {pretendToBeVisual:true,url:'http://localhost/'})
|
||||
for (const key of ['window','document','Node','Element','HTMLElement','MouseEvent','KeyboardEvent','Event']) Object.defineProperty(globalThis,key,{value:dom.window[key],configurable:true})
|
||||
Object.defineProperty(globalThis,'navigator',{value:dom.window.navigator,configurable:true})
|
||||
Object.assign(globalThis,{IS_REACT_ACT_ENVIRONMENT:true})
|
||||
const React = require('react') as typeof import('react')
|
||||
const {act} = React
|
||||
const {createRoot} = require('react-dom/client') as typeof import('react-dom/client')
|
||||
const MessageBubble = require('../src/renderer/src/components/MessageBubble').default as typeof import('../src/renderer/src/components/MessageBubble').default
|
||||
const root = createRoot(document.getElementById('root')!)
|
||||
const click = (el: Element) => el.dispatchEvent(new MouseEvent('click',{bubbles:true}))
|
||||
const answer = {questions:[{id:'q1',question:'Choose a unique option',answers:['Unique answer']}]}
|
||||
const base: ChatMessage = {id:'native-question',role:'assistant',createdAt:1,content:'Final prose',status:'done',parts:[{type:'tool',toolId:'q'},{type:'text',text:'Final prose'}],tools:[{id:'q',name:'AskUserQuestion',args:{questions:[{id:'q1',question:'Choose a unique option'}]},argsRaw:'{}',status:'ok',output:'RAW SDK ANSWER'}],interactions:[{id:'i',runId:'r',kind:'question',toolCallId:'q',title:'Question',questions:[],status:'answered',answer}]}
|
||||
let checks=0
|
||||
const check=(condition:unknown,label:string)=>{assert.ok(condition,label);checks++}
|
||||
async function main(){
|
||||
for (const name of ['AskUserQuestion','askuserquestion','ask_user_question']) {
|
||||
const fixture={...base,tools:base.tools!.map(tool=>({...tool,name})),interactions:[...base.interactions!,...base.interactions!]}
|
||||
const before=JSON.stringify(fixture)
|
||||
await act(async()=>root.render(<MessageBubble key={name} message={fixture}/>))
|
||||
check(!document.body.textContent!.includes('RAW SDK ANSWER'),`${name}: no raw answer`)
|
||||
await act(async()=>click(document.querySelector('.cr-collapse--completed > .cr-collapse__header')!))
|
||||
check(document.querySelectorAll('.ask-user-question--completed').length===1,`${name}: linked duplicate interactions produce one question card`)
|
||||
await act(async()=>click(document.querySelector('.ask-user-question__header .cr-tool-head__main')!))
|
||||
check(document.querySelector('.ask-user-question__header .cr-tool-head__main')?.getAttribute('aria-expanded')==='true',`${name}: answered card actually expanded`)
|
||||
check(document.querySelectorAll('.question-answer-transcript__question').length===1,`${name}: expanded answer occurs exactly once`)
|
||||
check(document.body.textContent!.includes('Choose a unique option') && document.body.textContent!.includes('Unique answer'),`${name}: question and answer visible`)
|
||||
check(JSON.stringify(fixture)===before,`${name}: recorded source unchanged`)
|
||||
}
|
||||
// Native SDK identity throughout pending → answered → completed → outer toggle.
|
||||
const pending: ChatMessage={...base,status:'streaming',parts:[{type:'tool',toolId:'q'}],interactions:[],tools:base.tools!.map(tool=>({...tool,status:'running',output:undefined}))}
|
||||
await act(async()=>root.render(<MessageBubble key="lifecycle" message={pending}/>))
|
||||
check(!!document.querySelector('.ask-user-question--pending'),'native pending question uses specialized renderer')
|
||||
await act(async()=>root.render(<MessageBubble key="lifecycle" message={{...base,status:'streaming'}}/>))
|
||||
check(!!document.querySelector('.ask-user-question--completed'),'native answered question uses transcript while turn runs')
|
||||
await act(async()=>root.render(<MessageBubble key="lifecycle" message={base}/>))
|
||||
const outer=()=>document.querySelector('.cr-collapse--completed > .cr-collapse__header')!
|
||||
await act(async()=>click(outer()))
|
||||
const header=()=>document.querySelector('.ask-user-question__header .cr-tool-head__main')!
|
||||
if(header().getAttribute('aria-expanded')!=='true') await act(async()=>click(header()))
|
||||
await act(async()=>click(outer()));await act(async()=>click(outer()))
|
||||
check(header().getAttribute('aria-expanded')==='true','native answered choice survives completed outer collapse/reopen')
|
||||
check(!document.body.textContent!.includes('RAW SDK ANSWER'),'native lifecycle never leaks raw SDK answer')
|
||||
const imported={...base,interactions:[],tools:base.tools!.map(tool=>({...tool,output:'q1 -> Unique answer',metadata:{importedHistory:true}}))}
|
||||
await act(async()=>root.render(<MessageBubble key="imported" message={imported}/>))
|
||||
await act(async()=>click(document.querySelector('.cr-collapse--completed > .cr-collapse__header')!))
|
||||
await act(async()=>click(document.querySelector('.ask-user-question__header .cr-tool-head__main')!))
|
||||
check(document.body.textContent!.includes('Unique answer'),'historical native name and answer-text adaptation preserved')
|
||||
check(imported.tools[0].name==='AskUserQuestion','historical stored name unchanged')
|
||||
for (const interactions of [base.interactions, []]) {
|
||||
const legacy={...base,parts:undefined,interactions,tools:base.tools!.map(tool=>({...tool,metadata:{questionAnswer:answer}}))}
|
||||
await act(async()=>root.render(<MessageBubble key={`legacy-${interactions?.length}`} message={legacy}/>))
|
||||
check(document.querySelector('.question-history-unpositioned')?.getAttribute('aria-label')==='历史问答(原始位置不可用)','missing recorded position remains explicitly unknown')
|
||||
check(document.querySelectorAll('.question-answer-transcript__question').length===1,'unknown-position metadata and interaction do not duplicate')
|
||||
check(!document.body.textContent!.includes('RAW SDK ANSWER'),'unknown-position raw duplicate suppressed')
|
||||
}
|
||||
await act(async()=>root.unmount());dom.window.close()
|
||||
console.log(`question-identity: ${checks} A01 DOM checks passed (not Electron/pixel/SDK acceptance)`)
|
||||
}
|
||||
main().then(() => process.exit(0)).catch(error=>{console.error(error);process.exit(1)})
|
||||
Reference in new issue
Block a user