news 2026/9/14 17:39:52

Vitest 值格式化器深度解析:@vitest/pretty-format 如何驱动快照与断言输出

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vitest 值格式化器深度解析:@vitest/pretty-format 如何驱动快照与断言输出

Vitest 值格式化器深度解析:@vitest/pretty-format 如何驱动快照与断言输出

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

@vitest/pretty-format是 Vitest 对 Jest 官方pretty-format包的 ESM 化 fork,负责把任意 JavaScript 值渲染成人类可读的字符串。它并不直接面向测试编写者,而是支撑着 Vitest 的四条关键输出链路:快照序列化、断言 diff 渲染、匹配器与错误信息、以及浏览器模式的prettyDOM。读完全文,你将理解format的完整选项语义、内置插件体系、Vitest 在 fork 上新增的printShadowRootmaxOutputLength机制,以及快照、diff、stringify各场景下实际使用的预设差异,从而能准确解释和控制测试输出中的每一个字符。

包的定位与发布形态

从 package.json 可以确认几个关键事实:

  • 包名@vitest/pretty-format,描述为 "Fork of pretty-format with support for ESM",即 Jestpretty-format的 ESM 支持版本;
  • "type": "module"且产物只有dist/index.js,这是一个 ESM-only 包;
  • 运行时仅依赖tinyrainbow(终端着色),react-is仅作为开发依赖;
  • 源码文件头部保留了 Meta Platforms 的 MIT 版权声明,例如 src/index.ts,说明它是在 Jest 许可基础上维护的衍生实现。

包的核心导出集中在 src/index.ts:一个format函数、一个plugins对象(内置插件集合)、一个createDOMElementFilter工具,以及一组类型。文档入口在 USAGE.md。

基础用法:format 函数

format(value, options?)接收任意值和可选配置,返回展示字符串:

import { format } from '@vitest/pretty-format' const value = { user: 'Ada', items: [1, 2, 3], } console.log(format(value)) /* -- output -- Object { "items": Array [ 1, 2, 3, ], "user": "Ada", } */

注意默认输出会带ObjectArray前缀并给对象键加双引号——这是printBasicPrototype: truequoteKeys: true的默认行为,与console.log的 JSON 化输出不同。

完整配置选项

format接受以下选项(完整表见 USAGE.md,默认值定义在 DEFAULT_OPTIONS):

keytypedefault说明
callToJSONbooleantrue若存在toJSON则调用它进行序列化
compareKeysfunction\|nullundefined对象键排序的自定义比较函数;传null表示按原始顺序不排序
escapeRegexbooleanfalse转义正则中的特殊字符
escapeStringbooleantrue转义字符串中的特殊字符
highlightbooleanfalse使用终端颜色进行语法高亮
indentnumber2每级缩进的空格数
maxDepthnumberInfinity最大打印深度
maxOutputLengthnumber1_000_000每层深度的近似输出预算(Vitest 扩展项,见下文)
maxWidthnumberInfinity集合(数组/Map/Set/对象)中最多打印的条目数
minbooleanfalse最小化额外空白
pluginsarray[]用于序列化应用专属数据类型的插件列表
printBasicPrototypebooleantrue为普通对象和数组打印Object/Array前缀
printFunctionNamebooleantrue是否包含函数名
printShadowRootbooleantrue序列化 DOM 节点时是否包含 shadow-root 内容(Vitest 扩展项)
quoteKeysbooleantrue始终给对象属性键加引号
singleQuotebooleanfalse字符串使用单引号而非双引号
spacingInnerstring\n逗号分隔项或条目之后的空白
spacingOuterstring\n[]/{}定界符内侧紧邻的空白

有两条语义需要特别留意(源码依据见 getConfig 与 validateOptions):

  1. plugins: []的默认值意味着包不会自动启用任何内置插件。你必须显式传入plugins才会走插件序列化路径;不传时,DOM、React、Immutable 等值会落到通用的复杂对象打印逻辑上。Vitest 各功能(快照、diff 等)在各自内部选定了自己的插件栈与预设,见下文。
  2. min: true会联动改写其他默认值spacingInner变为' 'spacingOuter变为''printBasicPrototype变为falseindent被禁用(若显式传入非 0 的indent会直接抛错)。
  3. 传入未知选项键会抛出pretty-format: Unknown option "...",这是防止拼写错误静默失效的硬校验。

内置插件体系

format的扩展点在于插件:每个插件有test(value)判定是否认领该值,serialize负责输出。findPlugin按数组顺序遍历,第一个test通过的插件生效(findPlugin 实现)。插件抛出的错误会被包装成PrettyFormatPluginError,序列化结果若非字符串则抛TypeError(printPlugin)。

包导出七个内置插件(导出见 index.ts 底部):

  • ReactTestComponent/ReactElement:React 测试容器与元素;
  • DOMElement:HTML/SVG 元素、文本、注释、DocumentFragment;
  • DOMCollection:DOM 节点集合(NodeList 等);
  • Immutable:immutable-js 的 Map、Set、List、Stack、Seq、Record;
  • AsymmetricMatcherexpect.anything()这类非对称匹配器;
  • Error:Error 及子类,展开messagecauseAggregateError会额外打印errors(ErrorPlugin)。

直接使用示例:

import { format, plugins } from '@vitest/pretty-format' console.log( format(document.body, { plugins: [plugins.DOMElement, plugins.DOMCollection], }), )

从源码看各插件的处理细节:

  • DOMElement(src/plugins/DOMElement.ts)按nodeType识别元素/文本/注释/Fragment,对自定义元素(标签含-或有is属性)也走 DOM 序列化;无filterNode时保留全部子节点,且默认过滤纯空白文本节点以避免空行(filterChildren)。
  • Immutable(src/plugins/Immutable.ts)通过@@__IMMUTABLE_*__@@哨兵符号识别 immutable v3/v4 类型,输出形如Immutable.Map {...},惰性Seq会用标记。
  • 集合打印统一走 src/collections.ts:maxWidth超出时输出…(剩余数量)截断(如 printListItems);对象键排序由compareKeys控制,null时保持Object.keys原始顺序并追加可枚举 Symbol 键(getKeysOfEnumerableProperties);quoteKeys: false时符合/^[a-z_]\w*$/i的键不加引号(isUnquotableKey)。

打印管线:plugin → basic → complex

format的内部流程(printer)分三层:

  1. 插件层findPlugin命中则交给插件;
  2. 基本类型层(printBasicValue):处理null/undefined/布尔/数字/BigInt(输出123n)/字符串(按singleQuoteescapeString转义)/函数([Function name])/Symbol/Date(ISO 字符串或Date { NaN })/Error/RegExpescapeRegex开启时转义元字符);-0会专门打印为-0WeakMap/WeakSet打印为叶子;
  3. 复杂对象层(printComplexValue):循环引用检测(refs命中则返回[Circular])、callToJSON调用、Arguments/TypedArray/Map/Set分别处理,其余对象按构造器名输出TypeName { ... },并特判了 jsdom 环境下的全局window避免序列化失败。

Vitest 扩展:printShadowRoot 与 maxOutputLength

printShadowRoot

控制 DOM 序列化是否包含 shadow-root 内容,Vitest 在此 fork 中新增该选项:

format(element, { printShadowRoot: false, })

对应实现位于 DOMElement 插件:序列化元素时同时收集node.shadowRoot.children,默认(printShadowRoot: true)一并输出,关闭则只打印 light DOM。

maxOutputLength

这是一个启发式安全阀,用于防止大型递归结构病态膨胀——它不是最终字符串长度的硬上限:

format(value, { maxOutputLength: 100_000, })

从源码看其实现相当精巧(printer 尾部):配置对象内维护一个按深度分桶的计数器_outputLengthPerDepth[depth],每个深度层独立累计该层产生的输出长度;一旦某层累计超过maxOutputLength,直接把config.maxDepth置为0,让后续所有节点瞬间触顶、以[构造器名]叶子形式收尾。按深度分桶的原因写在注释里:嵌套结果若挤在同一个计数器上会因 N 层嵌套而低估约 N 倍,而同一深度的节点在输出串中互不重叠,分桶统计才是精确的。默认值1_000_000的注释也说明了取值动机——避免为日志和错误信息生成过长字符串(Node 的字符串上限约 512MB)。

Vitest 自身如何使用它

快照(Snapshot)

快照序列化使用快照专用预设(见 USAGE.md 与 docs/config/snapshotformat.md):

  • printBasicPrototype: false
  • escapeString: false
  • escapeRegex: true
  • printFunctionName: false
  • maxOutputLength: 2 ** 27

快照的maxOutputLength比包默认值1_000_000宽得多:包默认值面向日志和错误信息这类通用场景,而快照用户可能有意把很大的序列化值持久化到专用文件。若仍想收紧,可通过test.snapshotFormat.maxOutputLength配置。

默认快照插件栈为:ReactTestComponentReactElementDOMElementDOMCollectionImmutableAsymmetricMatcherMockSerializer(后者的MockFunction序列化由 Vitest 侧注册)。快照格式经由test.snapshotFormat配置,自定义序列化器则通过expect.addSnapshotSerializersnapshotSerializers注册。

断言 Diff

断言 diff 使用不同的预设与插件栈。默认 diff 插件为:

  • ReactTestComponent
  • ReactElement
  • DOMElement
  • DOMCollection
  • Immutable
  • AsymmetricMatcher
  • Error(比快照多出的关键项——diff 中错误对象会被展开为结构化的Error { message, cause, ... }

内部 stringify:匹配器与错误信息

匹配器和错误消息普遍经过 packages/utils/src/display.ts 中的stringify工具。其默认插件栈为 PLUGINS:

const PLUGINS = [ ReactTestComponent, ReactElement, DOMElement, DOMCollection, Immutable, AsymmetricMatcher, ]

stringify(实现)在@vitest/pretty-format之上叠加了三层包装行为:

  1. maxLength自适应降级:默认预算 10000 字符,若结果超长且maxDepth > 1,则以maxDepth / 2递归重试,逐级压缩深度直至输出可控(重试逻辑);
  2. filterNode:配置后会把默认DOMElement插件替换为经createDOMElementFilter包装的过滤变体,支持传 CSS 选择器字符串自动构造过滤器(过滤注释节点和匹配选择器的元素,createNodeFilterFromSelector);
  3. 格式化失败兜底:若格式化抛错,自动以callToJSON: false重试,规避toJSON实现中的副作用异常(catch 分支)。

同文件的inspect进一步封装了minsingleQuotequoteKeys: falsecompareKeys: null等紧凑输出预设,并带有truncate截断能力:优先靠stringifymaxDepth自适应压缩,仍不够时按类型做尽力截断(字符串直接切片,数组/对象/Map/Set 用二分搜索找到满足阈值的最小maxWidth,其他类型退回maxDepth: 0的最简输出)。

浏览器 prettyDOM

浏览器模式的prettyDOM构建在stringify路径之上,并启用面向浏览器的默认值,典型如highlight: true(终端/ANSI 颜色高亮);配置filterNode时同样替换为过滤版 DOM 插件。

小结

@vitest/pretty-format的设计核心是「一个纯函数式格式化内核 + 可插拔类型序列化器 + 每个消费方自选预设」:

  • 内核(format)负责基础类型、循环引用、深度/宽度截断、键排序与输出预算;
  • 七组内置插件覆盖 DOM、React、Immutable、Error、非对称匹配器等测试场景高频值类型,默认不自动启用
  • Vitest 的快照、diff、stringify、浏览器prettyDOM四条链路各自组装插件栈与选项预设,形成差异化的输出风格;
  • fork 新增的printShadowRoot和按深度分桶的maxOutputLength安全阀,是对 Web 平台与大对象场景的针对性补强。

如果你需要控制测试输出形态(比如快照中不出现Object前缀、错误信息里截断 DOM 子树),入口分别是 docs/config/snapshotformat.md 中的snapshotFormat配置和 docs/api/expect.md 中的expect.addSnapshotSerializer,它们的最终落点都是本文所述的format(value, options)调用链。

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 17:37:40

UCINET安装配置全指南:从环境准备到高级优化

1. UCINET安装前的环境准备UCINET作为社会网络分析领域的专业工具,其安装过程需要特别注意系统兼容性问题。根据实测经验,建议在Windows 10或11系统上进行安装,这两个版本对UCINET的兼容性最佳。安装前需要确认系统类型(32位或64位…

作者头像 李华
网站建设 2026/9/14 17:36:31

如何用 Pydantic AI Gateway 用一个 key 访问多个模型 provider

如何用 Pydantic AI Gateway 用一个 key 访问多个模型 provider 【免费下载链接】pydantic-ai How Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end. 项目地址: https://gitcode.com/GitHub_Trendin…

作者头像 李华
网站建设 2026/9/14 17:35:59

Python属性测试:动态类型安全的工程实践

1. 项目概述:当Python遇上属性测试十年前我刚接触Python时,曾被它的动态类型系统深深吸引——不需要声明变量类型,赋值即定义,这种灵活性让开发效率大幅提升。但很快我就尝到了苦头:一个本该是整数的变量突然变成了字符…

作者头像 李华