Carbon 设计系统单元测试基座:深入解读 jest-config-carbon 预设与源码实现
2026/9/16 22:01:48 网站建设 项目流程

Carbon 设计系统单元测试基座:深入解读 jest-config-carbon 预设与源码实现

【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon

导读

jest-config-carbon是 IBM Carbon Design System 仓库中为旗下所有 JavaScript/TypeScript 包统一提供的 Jest 配置与预设(preset),它把 Babel 编译、SCSS/CSS 与静态资源转换、jsdom 环境补丁、无障碍(a11y)断言匹配器等一系列"基础设施"封装为一个可直接复用的 npm 包。本文以 config/jest-config-carbon/README.md 为骨架,结合仓库内真实源码,讲解如何安装与接入该预设、预设包含哪些关键配置项、各 transform 与 setup 文件在测试链路中的职责,以及如何在自己的 React/Sass 项目中把这套成熟方案移植过来。读完本文,你将能够独立完成 jest-config-carbon 的接入、理解其底层原理,并学会借助内置匹配器编写无障碍测试。

一、jest-config-carbon 是什么

jest-config-carbon是 Carbon Design System 中负责"统一 Jest 测试行为"的配置包。它本质上是一个 Jest 预设(preset):

  • 通过preset: 'jest-config-carbon'一行即可继承整套测试配置;
  • 覆盖了 Carbon 各包(React、Web Components、Utilities 等)共用的测试需求:JS/TS/JSX 的 Babel 编译、Sass 的实时编译注入、图片等静态资源的桩替换、jsdom 环境下的浏览器 API 补齐、以及无障碍违规断言;
  • 在仓库内部它被标记为"private": true(见 config/jest-config-carbon/package.json),主要服务于 monorepo 内部各包,同时其设计思路也适合任何 React + Sass + TypeScript 项目直接借鉴。

从仓库根目录的 jest.config.js 可以看到它的实际消费方式:

export default { preset: 'jest-config-carbon', testEnvironment: 'jsdom', // ...项目级覆盖配置 };

Carbon 的根配置在预设之上只做少量增量定制(如覆盖率收集范围、identity-obj-proxy映射、jest-junit报告器),说明预设已经承担了绝大多数"脏活累活"。

二、安装与快速接入

2.1 安装命令

原文档给出的安装方式非常简单。使用 npm:

npm install -S jest-config-carbon

或使用 Yarn:

yarn add jest-config-carbon

2.2 在 Jest 配置中启用预设

在项目的jest.config.js(或package.jsonjest字段)中加入:

export default { preset: 'jest-config-carbon', };

预设的核心导出位于 config/jest-config-carbon/index.js,而 config/jest-config-carbon/jest-preset.js 只是它的再导出入口,两者指向同一份配置对象。

2.3 接入后的能力清单

启用预设后,你的测试环境立即获得:

能力说明出处
多模块格式编译.tsx/.ts/.js/.json/.node全部纳入moduleFileExtensionsjest-preset.js
Babel 编译基于babel-jest的 transformer,含 preset-env/react/typescripttransform/jsTransform.js
Sass 实时编译.scss/.sass导入会先编译为 CSS 再注入<style>标签transform/cssTransform.js
静态资源桩替换图片、字体等文件导入被替换为文件名transform/fileTransform.js
jsdom 环境补丁requestAnimationFrameResizeObserverAnimationEventHTMLDialogElementsetup/setup.js
无障碍断言toHaveNoAxeViolationstoHaveNoACViolations两个自定义匹配器setup/setupAfterEnv.js
测试文件匹配规则覆盖__tests__目录与*.spec/*.test命名规范jest-preset.js

三、预设核心配置逐项解析

config/jest-config-carbon/jest-preset.js 是整份配置的"心脏",下面按类别拆解它的设计意图。

3.1 workerIdleMemoryLimit:防止 CI 内存溢出

workerIdleMemoryLimit: '1GB',

源码注释明确指出这是为了避免"worker 堆内存跨测试套件持续增长、CI 运行器中途被 OOM 杀掉"。Jest 默认会为每个测试文件创建独立 worker,长期运行的 suite 中堆内存可能持续膨胀,设置上限后 Jest 会回收空闲 worker。这是从实际 CI 故障中沉淀出来的经验值,多包大型仓库非常值得参考。

3.2 文件匹配与忽略规则

testMatch: [ '<rootDir>/**/__tests__/**/*.js?(x)', '<rootDir>/**/*.(spec|test).js?(x)', '<rootDir>/**/*-(spec|test).js?(x)', ],

支持的测试文件命名有三种形态:

  1. 任意__tests__目录下的.js/.jsx文件;
  2. *.spec.js(x)*.test.js(x)
  3. 短横线形态的*-spec.js(x)*-test.js(x)(Carbon 内部大量使用这种命名,如Button-test.avt.e2e.js)。

同时,testPathIgnorePatterns排除了/dist//es//lib//build//umd//vendor/等构建产物目录,并整体排除e2eexamples——端到端测试由仓库根目录的 jest.e2e.config.js 单独负责(配合 Playwright),单元测试职责边界清晰。

3.3 transform 映射:三类资源三种处理策略

transform: { '^.+\\.(mjs|cjs|js|jsx|ts|tsx)$': resolve(__dirname, './transform/jsTransform.js'), '^.+\\.s?css$': resolve(__dirname, './transform/cssTransform.js'), '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': resolve(__dirname, './transform/fileTransform.js'), },
  • JS 系文件走 Babel;
  • SCSS/Sass 走 Sass 编译;
  • 其余非 JS/CSS/JSON 的资源(图片、字体等)走文件桩替换。

注意第三个正则使用了负向先行断言,把所有"既不是 JS、也不是 CSS/JSON"的文件全部兜底到 fileTransform,确保任何静态资源导入都不会导致测试崩溃。

3.4 transformIgnorePatterns:node_modules 的白名单

transformIgnorePatterns: [ '/build/', '/es/', '/lib/', '/umd/', '[/\\\\]node_modules/\\\\.+\\.(js|jsx)$', ],

Jest 默认不转译 node_modules 里的代码,但lodash-es(ESM 语法)、nanoidchalk@babel/*这些包必须经过 Babel 才能被 Jest 理解。这里用负向前瞻把它们从忽略名单中"捞回来"。根配置 jest.config.js 中又追加了temporal-polyfill|temporal-utils两个白名单,属于同一模式的增量扩展。

3.5 watch 模式增强

watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],

通过jest-watch-typeahead插件,在--watch模式下可以输入文件名/测试名进行模糊过滤,显著提升大型仓库中的开发体验。

四、三大 Transformer 的源码级剖析

4.1 jsTransform:Carbon 专属的 Babel 管线

config/jest-config-carbon/transform/jsTransform.js 基于babel-jestcreateTransformer构造,其 Babel 配置包含三层关键设计:

preset-env 与 Carbon 浏览器基线对齐:

[ '@babel/preset-env', { targets: { browsers: ['extends browserslist-config-carbon'], }, }, ],

它直接引用仓库中的 config/browserslist-config-carbon 作为转译目标,保证测试环境与 Carbon 真实支持的浏览器范围保持一致,避免"测试通过、线上报错"的兼容性偏差。

TypeScript 转译的取舍:

[ '@babel/preset-typescript', // Babel 8 defaults this to true,而 Carbon 不使用 verbatimModuleSyntax { onlyRemoveTypeImports: false }, ],

注释解释了关键点:Babel 8 中onlyRemoveTypeImports默认为 true,会保留import { Type }这类纯类型导入为运行时 require,而 Carbon 并不使用verbatimModuleSyntax,因此显式关掉该选项,避免运行时解析到不存在的模块。

JSX 按文件类型分流:

overrides: [ { test: /\.(js|jsx|tsx)$/, presets: [ ['@babel/preset-react', { runtime: 'classic' }], ], }, ],

这个 override 非常巧妙:只在.js/.jsx/.tsx上启用 preset-react,.ts文件不解析 JSX,这样 TypeScript 泛型参数<T>不会被误判为 JSX 语法。同时显式指定runtime: 'classic'React.createElement),保持与 Carbon 现有输出一致,而不是采用 Babel 8 默认的 automatic runtime。

最后还挂载了export-default-fromexport-namespace-fromtransform-runtime三个插件,支撑 Carbon 源码中export { default } from './x'这类语法并复用@babel/runtime减少打包体积。

4.2 cssTransform:Sass 编译 + 样式注入

config/jest-config-carbon/transform/cssTransform.js 是一个高价值的设计,它让样式在测试中"真实生效":

编译阶段:使用sass.compile(filepath, { style: 'compressed', loadPaths })实时编译。loadPaths通过向上遍历文件目录的ancestors()函数收集所有可能存在的node_modules,确保 Sass 的@use '@carbon/styles'这类包内导入能正确解析。

注入阶段:编译产物不是简单地替换为空对象,而是生成一段测试代码,在beforeAll中创建<style>标签写入 CSS、afterAll中移除:

const css = "...编译后的CSS..."; let style; beforeAll(() => { style = document.createElement('style'); style.textContent = css; document.head.appendChild(style); }); afterAll(() => { document.head.removeChild(style); });

这意味着样式规则在 jsdom 中真实存在,组件测试可以断言最终渲染后的外观行为,而不只是"样式被 mock 掉"。

缓存键getCacheKey把 transformer 自身源码、源文本、相对路径、configString、覆盖率标记、Node 版本、sass.info全部纳入 MD5 哈希,任何一个维度变化都会使缓存失效,保证增量测试的正确性。

与之形成对照的是根配置 jest.config.js 中的moduleNameMapper'\\.(css|scss)$': 'identity-obj-proxy'——那里把 SCSS 映射为代理对象。两种策略的应用场景不同:预设的 cssTransform 用于需要真实样式语义的测试,identity-obj-proxy 则用于只关心类名引用的场景。

4.3 fileTransform:静态资源的文件名桩

config/jest-config-carbon/transform/fileTransform.js 只有 10 余行核心逻辑:

process(src, filename) { return `export default ${JSON.stringify(path.basename(filename))};`; },

任何非 JS/CSS/JSON 的资源文件(例如test-upload-file-for-tooltip-to-show-up.png这类上传测试用的图片)导入后,都会得到一个等于原文件名的字符串导出。这是 Facebook Jest 官方文档推荐的经典做法:测试不需要真的读取图片二进制内容,只需要一个稳定的标识。

五、setup 文件:jsdom 环境补丁与测试纪律

5.1 setup.js:为 jsdom 补齐浏览器 API

config/jest-config-carbon/setup/setup.js 在测试文件加载前执行,主要解决 jsdom 与真实浏览器之间的能力差距:

补丁目的源码注释/出处
jest.setTimeout(20000)将单个测试默认超时提高到 20 秒,为 a11y 检查这类重活留出余量文件第 10 行
requestAnimationFramejsdom 缺失,Carbon 组件依赖它驱动动画逻辑,直接同步执行回调文件第 12-14 行
HTMLElement.prototype.offsetParenttabbable依赖它判断元素可见性,不覆盖则焦点顺序计算错误文件第 16-26 行
window.getComputedStyle规避 jest-axe 在 jsdom 下的已知问题文件第 28-32 行
ResizeObserver组件中的响应式观察器用 jest.fn 桩化,observe/unobserve/disconnect均为空操作文件第 34-42 行
AnimationEventjsdom 未实现,按 testing-library 社区方案手工实现文件第 44-74 行
HTMLDialogElement的 show/showModal/closejsdom 尚未实现 dialog 元素(jsdom#3294 的仓库内引用),用 jest.fn 桩化并维护open状态文件第 76-95 行

这些补丁并非凭空而来,每一条都对应着 Carbon 组件在真实浏览器中的依赖——例如 Dialog、Modal 组件使用<dialog>元素,Tearsheet、Popover 依赖 ResizeObserver,焦点管理依赖 tabbable。测试环境越接近真实浏览器,测试结果就越可信。

5.2 setupAfterEnv.js:自定义匹配器与 console 纪律

config/jest-config-carbon/setup/setupAfterEnv.js 在测试框架就绪后运行,做三件事:

注册无障碍匹配器:通过expect.extend(customMatchers)注入toHaveNoAxeViolationstoHaveNoACViolations。其中 AC 匹配器做了延迟加载优化(getAChecker()惰性初始化),且只在global.window && global.document存在时才注册——因为 accessibility-checker 导入时会注册 Jest hooks 并拉入文件系统模块,纯 Node 环境测试(会 mockfs)必须避开它。

注册 jest-domimport '@testing-library/jest-dom'提供toBeInTheDocumenttoHaveAttribute等常用断言。

console 调用管制:默认对console.errorconsole.warn零容忍,CI 环境下连console.log也禁止:

const consoleMethods = ['error', 'warn', process.env.CI && 'log'].filter(Boolean);

实现机制是:在每个beforeEach检查是否有未预期的 console 调用并抛出格式化错误(含彩色调用栈),在afterEach校验 console 方法仍是被 patch 的版本(防止测试擅自恢复 mock)。这套"React 官方同款"策略能逼出所有 React 警告,例如废弃生命周期、缺少 key 等,从源头保证组件代码质量。

六、无障碍测试匹配器:把 a11y 断言写进单元测试

Carbon 对无障碍有严格要求,这也体现在测试基座上。两个匹配器分别对应两套 a11y 引擎。

6.1 toHaveNoAxeViolations:axe-core 规则

config/jest-config-carbon/matchers/toHaveNoAxeViolations.js 封装axe-core

await expect(document.body).toHaveNoAxeViolations();

默认规则集中显式关闭了 6 条规则——document-titlehtml-has-langlandmark-one-mainpage-has-heading-oneregioncolor-contrast。原因是单元测试的渲染片段并不构成完整页面,这些"整页级"规则天然无法满足;而color-contrast在 jsdom 中无法真实计算像素对比度。用户可以通过第二个参数覆盖默认值:

await expect(node).toHaveNoAxeViolations({ rules: { 'color-contrast': { enabled: true } } });

失败时,匹配器会输出结构化的违规报告:规则 id、impact 级别、帮助链接、违规节点 HTML 与 failureSummary,并用 80 字符分隔线排版,方便直接定位问题 DOM。

6.2 toHaveNoACViolations:IBM 合规规则

config/jest-config-carbon/matchers/toHaveNoACViolations.js 封装 IBM 的accessibility-checker

await expect(document.body).toHaveNoACViolations('MyComponent');

它读取IBM_Accessibility规则集,动态构造一份去除 7 条噪音规则的Custom_Ruleset(如html_lang_existspage_title_existsaria_child_tabbable等——这些同样属于"整页级"或与测试渲染上下文无关的规则),再执行合规检查。引擎采用懒加载(aCheckerPromise单例),只有真正断言时才启动,避免拖慢其余测试。

仓库的端到端无障碍测试(e2e/components 下大量*-test.avt.e2e.js文件)同样围绕这两个引擎构建,说明 Carbon 形成了"单元级 axe/AC 断言 + 端到端 AVT 测试"的立体 a11y 保障体系。不过请注意:AC 匹配器只在global.window && global.document存在的 jsdom 测试中可用。

七、在 Carbon 仓库中的实际应用模式

7.1 从根配置看预设的组合方式

Carbon 根目录 jest.config.js 展示了"预设 + 项目覆盖"的标准用法:继承jest-config-carbon后,针对 monorepo 特点补充了collectCoverageFrom(只统计packages/**/src/**源码)、coveragePathIgnorePatternstestPathIgnorePatterns(web-components 与 scss-generator 由独立 job 负责)、extensionsToTreatAsEsm.jsx/.ts/.tsx视为 ESM)等。

7.2 预设在本仓库单元测试中的落地

Carbon 各包下的__tests__目录(如 packages/colors/tests、packages/type/tests、packages/motion/tests)以及packages/react/src下大量的*.test.js与快照文件(*.snap),都是这套预设的实际消费方。测试快照机制配合 cssTransform 的"真实样式注入",使得组件快照能反映样式影响。

7.3 完整的最小迁移示例

假设你要在一个新的 React + Sass + TypeScript 项目中复刻这套方案,最小配置如下:

// jest.config.js export default { preset: 'jest-config-carbon', // 覆盖预设中的默认规则 testMatch: ['<rootDir>/src/**/*.test.{js,jsx,ts,tsx}'], };
// 一个使用到预设能力的测试示例 import { render } from '@testing-library/react'; import Button from './Button'; it('渲染按钮且无无障碍违规', async () => { const { container } = render(<Button>点击</Button>); await expect(container).toHaveNoAxeViolations(); });

运行时需满足 config/jest-config-carbon/package.json 声明的依赖环境:Babel 8、Jest 30(babel-jestjest-environment-jsdom均要求 ^30)、React 19(devDependencies 中为 ^19.2.3)。预设内部为 ESM 模块("type": "module"),项目请确保使用 ESM 语法的 Jest 配置或按 Jest 的 ESM 支持要求调整。

八、版本与许可信息

  • 当前版本:1.31.0(见 config/jest-config-carbon/package.json);
  • 许可:Apache-2.0,仓库根目录 LICENSE 有完整文本;
  • 维护方式:遵循仓库的 贡献指南 与决策记录(docs/decisions)推进演进。

结语

jest-config-carbon是观察大型设计系统如何管理测试基础设施的极佳样本:它把 Babel 转译、Sass 实时编译、静态资源桩替换、jsdom 补丁、无障碍断言与 console 纪律统一收敛为一个预设,让每个组件包的开发者只需关注测试本身。无论是直接安装使用,还是借鉴其 transformer 与 setup 的设计模式,这份配置都能为你的前端测试基建带来直接收益。

【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询