teact
单测界的 React — 受控数据驱动快照框架(test + react)。
把 React 的不可约结构同构到测试:
| React | teact |
|---|---|
UI = f(state) | out = run(in)(被测纯函数 + 渲染器) |
| JSX(声明终态) | fixture 的 out.txt |
| key(节点身份) | case 目录名 |
| reconciler | run 改了 → 跑遍 in → diff 新旧 out |
| 受控数据流 | diff 经 review,approve 才落盘——不像 jest -u 盲写 |
#用法
每个 __fixtures__/<suite>/<case>/ 目录是一个用例:
__fixtures__/
tokenize/
cjk-bigram/
in.txt ← 输入(任意 *.txt 字符串 / *.json 结构,文件名即 key)
out.txt ← 期望终态(手写,全等比对)
applyEdit/
replace/
cur.txt old.txt new.txt ← 多具名输入
out.txtimport { createSuite } from 'teact'
const snapshotSuite = createSuite(import.meta.dirname) // → <testDir>/__fixtures__
// 单输入:input 直接是那个值
snapshotSuite('tokenize', (input: string) => tokenize(input).join('\n'))
// 多具名:用 ctx.inputs
snapshotSuite('applyEdit', (_in, _opts, { inputs }) =>
applyEdit(inputs.cur as string, inputs.new as string, inputs.old as string))
// 带 opts.json
snapshotSuite('compactCss', (input: string, opts) =>
compactCss(input, (opts as { filename: string }).filename))输入约定:一个 case 目录里除 out.txt/opts.json 外,每个 *.txt 是字符串输入、*.json 是 JSON.parse 结构输入,key = 去扩展名的文件名。恰好一个输入文件时 input 直接给那个值;多个时 input === ctx.inputs。
#受控回写:propose → review → approve
改了被测函数或渲染器、或加新 case 缺 out.txt 时:
UPDATE_SNAPSHOTS=1 vitest run # propose:不碰 out.txt,产 out.txt.proposed + 打彩色 diff
teact status # review:列出所有待审变更
teact approve [pattern] # 落盘:out.txt.proposed → out.txt
teact clean # 丢弃所有 proposedpropose 模式仍跑 toBe 断言——CI 误设环境变量也照样红。框架永不在测试运行中直接写 out.txt,只产未跟踪的 .proposed 旁路文件,必须人 approve 才落盘。钉错无法静默发生。
把 out.txt.proposed 加进 .gitignore。
#为什么不用 vitest -u
.snap 机器生成、人不可读;-u 盲写覆盖、无 review → 钉错静默发生。teact 的 out.txt 是手写可读的终态声明,回写受控(变更经人 approve 才生效,= React 单向数据流)。