'use strict'; const assert = require('node:assert'); const fs = require('node:fs'); const path = require('node:path'); const rendererSrc = fs.readFileSync( path.join(__dirname, '..', 'renderer', 'renderer.js'), 'utf8' ); let passed = 0; function check(name, fn) { try { fn(); console.log(' ok -', name); passed++; } catch (e) { console.error(' FAIL -', name, e.message); process.exitCode = 1; } } // ---------- buildCard 抽函数 ---------- check('renderer.js 定义了 buildCard 函数', () => { assert.ok( /function\s+buildCard\s*\(/.test(rendererSrc), '找不到 buildCard(c, i) 定义' ); }); check('render() 调用 buildCard 而不是手写卡片构建', () => { const renderFn = rendererSrc.match(/function\s+render\s*\(\s*\)\s*\{[\s\S]*?\n\}/); assert.ok(renderFn, '找不到 render()'); assert.ok( /buildCard\s*\(/.test(renderFn[0]), 'render() 没调用 buildCard' ); // 抽函数后,render() 内不应该有 cloneNode(true) 这种属于 buildCard 的细节 assert.ok( !/cloneNode\s*\(\s*true\s*\)/.test(renderFn[0]), 'render() 还在做 cloneNode,buildCard 抽得不够彻底' ); }); // ---------- onAdded 增量插入路径 ---------- check('onAdded 在 history Tab + 无搜索时走 prepend 而非 reload', () => { const onAdded = rendererSrc.match(/onAdded\s*\(\s*\(\s*\w+\s*\)\s*=>\s*\{[\s\S]*?\}\s*\)/); assert.ok(onAdded, '找不到 onAdded 回调'); // 存在非 reload 的快路径:增量插入。reload 只应出现在跳过分支(收藏 Tab/ // 搜索中)和 catch 兜底里 —— 最多 2 次。快路径由下一条断言(insertBefore/ // unshift)单独保证。原计划写死 <=1 与 spec 强制的 catch fallback 冲突, // 这里修正为 <=2 以匹配 spec(见 task-7 报告的 controller addendum)。 const reloadCount = (onAdded[0].match(/\breload\s*\(/g) || []).length; assert.ok(reloadCount <= 2, `onAdded 内 reload 次数 ${reloadCount},应是跳过分支 + catch 兜底 ≤ 2 次`); // 应该有 prepend 路径或 append 增量逻辑 assert.ok( /insertBefore|prepend|unshift/.test(onAdded[0]), 'onAdded 没走增量插入路径(看不到 insertBefore / unshift)' ); }); check('onAdded 跳过条件:搜索框非空或非 history Tab', () => { const onAdded = rendererSrc.match(/onAdded\s*\(\s*\(\s*\w+\s*\)\s*=>\s*\{[\s\S]*?\}\s*\)/); assert.ok(onAdded, '找不到 onAdded 回调'); assert.ok( /currentTab\s*!==\s*'history'/.test(onAdded[0]), 'onAdded 没判断非 history Tab 时跳过 prepend' ); assert.ok( /\$search\.value\.trim\s*\(\s*\)\s*!==\s*''/.test(onAdded[0]), 'onAdded 没判断搜索框非空时跳过 prepend' ); }); // ---------- 增量插入的两个回归坑 ---------- check('prependClip 熄灭空状态(#empty)', () => { // 空列表时 render() 亮起 #empty;第一条增量不走 render(), // 不主动隐藏就会出现"卡片 + 暂无内容"同屏。 const prepend = rendererSrc.match(/function\s+prependClip\s*\([\s\S]*?\n\}/); assert.ok(prepend, '找不到 prependClip()'); assert.ok( /\$empty\.classList\.add\(\s*'hidden'\s*\)/.test(prepend[0]), 'prependClip 没有隐藏 #empty —— 空列表收到第一条增量时空状态不熄灭' ); }); check('buildCard 的点击选中按运行时下标,不捕获构建时 i', () => { // prependClip 会把已有卡片整体后移,捕获构建时下标的闭包会选中错误的项。 assert.ok( /node\.onclick\s*=\s*\(\)\s*=>\s*select\(\s*\$cards\.indexOf\(node\)\s*\)/.test(rendererSrc), 'node.onclick 应在点击时从 $cards 现取下标(select($cards.indexOf(node))),而不是捕获构建时的 i' ); }); console.log(`\n${passed} checks passed`);