33-js-concepts 贡献实战指南:用 Vitest 验证文档代码示例的测试规范与翻译流程
本文基于 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.mdx、docs/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.json 的 scripts 字段,可以确认每条命令背后的真实行为:
| 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.16jsdom:^27.4.0(用于少量 DOM 测试)@vitest/coverage-v8:^4.0.16(test:coverage命令的支撑依赖)
另外两个值得注意的脚本是 docs(cd docs && npx mintlify dev)与 docs:build(cd 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.js 的 include 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 额外导入了 beforeEach、afterEach 和 vi,并在 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”给出了八步流程,全部步骤与格式约定如下(翻译工作针对的是整个文档仓库,而非当前仓库的代码):
- Fork 主仓库(leonardomso/33-js-concepts);
- 将主仓库加入 watch 列表,保持与上游同步;
- 在自己的 fork 中完成翻译;
- 在主仓库的 README.md 中编辑链接,指向你的翻译仓库;
- 在 Community 区块按固定格式新增一行:
- 格式:
Your language in native form (English name) — Your Name - 文档给出的示例:
[日本語 (Japanese)](https://github.com/oimo23/33-js-concepts) — oimo23
- 格式:
- 创建 Pull Request,命名格式为
"Add *your language here* translation."; - 等待合并。
仓库根目录另有 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 与仓库实际配置,提交前可按以下清单自查:
- 测试文件位置正确:
tests/{分类}/{concept-name}/{concept-name}.test.js,文件名匹配tests/**/*.test.js; - 第一行显式导入:
import { describe, it, expect } from 'vitest'(globals: false下不可省略); - 文档示例全部转为断言:原始值用
toBe,复合结构用toEqual,抛错行为用toThrow(); - 浏览器 API 处理得当:Node 测试跳过 DOM 示例;确需 DOM 验证时使用
@vitest-environment jsdomdocblock 并妥善清理全局状态; - 严格模式预期:确认示例在严格模式下的行为与文档描述一致;
- 本地跑通:
npm test一次性全量通过;开发过程可用npm run test:watch,合并前用npm run test:coverage查看覆盖情况; - 翻译类 PR:README 的 Community 区块格式与 PR 命名符合约定。
这套“文档示例 + Vitest 断言”的组合,使 33-js-concepts 的每个代码片段都不只是“看起来能跑”,而是被测试持续验证的行为契约——这也是贡献者理解并参与该项目最重要的机制。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00