首页
/ 33-js-concepts 贡献实战指南:用 Vitest 验证文档代码示例的测试规范与翻译流程

33-js-concepts 贡献实战指南:用 Vitest 验证文档代码示例的测试规范与翻译流程

2026-09-04 09:45:09作者:牧宁李

本文基于 33-js-concepts 仓库的 CONTRIBUTING.md 展开,系统讲解该项目如何以 Vitest 测试框架保障“文档中的每段代码示例都真实可运行”:从三条 npm 测试命令的实际定义、tests/ 目录的分类组织方式,到编写测试的六条硬性规范(显式导入、断言转换、错误用例、浏览器 API 处理、严格模式注意),再到新增语言翻译的完整流程和 MIT 许可约定。读完后你可以独立完成:跑通全量测试、为新的概念文档编写配套测试、以及提交一个翻译 PR。

项目定位与“文档即代码”的贡献模式

33-js-concepts 是一个整理 JavaScript 核心概念的学习型仓库:docs/ 目录下按 fundamentals、functions-execution、object-oriented、functional-programming、beyond 等分类存放 MDX 文档(如 docs/concepts/call-stack.mdxdocs/concepts/promises.mdx),而 CONTRIBUTING.md 明确了本项目的核心贡献约定——使用 Vitest 作为测试运行器,用来验证文档中的代码示例工作正常

This project uses Vitest as the test runner to verify that code examples in the documentation work correctly.

这意味着仓库的贡献不是“写功能代码”,而是“写文档 + 写能验证文档示例的测试”。仓库本身没有业务源码,根目录的 index.js 只是一个包含项目说明的注释占位文件,真正的质量保障体系全部落在 tests/ 目录和测试配置上。

运行测试:三条命令与其在 package.json 中的真实定义

CONTRIBUTING.md 给出三条测试命令:

# Run all tests once
npm test

# Run tests in watch mode (re-runs on file changes)
npm run test:watch

# Run tests with coverage report
npm run test:coverage

对照 package.jsonscripts 字段,可以确认每条命令背后的真实行为:

npm 命令 实际执行的命令 行为说明
npm test vitest run 一次性运行全部测试并退出,适合 PR 前自检
npm run test:watch vitest 进入 watch 模式,文件变化时自动重跑,适合开发中边写边验证
npm run test:coverage vitest run --coverage 运行测试并输出覆盖率报告,依赖 @vitest/coverage-v8 提供

与之一致的 devDependencies 声明为:

  • vitest: ^4.0.16
  • jsdom: ^27.4.0(用于少量 DOM 测试)
  • @vitest/coverage-v8: ^4.0.16test:coverage 命令的支撑依赖)

另外两个值得注意的脚本是 docscd docs && npx mintlify dev)与 docs:buildcd docs && npx mintlify build),从 package.json 的 scripts 结构可以看出,仓库同时承担文档站点(基于 Mintlify)的构建,贡献者改完 MDX 后可以本地预览渲染效果。

测试全局配置:为什么必须显式 import

vitest.config.js 的全部配置只有三项,但它们直接决定了测试的写法:

import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    include: ['tests/**/*.test.js'],
    globals: false,
    environment: 'node'
  }
})

三个配置项的含义与影响:

  • include: ['tests/**/*.test.js']:只有 tests/ 目录下以 .test.js 结尾的文件会被执行。这解释了 CONTRIBUTING.md 中“在 tests/{concept-name}/ 下创建 {concept-name}.test.js”的命名约定——文件名不匹配这个 glob 就会被静默忽略。

  • globals: false:Vitest 不会把 describe/it/expect 挂到全局。这正是 CONTRIBUTING.md 第 2 条规范要求“使用显式导入”的配置级原因:

    import { describe, it, expect } from 'vitest'
    

    任何测试文件缺少这行导入都会直接报 describe is not defined,而仓库中的 tests/fundamentals/call-stack/call-stack.test.js 第一行就是标准的显式导入写法。

  • environment: 'node':默认测试环境是 Node.js 而非浏览器,这也是 CONTRIBUTING.md 第 5 条“跳过浏览器专属示例”的根据(下文会讲到 DOM 测试的例外处理方式)。

tests/ 目录结构:按概念组织,并按知识域分层

CONTRIBUTING.md 中给出的结构示意是扁平化的:

tests/
├── call-stack/
│   └── call-stack.test.js
├── primitive-types/
│   └── primitive-types.test.js
└── ...

实际仓库在此基础上多做了一层按知识域分组,当前 tests/ 的真实组织是“分类目录 / 概念目录 / 测试文件”三层,例如:

tests/
├── fundamentals/
│   ├── call-stack/call-stack.test.js
│   ├── primitive-types/primitive-types.test.js
│   └── ...
├── functions-execution/
│   ├── event-loop/event-loop.test.js
│   ├── promises/promises.test.js
│   └── ...
├── object-oriented/
│   ├── this-call-apply-bind/this-call-apply-bind.test.js
│   └── ...
├── functional-programming/
│   ├── recursion/recursion.test.js
│   └── ...
├── web-platform/
│   ├── dom/dom.test.js
│   └── http-fetch/http-fetch.test.js
└── beyond/
    ├── memory-performance/memoization/memoization.test.js
    ├── observer-apis/performance-observer/performance-observer.test.js
    └── ...

可以推断,外层分类(fundamentals/beyond/ 等)与 docs/ 下的 concepts/beyond/concepts/ 文档分类保持对应,贡献者在为新文档补测试时,应把测试文件放进与文档一致的概念目录中,而不是平铺在 tests/ 根部。

为代码示例编写测试:六条规范逐条解析

CONTRIBUTING.md 的“Writing Tests for Code Examples”给出了六条规范。下面逐条结合仓库实际代码展开,使每条规范可操作、可验证。

1. 文件命名:tests/{concept-name}/{concept-name}.test.js

文件名必须匹配 vitest.config.jsinclude glob,且目录名与概念名一致。以调用栈为例,tests/fundamentals/call-stack/call-stack.test.js 中的测试按主题组织成 describe 块("Basic Function Calls"、"Nested Function Calls" 等),每条 it 的标题直接描述被验证的行为,如 'should execute nested function calls and return correct greeting'

2. 显式导入:由 globals: false 强制要求

见上文“测试全局配置”一节。CONTRIBUTING.md 给出的标准写法:

import { describe, it, expect } from 'vitest'

需要 mock 或生命周期钩子时按需补充导入,例如 tests/beyond/browser-storage/cookies/cookies.dom.test.js 额外导入了 beforeEachafterEachvi,并在 afterEach 中用 vi.restoreAllMocks() 还原 mock,这是多测试共享 DOM 状态时的标准清理姿势。

3. 把 console.log 示例转成断言

这是本仓库测试哲学的核心:文档里 // => "string" 这种注释式预期,在测试中必须变成可执行的 expect。CONTRIBUTING.md 给出的对照示例:

// Documentation example:
// console.log(typeof "hello") // "string"

// Test:
it('should return string type', () => {
  expect(typeof "hello").toBe("string")
})

实际仓库中的写法与之完全一致,比如 call-stack 测试中:

it('should execute nested function calls and return correct greeting', () => {
  function createGreeting(name) {
    return "Hello, " + name + "!"
  }

  function greet(name) {
    const greeting = createGreeting(name)
    return greeting
  }

  expect(greet("Alice")).toBe("Hello, Alice!")
})

即:先完整搬入文档中的示例函数,再对文档注释里写明的输出用 toBe / toEqual 断言。对对象、数组等复合结果使用 toEqual(深度比较),对原始值使用 toBe,仓库中两种断言均有实例。

4. 错误用例:用 toThrow() 验证“应当抛出”的行为

CONTRIBUTING.md 第 4 条:对预期抛错的示例使用 expect(() => { ... }).toThrow()。典型场景是文档中讲解“访问 TDZ 中的变量会抛 ReferenceError”“调用 Object.freeze 后的属性赋值在严格模式下抛 TypeError”这类内容——测试代码把抛错本身当作断言对象,从而保证文档描述的失败行为与运行时行为一致。

5. 浏览器专属示例:默认跳过,需要时用 jsdom docblock

因为 environment: 'node'window / document / DOM 相关示例默认不在 Node 测试中覆盖(CONTRIBUTING.md 第 5 条)。但仓库并没有完全放弃 DOM 测试:对确实要验证浏览器 API 的文档(如 cookies、DOM 操作、Observer 系列),采用文件后缀 + docblock 注释的方式单独标记,例如 tests/beyond/browser-storage/cookies/cookies.dom.test.js

/**
 * @vitest-environment jsdom
 */
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'

文件头部的 @vitest-environment jsdom 注释会覆盖全局的 node 环境,使该文件单独运行在 jsdom 中(对应 devDependencies 里的 jsdom),这也解释了仓库中 .dom.test.js 与普通 .test.js 并存的双文件模式:同一概念(如 cookies)会同时有 Node 环境可测的 cookies.test.js 与 jsdom 环境的 cookies.dom.test.js。该文件还在 beforeEach / afterEach 中清理 document.cookie 与 mock,保证 DOM 测试之间互不污染。

6. 严格模式行为:Vitest 下“静默失败”会变成 TypeError

CONTRIBUTING.md 第 6 条提醒:Vitest 以严格模式运行,因此在非严格模式下“静默失败”的操作(如给未声明变量赋值、修改只读属性)在测试中会直接抛 TypeError。写测试时要据此调整预期:文档若演示非严格模式的宽松行为,测试里要么用 toThrow(TypeError) 断言其失败,要么改写示例本身使其在严格模式下成立。这与 beyond/ 文档中 strict-mode 主题的内容相呼应。

覆盖率:用 test:coverage 检查示例覆盖情况

npm run test:coverage(即 vitest run --coverage)会基于 @vitest/coverage-v8 输出覆盖率报告。对“文档示例 + 配套测试”的仓库而言,覆盖率的意义在于核对文档里出现过、但没有对应测试的示例——它们是贡献者可以补齐的空白点。

创建新翻译:完整流程与格式约定

CONTRIBUTING.md 的“Creating a New Translation”给出了八步流程,全部步骤与格式约定如下(翻译工作针对的是整个文档仓库,而非当前仓库的代码):

  1. Fork 主仓库(leonardomso/33-js-concepts);
  2. 将主仓库加入 watch 列表,保持与上游同步;
  3. 在自己的 fork 中完成翻译;
  4. 在主仓库的 README.md 中编辑链接,指向你的翻译仓库;
  5. Community 区块按固定格式新增一行:
    • 格式:Your language in native form (English name) — Your Name
    • 文档给出的示例:[日本語 (Japanese)](https://github.com/oimo23/33-js-concepts) — oimo23
  6. 创建 Pull Request,命名格式为 "Add *your language here* translation."
  7. 等待合并。

仓库根目录另有 TRANSLATIONS.md 汇总现有翻译,docs/translations.mdx 则提供文档站内的翻译入口;README.md 也声明该指南已被翻译为 40+ 语言(README 的 Community 区块即为翻译链接的挂载位置)。

许可约定:贡献即接受 MIT

CONTRIBUTING.md 末尾明确:

By contributing, you agree that your contributions will be licensed under the MIT license.

即任何贡献(文档、测试、翻译)一经提交,即视为以 LICENSE 中的 MIT 协议授权。这一点在提交 PR 前需要知悉——MIT 是宽松协议,允许自由使用、修改与再分发,但对贡献者的实际约束主要体现在:不附带担保、贡献内容归入项目统一的 MIT 授权之下。

贡献前自检清单

结合 CONTRIBUTING.md 与仓库实际配置,提交前可按以下清单自查:

  1. 测试文件位置正确tests/{分类}/{concept-name}/{concept-name}.test.js,文件名匹配 tests/**/*.test.js
  2. 第一行显式导入import { describe, it, expect } from 'vitest'globals: false 下不可省略);
  3. 文档示例全部转为断言:原始值用 toBe,复合结构用 toEqual,抛错行为用 toThrow()
  4. 浏览器 API 处理得当:Node 测试跳过 DOM 示例;确需 DOM 验证时使用 @vitest-environment jsdom docblock 并妥善清理全局状态;
  5. 严格模式预期:确认示例在严格模式下的行为与文档描述一致;
  6. 本地跑通npm test 一次性全量通过;开发过程可用 npm run test:watch,合并前用 npm run test:coverage 查看覆盖情况;
  7. 翻译类 PR:README 的 Community 区块格式与 PR 命名符合约定。

这套“文档示例 + Vitest 断言”的组合,使 33-js-concepts 的每个代码片段都不只是“看起来能跑”,而是被测试持续验证的行为契约——这也是贡献者理解并参与该项目最重要的机制。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.82 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
504
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384