33-js-concepts 贡献实战指南:用 Vitest 验证文档代码示例的测试规范与翻译流程
【免费下载链接】33-js-concepts📜 33 JavaScript concepts every developer should know.项目地址: https://gitcode.com/GitHub_Trending/33/33-js-concepts
本文基于 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的includeglob,且目录名与概念名一致。以调用栈为例,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 的每个代码片段都不只是“看起来能跑”,而是被测试持续验证的行为契约——这也是贡献者理解并参与该项目最重要的机制。
【免费下载链接】33-js-concepts📜 33 JavaScript concepts every developer should know.项目地址: https://gitcode.com/GitHub_Trending/33/33-js-concepts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考