首页
/ freeCodeCamp 课程构建测试夹具解剖:combined-js-comments.md 挑战文件结构与国际化管线解析

freeCodeCamp 课程构建测试夹具解剖:combined-js-comments.md 挑战文件结构与国际化管线解析

2026-09-05 23:53:02作者:蔡怀权

本篇指南以 combined-js-comments.md 这份课程测试夹具为主体,逐字段拆解 freeCodeCamp 挑战文件的完整格式:frontmatter 元数据、五大内容分区(Description / Instructions / Tests / Challenge Seed / Solution)、YAML 测试断言与正则校验写法,并结合 get-challenges.ts 等课程构建源码,说明这类夹具在课程国际化(i18n)构建流程中扮演的角色。读完后你可以准确理解该挑战文件格式的每个区块约定,并能据此阅读或编写符合规范的课程挑战文件。

一、combined 夹具是什么:课程构建中的"合并版"挑战样例

curriculum/__fixtures__/ 目录是课程构建管线的测试数据目录。真实挑战源文件存放在 curriculum/challenges/english/blocks/ 下(每块目录含多个 .md 挑战文件),而 __fixtures__ 目录内则放置供测试使用的缩小版挑战文件,按语言与场景组织:

从源码结构看,combined 系列文件与 -js-comments-html-comments-jsx-comments 三个后缀成组出现,分别对应 JS、HTML、JSX 三类注释代码块的测试场景,用于覆盖课程翻译与构建流程中不同语言环境下的代码块注释处理。

这份夹具承载的是课程中第一个经典挑战——"Say Hello to HTML Elements"(向 HTML 元素问好):学习者在代码编辑器中把 <h1> 元素的文本改为 "Hello World" 并通过测试。

二、frontmatter:挑战元数据区

文件开头的 YAML frontmatter(combined-js-comments.md)声明挑战的核心身份与关联信息:

---
id: bd7123c8c441eddfaeb5bdef
originalTitle: Say Hello to HTML Elements
challengeType: 0
videoUrl: 'https://scrimba.com/p/pVMPUv/cE8Gpt2'
forumTopicId: 18276
title: 向HTML Elements说你好
---

各字段含义如下:

字段 示例值 说明
id bd7123c8c441eddfaeb5bdef 挑战的全局唯一标识(24 位十六进制串),是跨语言版本关联同一挑战的锚点
originalTitle Say Hello to HTML Elements 英文源标题。combined 夹具同时保留该字段与本地化 title,体现"英文源 + 目标语言译文"的合并结构
title 向HTML Elements说你好 当前语言(中文)版本的展示标题
challengeType 0 挑战类型编号,0 对应可交互编辑器(可运行测试)类挑战
videoUrl 外部视频页面链接 指向该挑战配套讲解视频的地址(原文档中为一个外部视频链接)
forumTopicId 18276 社区论坛中该挑战对应讨论串的 ID,用于从课程页跳转求助

值得注意的是 originalTitletitle 并存这一细节:它正是 combined 夹具的核心特征——同一文件内同时携带英文源标题与中文标题,供构建与翻译校验逻辑对照使用。

三、五大分区:挑战正文的骨架

frontmatter 之后是五个以二级标题划分、并各自包裹在 <section> 标签中的区块。这个"标题 + section 包裹"的结构是挑战文件的统一约定,sectioniddescriptioninstructionstestschallengeSeedsolution)是解析管线定位内容的依据。

3.1 Description:挑战描述

## Description
<section id="description">
欢迎来到freeCodeCamp的HTML编码挑战。这些将逐步引导您完成Web开发。首先,您将首先使用HTML构建一个简单的网页。您可以在<code>code editor</code>编辑<code>code</code> ,该<code>code editor</code>嵌入到此网页中。您是否在代码编辑器中看到<code>&lt;h1&gt;Hello&lt;/h1&gt;</code> ?这是一个HTML <code>element</code> 。大多数HTML元素都有一个<code>opening tag</code>和一个<code>closing tag</code> 。打开标记如下所示: <code>&lt;h1&gt;</code>结束标记如下所示: <code>&lt;/h1&gt;</code>开始标记和结束标记之间的唯一区别是结束标记的左括号后面的正斜杠。每个挑战都有可以随时单击“运行测试”按钮运行的测试。当您通过所有测试时,系统会提示您提交解决方案并转到下一个编码挑战。
</section>

该区块(combined-js-comments.md)是纯 HTML 富文本,内嵌 <code> 标签渲染行内代码。内容上它承担了三重职责:

  1. 教学概念:解释 HTML 元素(element)、开始标记(opening tag)与结束标记(closing tag)的区别——结束标记在左尖括号后多一个正斜杠,如 <h1> 对比 </h1>
  2. 产品引导:说明课程内嵌代码编辑器的用法,以及"随时可点'运行测试'"的交互流程;
  3. 完成条件预告:通过全部测试后系统提示提交并进入下一题。

3.2 Instructions:任务指令

## Instructions
<section id="instructions">
要通过此挑战的测试,请将<code>h1</code>元素的文本更改为“Hello World”。
</section>

Instructions 是给学习者的具体操作目标:把 h1 元素的文本改为 "Hello World"。它与 Description 的分工是:前者讲"为什么、背景是什么",后者讲"这一题到底要做什么"。

3.3 Tests:YAML 测试断言

## Tests
<section id='tests'>
```yml
tests:
  - text: 你的<code>h1</code>元素应该有“Hello World”文本。
    testString: assert.isTrue((/hello(\s)+world/gi).test($('h1').text()));
```

Tests 区块(combined-js-comments.md)用一个 yml 代码围栏描述测试用例数组,每条用例两个字段:

  • text:测试失败时展示给学习者的提示文案(此处为中文译文,可内嵌 <code> 标签);
  • testString:实际执行的断言语句。

这条 testString 值得逐段剖析:

assert.isTrue((/hello(\s)+world/gi).test($('h1').text()));
  • $('h1') 以 jQuery 选择器取页面中的 h1 元素,.text() 取其纯文本内容;
  • /hello(\s)+world/gi 是校验正则:(\s)+ 要求 "hello" 与 "world" 之间至少一个空白字符,g 全局匹配、i 忽略大小写——因此 "Hello World"、"HELLO world" 都能通过;
  • assert.isTrue(...) 是 Mocha 风格的断言,正则命中则测试通过。

这种"断言与提示文案分离"的写法,让翻译版本可以只替换 text 字段而不触碰 testString 逻辑,这正是 combined 夹具同时保留中英文文案的结构基础。

3.4 Challenge Seed:代码种子

## Challenge Seed
<section id='challengeSeed'>
<div id='js-seed'>
```js
/* (Chinese) Add your code below this line (Chinese) */
// Add your code above this line */
```

Challenge Seed(combined-js-comments.md)是学习者在编辑器中看到的初始代码。两个要点:

  1. 语言环境由 divid 声明:本文件用 <div id='js-seed'>,表示种子代码是 JS 环境。对照 chinese/challenge-js-comments.md(用 html-seed 且种子为 h1 的 HTML 注释版本)与 combined.mdhtml-seed 且种子为 <h1>Hello</h1>),可见 id 取值决定代码编辑器的语言与初始内容——同一挑战在不同夹具中会以不同的种子环境出现;
  2. 注释本身也被本地化:本夹具的种子注释是 /* (Chinese) Add your code below this line (Chinese) */,而英文源 english/challenge-js-comments.md 中是 /* Add your code below this line */。代码块内的注释属于翻译管线的处理对象,这也解释了为什么会有 dictionaries/ 词典与"不翻译注释"白名单(comments-to-not-translate.json)的配套夹具。

3.5 Solution:参考解答

## Solution
<section id='solution'>
```html
<h1>Hello World</h1>
```

Solution 区块(combined-js-comments.md)给出标准答案:一个文本为 "Hello World" 的 h1 元素。它必须恰好满足 Tests 区块中正则 /hello(\s)+world/gi 的要求——这也是各区块之间的内在一致性约束:Instructions 的目标、Seed 的初始状态、Tests 的断言与 Solution 的答案四者构成闭环。

四、夹具如何被构建管线消费

4.1 入口:getChallengesForLang 与语言校验

课程构建的入口在 get-challenges.tsgetChallengesForLang(lang, filters) 先依据 @freecodecamp/shared/config/i18n 中的 availableLangs.curriculum 白名单校验语言参数——传入非法语言会抛出 ${lang} is not an accepted language 错误,然后调用 buildCurriculum(lang, filters) 生成指定语言的课程数据:

export async function getChallengesForLang(
  lang: string,
  filters: Filter = curriculumFilter
) {
  const invalidLang = !curriculumLangs.includes(lang);
  if (invalidLang)
    throw Error(`${lang} is not an accepted language.
Accepted languages are ${curriculumLangs.join(', ')}`);

  return buildCurriculum(lang, filters);
}

4.2 英文源存在性检查:hasEnglishSource

同一文件中的 hasEnglishSourceget-challenges.ts)负责校验翻译挑战是否具备对应的英文源:

export async function hasEnglishSource(
  basePath: string,
  translationPath: string
) {
  const englishRoot = resolve(__dirname, basePath, 'english');
  return await access(join(englishRoot, translationPath), constants.F_OK)
    .then(() => true)
    .catch(() => false);
}

它把 basePath 解析到 {basePath}/english/ 下,检查同名文件是否存在。这套"英文源 + 翻译版"双轨结构正是 __fixtures__/english/__fixtures__/chinese/ 目录分工的原因。

4.3 测试用例如何引用夹具

get-challenges.test.js 直接以该夹具目录为 basePath

const EXISTING_CHALLENGE_PATH = 'challenge.md';
const MISSING_CHALLENGE_PATH = 'no/challenge.md';
const basePath = '../__fixtures__';

测试验证了三个行为:非法语言抛出 notlang is not an accepted language;英文挑战存在时 hasEnglishSource 返回 true;路径不存在时返回 false。可见 __fixtures__ 目录既是"格式范例",也是被测试直接断言的活数据。

4.4 翻译词典:comments.json 的映射规则

dictionaries/ 下的词典夹具展示了代码注释翻译的映射形式——english/comments.json 存放待翻译原文,chinese/comments.json 存放译文,两者以相同键对应:

// dictionaries/english/comments.json
{ "hyek8f": "To be translated one", "rscjup": "To be translated two" }

// dictionaries/chinese/comments.json
{ "hyek8f": "Chinese translation one", "rscjup": "Chinese translation two" }

incomplete-dicts/ 子目录则存放"部分翻译完成"的不完整词典夹具,用于覆盖词典缺失条目时的构建分支。

五、小结:从单个夹具看课程文件格式的完整约定

回看 combined-js-comments.md 全文,可以提炼出 freeCodeCamp 挑战文件的五项硬约定:

  1. frontmatter 定身份id 跨语言唯一,originalTitle 与本地 title 并存,challengeType 决定交互形态,videoUrl/forumTopicId 提供视频与论坛入口;
  2. 五分区定骨架:Description、Instructions、Tests、Challenge Seed、Solution 各自包裹在带 id<section> 中,id 是解析管线的定位锚点;
  3. 测试可执行且可本地化testString 是真正的 JS 断言(正则 + DOM 断言),text 是面向用户的多语言提示,二者解耦;
  4. 种子声明代码环境<div id='js-seed'> / html-seedid 决定编辑器语言与初始代码,代码内注释同样进入翻译管线(配合 dictionaries/ 与 not-translate 白名单);
  5. 四区块闭环:Instructions 的目标必须能被 Solution 满足、并被 Tests 的断言精确校验。

这套格式在课程规模下(curriculum/challenges/english/blocks/ 下逾一万六千个挑战 .md 文件)保持统一,使构建管线能够以相同方式解析任意语言、任意块目录下的挑战文件。

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