/** Offline real React DOM regression. Not a browser layout/pixel or native Electron test. * Usage: YCODE_TEST_DOM_MODULE=/path/to/node_modules/jsdom node scripts/test-recommended-skills-dom.mjs * --build-only creates a browser fixture without loading jsdom or Electron. No dependency installation. */ import fs from 'node:fs' import path from 'node:path' import assert from 'node:assert/strict' import { createRequire } from 'node:module' const require = createRequire(import.meta.url) const base = path.resolve('.tmp'); fs.mkdirSync(base, { recursive: true }) const output = fs.mkdtempSync(path.join(base, 'recommended-skills-dom-')) const source = path.resolve('src/renderer/src/capabilities/CapabilityCenter.tsx') fs.writeFileSync(path.join(output, 'fixture.tsx'), `import React from 'react';import{createRoot}from'react-dom/client';import SkillsPage from${JSON.stringify(source)};const root=createRoot(document.getElementById('root'));window.__mount=()=>root.render({}} cwd="/controlled/workspace" onUseSkill={s=>window.__uses.push(s)}/>);window.__unmount=()=>root.unmount();window.__mount();`) await require('esbuild').build({ entryPoints: [path.join(output, 'fixture.tsx')], outfile: path.join(output, 'fixture.js'), bundle: true, platform: 'browser', format: 'iife', jsx: 'automatic', loader: { '.svg': 'dataurl', '.png': 'dataurl', '.jpg': 'dataurl', '.woff': 'dataurl', '.woff2': 'dataurl', '.ttf': 'dataurl' }, logLevel: 'error' }) if (process.argv.includes('--build-only')) { console.log(JSON.stringify({ result: 'BUILD_ONLY', output, limits: 'Compiled real React/CSS only; no UI execution or visual verification.' })); process.exit(0) } let JSDOM try { ({ JSDOM } = require(process.env.YCODE_TEST_DOM_MODULE || process.env.YCODE_JSDOM_MODULE || 'jsdom')) } catch { throw Error('Provide an already-installed jsdom through YCODE_TEST_DOM_MODULE; this test never installs dependencies.') } const rec = { id:'archify', name:'Archify', description:'Controlled recommendation', author:'fixture', license:'MIT', repository:'fixture/archify', version:'3', commit:'a'.repeat(40), packagePath:'archify.zip', sha256:'b'.repeat(64), expectedBytes:100, containsScripts:true } const current = { id:'user:rec', name:rec.name, description:rec.description, version:rec.version, path:'/controlled/archify', scope:'user', enabled:true, userInvocable:true, disableModelInvocation:false, source:'imported', userModified:false, origin:{recommendationId:rec.id, repository:rec.repository, commit:rec.commit, packagePath:rec.packagePath, archiveSha256:rec.sha256, version:rec.version, installedAt:1} } const checks = [], errors = [] let installed=[], failEnable=false, failCatalog=false, catalog=[rec], changed=new Set(), progressListeners=new Set(), progress=null, calls=[], sequence=0, holdInstall=null const dom = new JSDOM('
', { url:'https://fixture.invalid', runScripts:'outside-only', pretendToBeVisual:true }) const w=dom.window, d=w.document w.addEventListener('error', e=>errors.push(e.message)) w.__uses=[] w.fetch=()=>{ throw Error('Network is forbidden in this fixture') } w.matchMedia=()=>({ matches:false, addEventListener(){}, removeEventListener(){} }) w.ResizeObserver=class{observe(){} unobserve(){} disconnect(){}} const notify=()=>{ for(const listener of changed) listener() } const emit=(phase, operationId='foreign')=>{progress={phase,operationId,recommendationId:rec.id,receivedBytes:0,totalBytes:100,updatedAt:++sequence};for(const listener of progressListeners)listener(progress)} w.ycode={skills:{ list:async()=>installed, recommendations:async()=>{if(failCatalog)throw Error('controlled catalog failure'); return catalog}, onChanged:fn=>{changed.add(fn);return()=>changed.delete(fn)}, onInstallProgress:fn=>{progressListeners.add(fn);return()=>progressListeners.delete(fn)}, installState:async()=>progress, read:async()=>({skill:current,content:'Controlled content',files:[]}), setEnabled:async(id,enabled)=>{calls.push(['enable',id]);if(failEnable)throw Error('controlled enable failure');installed=installed.map(s=>s.id===id?{...s,enabled}:s);notify()}, installRecommended:async(id,options)=>{calls.push(['install',id,options]);emit('downloading',options.operationId);await new Promise(resolve=>{holdInstall=resolve});installed=[current];emit('installed',options.operationId);notify();return current}, cancelInstall:async()=>({cancelled:true}),remove:async()=>{},export:async()=>null,importPath:async()=>null },resources:{openExternal:async()=>{throw Error('External navigation forbidden')}}} const pause=()=>new Promise(resolve=>setTimeout(resolve,15)) const wait=async(fn,label='condition')=>{for(let i=0;i<150;i++){if(fn())return;await pause()}throw Error('Timed out: '+label)} const check=(label,value)=>{assert.ok(value,label); checks.push(label)} const card=()=>d.querySelector('.yc-skill-recommendation-card') const button=label=>d.querySelector(`[aria-label="${label}"]`) const click=async(el)=>{assert.ok(el,'click target exists');el.click();await pause()} const set=async(skills,state)=>{installed=skills;notify();await wait(()=>card()?.dataset.recommendationState===state,state);await pause()} const close=async()=>{if(button('关闭技能详情'))await click(button('关闭技能详情'))} try { w.eval(fs.readFileSync(path.join(output,'fixture.js'),'utf8')) await wait(()=>card(),'initial render') check('uninstalled uses add slot',!!card().querySelector('.sm-add-btn')) check('normal footer removed from DOM',!d.querySelector('.yc-skill-recommendation-footer')&&!d.body.textContent.includes('查看已安装技能')) await set([current],'current') check('current uses installed CardFrame',!!card().querySelector('.sm-card--installed')) check('current removes Plus/add DOM',!card().querySelector('.sm-add-btn')) check('current use occupies status slot exactly once',card().querySelectorAll('.sm-status .sm-use-btn').length===1&&!card().querySelector('.yc-skill-recommendation-meta button')) await click(card().querySelector('.sm-use-btn')) check('use invokes one draft callback without installation',w.__uses.length===1&&!calls.some(c=>c[0]==='install')) check('use does not open install detail',!d.querySelector('[role=dialog]')) check('top nav count reflects installed list',button('已安装技能').textContent.includes('(1)')) await click(button('已安装技能'));await wait(()=>d.querySelector('[data-skill-id="user:rec"]')) await click(button('返回专家中心'));await wait(()=>card()) check('reopen retains current state',card().dataset.recommendationState==='current') await set([{...current,enabled:false}],'current');failEnable=true await click(card().querySelector('.sm-use-btn'));await wait(()=>d.body.textContent.includes('controlled enable failure')) check('enable failure prevents draft invocation',w.__uses.length===1) failEnable=false;await click(card().querySelector('.sm-use-btn'));await wait(()=>w.__uses.length===2) check('disabled skill enabled before callback',w.__uses.at(-1).enabled===true&&calls.filter(c=>c[0]==='enable').length===2) await set([{...current,userInvocable:false}],'current') check('non-invocable current cannot use or reinstall',card().querySelector('.sm-use-btn')?.disabled&&!card().querySelector('.sm-add-btn')) for(const [name,skill,state,label] of [ ['outdated',{...current,origin:{...current.origin,version:'2'}},'outdated','更新 Archify'], ['modified',{...current,userModified:true},'modified','替换 Archify'], ['broken',{...current,error:'bad document'},'outdated','修复 Archify'], ['same-name local',{...current,origin:undefined,userModified:true},'conflict','替换 Archify'], ['broken same-name local',{...current,origin:undefined,error:'bad local'},'conflict','替换 Archify'], ['different source',{...current,origin:{...current.origin,repository:'other/source'}},'outdated','更新 Archify'] ]) { await set([skill],state);check(name+' installed identity distinction',!!card().querySelector('.sm-card--installed')===(state!=='conflict'));check(name+' not represented as first add',!card().querySelector('.sm-add-btn')&&!!button(label));const before=calls.length await click(button(label));check(name+' requires explicit confirmation',!!button('确认安装推荐技能')&&calls.length===before);await close() } await set([{...current,id:'workspace:rec',scope:'workspace'}],'none') check('workspace is not treated as managed current',!!card().querySelector('.sm-add-btn')&&!card().querySelector('.sm-card--installed')) await set([],'none');await click(button('安装 Archify')) const staleConfirm=button('确认安装推荐技能');const beforeExternal=calls.filter(c=>c[0]==='install').length await set([current],'current');staleConfirm.click();await pause() check('external current while confirming prevents duplicate installation',calls.filter(c=>c[0]==='install').length===beforeExternal);await close() await set([],'none');await click(button('安装 Archify')) check('first install confirms provenance and scripts',d.querySelector('[role=dialog]').textContent.includes('fixture/archify')&&d.querySelector('[role=dialog]').textContent.includes('不会执行仓库中的任何安装命令')) const confirm=button('确认安装推荐技能');confirm.click();confirm.click();await wait(()=>holdInstall) check('duplicate confirmation issues only one request',calls.filter(c=>c[0]==='install').length===1) check('real host progress rendered',d.querySelector('[data-install-phase="downloading"]')) holdInstall();await wait(()=>card()?.dataset.recommendationState==='current');await close() check('install success automatically switches action DOM',!card().querySelector('.sm-add-btn')&&!!card().querySelector('.sm-use-btn')) await set([],'none');check('external uninstall restores add',!!card().querySelector('.sm-add-btn')) emit('downloading');await wait(()=>d.querySelector('[data-foreign-install=true]')) check('external install blocks new add',card().querySelector('.sm-add-btn').disabled) emit('installed');await wait(()=>!d.querySelector('[data-foreign-install=true]')) check('external terminal progress clears block',!card().querySelector('.sm-add-btn').disabled) // Unmount/remount via existing navigation reloads catalog, without touching real storage. failCatalog=true;await click(button('已安装技能'));await click(button('返回专家中心'));await wait(()=>d.querySelector('.yc-skill-recommendation-unavailable')) check('unavailable has no duplicate installed entry',!d.body.textContent.includes('查看已安装技能')&&!!button('已安装技能')) failCatalog=false;await click(d.querySelector('.yc-skill-recommendation-unavailable button'));await wait(()=>card()) check('catalog retry recovers in place',!!card()) catalog=[];await click(button('已安装技能'));await click(button('返回专家中心'));await wait(()=>d.querySelector('.yc-skill-recommendation-unavailable')) check('empty catalog has no footer',!d.querySelector('.yc-skill-recommendation-footer')&&!d.body.textContent.includes('查看已安装技能')) for(const tab of ['SkillHub','套件']) {await click([...d.querySelectorAll('[role=tab]')].find(e=>e.textContent===tab));check(tab+' has only top installed navigation',!d.body.textContent.includes('查看已安装技能')&&!!button('已安装技能'))} check('no uncaught renderer errors',errors.length===0) const report={result:'PASS',checks,calls,errors,limits:'Real React in offline jsdom; callback boundary tested, not production composer dispatch. No native Chromium, computed layout, screenshot, theme/hover/focus pixels, model SDK, actual install or network.'} fs.writeFileSync(path.join(output,'report.json'),JSON.stringify(report,null,2));console.log(JSON.stringify({result:'PASS',checks:checks.length,output})) } catch(error) {fs.writeFileSync(path.join(output,'failure.json'),JSON.stringify({error:String(error),stack:error.stack,checks,errors,html:d.body.innerHTML},null,2));console.error('Evidence:',output);throw error} finally {w.__unmount?.();w.close()}