first commit
This commit is contained in:
27
contributors/code/README.md
Normal file
27
contributors/code/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 代码贡献指南
|
||||
|
||||
关于如何开始为古腾堡项目贡献代码的指南。
|
||||
|
||||
## 讨论交流
|
||||
|
||||
[Make WordPress Core 博客](https://make.wordpress.org/core/)是获取WordPress开发最新信息的主要平台,包括公告、产品目标、会议记录、会议议程等。
|
||||
|
||||
开发讨论将在[Make WordPress Slack](https://make.wordpress.org/chat)(需要注册)的`#core-editor`和`#core-js`频道实时进行。
|
||||
|
||||
## 开发中心
|
||||
|
||||
古腾堡项目使用GitHub管理代码并跟踪问题。主代码库位于:[https://github.com/WordPress/gutenberg](https://github.com/WordPress/gutenberg)。
|
||||
|
||||
浏览[问题列表](https://github.com/wordpress/gutenberg/issues)寻找可参与解决的问题。[初试身手](https://github.com/wordpress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22)和[初次审阅](https://github.com/WordPress/gutenberg/pulls?q=is%3Aopen+is%3Apr+label%3A%22Good+First+Review%22)标签是不错的入门起点。
|
||||
|
||||
## 贡献者资源
|
||||
|
||||
- [入门指南](/docs/contributors/code/getting-started-with-code-contribution.md):记录开发环境搭建流程,包括测试站点和开发者工具建议
|
||||
- [Git工作流](/docs/contributors/code/git-workflow.md):说明使用拉取请求部署更改的git流程
|
||||
- [编码规范](/docs/contributors/code/coding-guidelines.md):概述古腾堡项目中使用的额外模式与约定
|
||||
- [测试概览](/docs/contributors/code/testing-overview.md):针对古腾堡中PHP和JavaScript开发的测试指南
|
||||
- [无障碍测试](/docs/contributors/accessibility-testing.md):记录古腾堡无障碍功能测试流程
|
||||
- [包管理](/docs/contributors/code/managing-packages.md):说明npm包的管理流程
|
||||
- [古腾堡发布流程](/docs/contributors/code/release.md):古腾堡项目不同类型发布的检查清单
|
||||
- [React Native移动编辑器](/docs/contributors/code/react-native/README.md):参与React Native移动编辑器开发的指南
|
||||
- [React Native集成测试指南](/docs/contributors/code/react-native/integration-test-guide.md):创建移动编辑器集成测试的指南
|
||||
91
contributors/code/auto-cherry-picking.md
Normal file
91
contributors/code/auto-cherry-picking.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 自动化精选合并
|
||||
|
||||
`npm run other:cherry-pick` 可自动将带有特定标签的拉取请求精选合并至**当前分支**。
|
||||
|
||||
该功能在WordPress主要版本发布时尤为实用,因为脚本默认会查找带有`Backport to WP Beta/RC`标签的已合并拉取请求。
|
||||
|
||||
您也可以通过传递自定义标签作为第一个参数,在不同场景中使用此功能。具体示例可参阅本文档末尾的Gutenberg插件发布案例。
|
||||
|
||||
运行`npm run other:cherry-pick`会出现以下提示:
|
||||
|
||||
```
|
||||
当前位于 "wp/6.2" 分支
|
||||
本脚本将执行以下操作:
|
||||
• 将标记为"Backport to WP Beta/RC"的已合并PR精选合并至此分支
|
||||
• 询问是否推送该分支
|
||||
• 在每个PR中添加注释
|
||||
• 移除每个PR的标签
|
||||
|
||||
后两项操作将使用您关联至GitHub CLI(gh命令)的账户执行
|
||||
|
||||
是否继续?(Y/n)
|
||||
```
|
||||
|
||||
同意后将会执行以下流程:
|
||||
|
||||
```
|
||||
开始逐个执行精选合并..
|
||||
|
||||
$ git pull origin wp/6.2 --rebase...
|
||||
$ git fetch origin trunk...
|
||||
|
||||
发现以下待精选合并的PR:
|
||||
#41198 – 站点编辑器:设置样式预览最小宽度
|
||||
|
||||
正在获取提交ID... 完成!
|
||||
#41198 – 860a39665c318d33027d – 站点编辑器:设置样式预览...
|
||||
|
||||
开始逐个执行精选合并...
|
||||
|
||||
第一轮精选合并:
|
||||
精选提交:afe9b757b4 对应PR:#41198 – 站点编辑器:设置样式预览...
|
||||
精选合并完成!
|
||||
|
||||
执行结果:
|
||||
成功精选合并 1 个PR
|
||||
合并失败 0 个PR
|
||||
|
||||
即将推送至 origin/wp/6.2
|
||||
是否继续?(Y/n)
|
||||
```
|
||||
|
||||
本次运行成功!此时您可以确认是否精选合并了正确的PR。
|
||||
|
||||
如果精选合并出现冲突怎么办?脚本会继续处理其他PR并自动重试。
|
||||
若部分PR仍合并失败,脚本将跳过这些PR并告知需要手动解决冲突的节点。
|
||||
|
||||
无论哪种情况,当您通过精选合并阶段后:
|
||||
|
||||
```
|
||||
正在推送至 origin/wp/6.2
|
||||
添加注释并移除标签...
|
||||
41198: 已将此PR精选合并至wp/6.2分支以便纳入下一版本:afe9b757b4
|
||||
完成!
|
||||
```
|
||||
|
||||
注释功能为可选操作,仅当您安装[`gh`命令行工具](https://cli.github.com/)时可用。
|
||||
|
||||
### 能否使用`Backport to WP Beta/RC`之外的标签?
|
||||
|
||||
可以!只需将其作为第一个参数传递:
|
||||
|
||||
```
|
||||
npm run other:cherry-pick "其他标签"
|
||||
```
|
||||
|
||||
### 如何用于Gutenberg插件发布?
|
||||
|
||||
```
|
||||
# 切换到发布分支
|
||||
git checkout release/X.Y
|
||||
|
||||
# 精选合并所有带有相关回溯标签的已合并PR
|
||||
npm run other:cherry-pick "Backport to Gutenberg RC"
|
||||
```
|
||||
|
||||
### 未来改进方向
|
||||
|
||||
未来有望实现根据当前所选分支自动匹配对应标签:
|
||||
|
||||
* release/X.Y - 插件发布分支 → "Backport to Gutenberg RC"
|
||||
* wp/X.Y - WordPress发布分支 → "Backport to WP Beta/RC"
|
||||
38
contributors/code/back-merging-to-wp-core.md
Normal file
38
contributors/code/back-merging-to-wp-core.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 将代码反向合并至WordPress核心
|
||||
|
||||
在WordPress软件的主要版本发布时,需要将Gutenberg的功能合并到WordPress核心代码中。通常这涉及提取Gutenberg代码库中`.php`文件的变更,并在WP核心代码库中进行相应更新。
|
||||
|
||||
## 合并标准
|
||||
|
||||
### 文件/目录范围
|
||||
|
||||
以下文件/目录内的变更通常需要反向合并至WP核心:
|
||||
|
||||
- `lib/` 目录
|
||||
- `phpunit/` 目录
|
||||
|
||||
### 排除目录/文件
|
||||
|
||||
以下目录/文件_无需_反向合并至WP核心:
|
||||
|
||||
- `lib/load.php` - 插件专用代码
|
||||
- `lib/experiments-page.php` - 实验性功能为插件专用
|
||||
- `packages/block-library` - 将在程序包同步过程中自动处理
|
||||
- `packages/e2e-tests/plugins` - 仅限端到端测试相关的PHP文件(主要为测试数据生成器)
|
||||
- `phpunit/blocks` - 该代码由Gutenberg维护,测试文件也应保留在此
|
||||
|
||||
请注意此列表并未涵盖所有情况。
|
||||
|
||||
### 拉取请求标准
|
||||
|
||||
通常来说,自[上一稳定版WP核心](https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/)所包含的最终版Gutenberg发布之日起,所有提交至Gutenberg代码库的PHP代码都应考虑反向合并至WP核心。
|
||||
|
||||
但存在以下例外情况,符合这些标准的PR_无需_反向合并至WP核心:
|
||||
|
||||
- 未包含PHP代码变更
|
||||
- 具有`Backport from WordPress Core`标签 - 该代码已存在于WP核心,正在同步回Gutenberg
|
||||
- 具有`Backported to WordPress Core`标签 - 该代码已完成向WP核心的同步
|
||||
|
||||
## 扩展阅读
|
||||
|
||||
另请参阅关于[Gutenberg PHP代码](/lib/README.md)的补充文档。
|
||||
79
contributors/code/backward-compatibility.md
Normal file
79
contributors/code/backward-compatibility.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# 向后兼容性
|
||||
|
||||
历史上,WordPress 以其跨版本保持向后兼容性而闻名。Gutenberg 在其生产环境的公共 API 中尽可能遵循这一原则。在极少数情况下,破坏向后兼容性不可避免,此时需确保:
|
||||
|
||||
- 破坏范围应尽可能限制在 API 的小部分区域内。
|
||||
- 应通过开发者说明文档向第三方开发者尽可能清晰地记录破坏性变更。
|
||||
|
||||
## 生产环境公共 API 的界定标准
|
||||
|
||||
Gutenberg 代码库由两种不同类型的包组成:
|
||||
|
||||
- **生产环境包**:这些包作为 WordPress 脚本发布(例如:wp-components、wp-editor...)。
|
||||
- **开发环境包**:这些包由开发者工具组成,可供第三方开发者用于检查、测试、格式化和构建其主题和插件(例如:@wordpress/scrips、@wordpress/env...)。通常,这些包在第三方项目中作为 npm 依赖项使用。
|
||||
|
||||
向后兼容性保证仅适用于生产环境包,因为更新是通过 WordPress 升级进行的。
|
||||
|
||||
生产环境包使用 `wp` 全局变量向第三方开发者提供 API。这些 API 可以是 JavaScript 函数、变量和 React 组件。
|
||||
|
||||
### 如何保持 JavaScript 函数的向后兼容性
|
||||
|
||||
- 函数名称不应更改。
|
||||
- 函数的参数顺序不应更改。
|
||||
- 函数的返回值类型不应更改。
|
||||
- 如果保证所有之前的调用仍然有效,可以对参数进行更改(新增参数、修改语义)。
|
||||
|
||||
### 如何保持 React 组件的向后兼容性
|
||||
|
||||
- 组件名称不应更改。
|
||||
- 组件的属性不应被移除。
|
||||
- 应继续支持现有的属性值。如果组件接受函数作为属性,可以更新组件以接受同一属性的新类型,但不应破坏现有用法。
|
||||
- 允许添加新属性。
|
||||
- 只有在确保之前的上下文契约不被破坏的情况下,才能添加或移除 React Context 依赖。
|
||||
|
||||
### 如何保持区块的向后兼容性
|
||||
|
||||
- 当编辑器加载时,区块的现有用法不应被破坏或标记为无效。
|
||||
- 应保证现有区块的样式。
|
||||
- 标记更改应尽可能限制在最小范围内,但如果区块需要更改其保存的标记,导致先前版本无效,则应添加该区块的[**废弃版本**](/docs/reference-guides/block-api/block-deprecation.md)。
|
||||
|
||||
## 类名和 DOM 更新
|
||||
|
||||
React 组件树内部使用的类名和 DOM 节点不被视为公共 API 的一部分,可以进行修改。
|
||||
|
||||
对这些内容的更改应谨慎进行,因为它们可能影响第三方代码的样式和行为(即使它们本不应依赖这些内容)。如果可能,保留旧的类名和 DOM 节点。如果无法保留,请记录更改并撰写开发者说明。
|
||||
|
||||
## 废弃
|
||||
|
||||
随着项目的发展,现有 API 的缺陷会被发现,或者需要更新以支持新功能。在这种情况下,我们尽量保证现有 API 不被破坏,并构建新的替代 API。
|
||||
|
||||
为了鼓励第三方开发者采用新的 API,我们可以使用[**废弃**](/packages/deprecated/README.md)辅助工具来显示一条消息,说明废弃情况并在使用旧 API 时提供替代方案。
|
||||
|
||||
明确说明功能何时被废弃。使用辅助方法的 `since` 和 `plugin` 选项。
|
||||
|
||||
示例:
|
||||
|
||||
```js
|
||||
deprecated( 'wp.components.ClipboardButton', {
|
||||
since: '10.3',
|
||||
plugin: 'Gutenberg',
|
||||
alternative: 'wp.compose.useCopyToClipboard',
|
||||
} );
|
||||
```
|
||||
|
||||
## 开发者说明
|
||||
|
||||
开发者说明是在 WordPress 发布之前[发布在 make/core 网站上的文章](https://make.wordpress.org/core/tag/dev-notes/),用于向第三方开发者通报开发者 API 的重要变更,这些变更可能包括:
|
||||
|
||||
- 新的 API。
|
||||
- 可能影响现有插件和主题的现有 API 变更(例如:类名更改等)。
|
||||
- 不可避免的向后兼容性破坏,附带理由和迁移流程。
|
||||
- 重要的废弃(即使没有破坏性变更),附带理由和迁移流程。
|
||||
|
||||
### 开发者说明工作流程
|
||||
|
||||
- 在处理拉取请求时,如果发现需要开发者说明,请为 PR 添加 **Needs Dev Note** 标签。
|
||||
- 如果可能,在 PR 中添加评论,说明为什么需要开发者说明。
|
||||
- 当即将发布的 WordPress 版本的第一个测试版发布时,检查发布中包含的已合并 PR 列表,这些 PR 标记有 **Needs Dev Note** 标签。
|
||||
- 对于每一个这样的 PR,撰写开发者说明,并与 WordPress 发布负责人协调发布开发者说明。
|
||||
- 一旦 PR 的开发者说明发布,从 PR 中移除 **Needs Dev Note** 标签。
|
||||
766
contributors/code/coding-guidelines.md
Normal file
766
contributors/code/coding-guidelines.md
Normal file
@@ -0,0 +1,766 @@
|
||||
### 记录 React 组件文档
|
||||
|
||||
在可能的情况下,所有组件都应实现为[函数式组件](https://react.dev/learn/your-first-component),并使用[钩子](https://react.dev/reference/react/hooks)来管理组件的生命周期和状态。
|
||||
|
||||
记录函数式组件的方式应与记录其他函数相同。在记录组件时需要注意的主要问题是,该函数通常只接受一个参数(即“props”),该参数可能包含多个属性成员。使用[参数属性的点语法](https://jsdoc.app/tags-param.html#parameters-with-properties)来记录各个属性的类型。
|
||||
|
||||
````js
|
||||
/**
|
||||
* 将区块配置的标题渲染为字符串,如果无法确定标题则返回空值。
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```jsx
|
||||
* <BlockTitle clientId="afd1cb17-2c08-4e7a-91be-007ba7ddc3a1" />
|
||||
* ```
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {string} props.clientId 区块的客户端 ID。
|
||||
*
|
||||
* @return {?string} 区块标题。
|
||||
*/
|
||||
````
|
||||
|
||||
对于类组件,没有关于记录组件属性的推荐方式。Gutenberg 不使用也不认可 [`propTypes` 静态类成员](https://react.dev/reference/react/Component#static-proptypes)。
|
||||
|
||||
## PHP
|
||||
|
||||
我们使用 [`phpcs`(PHP_CodeSniffer)](https://github.com/squizlabs/PHP_CodeSniffer) 和 [WordPress 编码标准规则集](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) 对本项目中的所有 PHP 代码进行大量自动化检查。这确保我们符合 WordPress 的 PHP 编码标准。
|
||||
|
||||
使用 PHPCS 最简单的方式是通过[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment)。安装完成后,可以通过运行 `npm run lint:php` 来检查 PHP 代码。
|
||||
|
||||
如果更倾向于在本地安装 PHPCS,应使用 `composer`。在计算机上[安装 `composer`](https://getcomposer.org/download/),然后运行 `composer install`。这将安装 `phpcs` 和 `WordPress-Coding-Standards`,之后可以通过 `composer lint` 运行检查。
|
||||
|
||||
# 编程规范
|
||||
|
||||
本文档旨在为古腾堡项目制定专属的编程规范。基础编程规范遵循[WordPress编码标准](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/)。以下章节将阐述古腾堡项目中使用的额外模式与约定。
|
||||
|
||||
## CSS
|
||||
|
||||
### 命名规范
|
||||
|
||||
为避免类名冲突,所有类名**必须**遵循以下准则,这些准则借鉴了[BEM(块、元素、修饰符)方法论](https://en.bem.info/methodology/)的核心思想。
|
||||
|
||||
所有分配给元素的类名都必须以软件包名称为前缀,后接连字符和组件所在目录的名称。组件根元素的所有后代元素必须追加以连字符分隔的描述符,并通过双下划线`__`与基础类名分隔。
|
||||
|
||||
- 根元素:`package-directory`
|
||||
- 子元素:`package-directory__descriptor-foo-bar`
|
||||
|
||||
根元素是指`index.js`中默认导出返回的最高层级祖先元素。需要注意的是,如果文件夹包含多个文件,且每个文件都有各自默认导出的组件,则只有`index.js`渲染的元素可视为根元素,其余所有元素都应视为后代元素。
|
||||
|
||||
**示例:**
|
||||
|
||||
假设存在位于`packages/components/src/notice/index.js`的组件:
|
||||
|
||||
```jsx
|
||||
export default function Notice( { children, onRemove } ) {
|
||||
return (
|
||||
<div className="components-notice">
|
||||
<div className="components-notice__content">{ children }</div>
|
||||
<Button
|
||||
className="components-notice__dismiss"
|
||||
icon={ check }
|
||||
label={ __( '关闭此通知' ) }
|
||||
onClick={ onRemove }
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
组件可被赋予表示状态的类名(例如“激活”标签页或“展开”面板)。这些修饰符应作为独立类名应用,通过`is-`前缀构成形容词表达式(如`is-active`或`is-opened`)。特殊情况下,可能会遇到修饰符前缀的变体,通常是为了提升可读性(如`has-warning`)。由于修饰符类名不限定于特定组件,在样式表中应始终与被修饰的组件配合使用(`.components-panel.is-opened`)。
|
||||
|
||||
**示例:**
|
||||
|
||||
再次以通知组件为例。我们可能需要为可关闭通知应用特定样式。[`clsx`工具包](https://www.npmjs.com/package/clsx)可辅助实现条件化应用修饰符类名。
|
||||
|
||||
```jsx
|
||||
import clsx from 'clsx';
|
||||
|
||||
export default function Notice( { children, onRemove, isDismissible } ) {
|
||||
const classes = clsx( 'components-notice', {
|
||||
'is-dismissible': isDismissible,
|
||||
} );
|
||||
|
||||
return <div className={ classes }>{ /* ... */ }</div>;
|
||||
}
|
||||
```
|
||||
|
||||
组件的类名**绝不应**在其所属文件夹之外使用(极少数例外情况如[`_z-index.scss`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/base-styles/_z-index.scss))。若需在自己组件中继承其他组件的样式,应渲染该组件的实例。最不推荐的做法是在自身组件的样式表中复制样式。此举旨在通过将共享组件隔离为可复用接口来提升可维护性,通过适配有限通用组件支持多样化使用场景,从而减少相似UI元素的实现复杂度。
|
||||
|
||||
#### 区块组件的SCSS文件命名规范
|
||||
|
||||
当Webpack运行时,构建过程会将区块库目录内的SCSS拆分为两个独立的CSS文件。
|
||||
|
||||
置于`style.scss`文件中的样式将被编译至`blocks/build/style.css`,同时在前端主题和编辑器中加载。如需针对区块在编辑器中的显示添加特定样式,请将其加入`editor.scss`文件。
|
||||
|
||||
同时出现在主题和编辑器中的样式示例包括图库列数和首字下沉效果。
|
||||
|
||||
### React 组件
|
||||
|
||||
推荐将所有组件实现为[函数式组件](https://react.dev/learn/your-first-component),并使用[钩子](https://react.dev/reference/react/hooks)来管理组件状态和生命周期。除[错误边界](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)外,您不应遇到必须使用类组件的情况。请注意,[WordPress 代码重构指南](https://make.wordpress.org/core/handbook/contribute/code-refactoring/)在此适用:无需集中批量更新类组件,而应将其视为结合其他更改进行重构的良好机会。
|
||||
|
||||
## 使用 JSDoc 的 JavaScript 文档
|
||||
|
||||
Gutenberg 遵循 [WordPress JavaScript 文档标准](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/),并针对其在文件组织中对[导入语义](/docs/contributors/code/coding-guidelines.md#imports)的特殊使用、[使用 TypeScript 工具](/docs/contributors/code/testing-overview.md#javascript-testing)进行类型验证,以及使用 [`@wordpress/docgen`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/docgen) 自动生成文档,制定了相关附加指南。
|
||||
|
||||
更多指导请参考以下资源:
|
||||
|
||||
- [JSDoc 官方文档](https://jsdoc.app/index.html)
|
||||
- [TypeScript 支持的 JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html)
|
||||
|
||||
### 自定义类型
|
||||
|
||||
使用 [JSDoc `@typedef` 标签](https://jsdoc.app/tags-typedef.html)定义自定义类型。
|
||||
|
||||
自定义类型应包含描述,并始终注明其基础类型。命名应尽可能简洁,同时保持含义清晰并避免与其他全局或作用域内的类型冲突。所有自定义类型需添加 `WP` 前缀,避免多余或冗余的前缀和后缀(例如 `Type` 后缀或 `Custom` 前缀)。自定义类型默认非全局类型,因此无需过度特指某个特定包,但命名应具备足够特异性,以避免在与其他类型处于同一作用域时产生歧义或命名冲突。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 块选择对象。
|
||||
*
|
||||
* @typedef WPBlockSelection
|
||||
*
|
||||
* @property {string} clientId 块客户端 ID。
|
||||
* @property {string} attributeKey 块属性键。
|
||||
* @property {number} offset 基于富文本值的属性值偏移量。
|
||||
*/
|
||||
```
|
||||
|
||||
注意 `@typedef` 和类型名之间没有 `{Object}`。由于下方的 `@property` 表明这是对象类型,建议在定义对象类型时不使用 `{Object}`。
|
||||
|
||||
自定义类型也可用于描述一组预定义选项。虽然[类型联合](https://jsdoc.app/tags-type.html)可与字面值一起用作内联类型,但要在保持 80 字符最大行宽的同时对齐标签可能较为困难。使用自定义类型定义联合类型既能描述这些选项的用途,又有助于避免行宽问题。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 命名的断点尺寸。
|
||||
*
|
||||
* @typedef {'huge'|'wide'|'large'|'medium'|'small'|'mobile'} WPBreakpoint
|
||||
*/
|
||||
```
|
||||
|
||||
注意定义字符串字面量集合时需使用引号。根据 [JavaScript 编码标准](https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/),在为类型或[默认函数参数](#nullable-undefined-and-void-types)分配字符串字面量时,或在[指定导入类型的路径](#importing-and-exporting-types)时,应使用单引号。
|
||||
|
||||
### 可空类型、undefined 类型与 void 类型
|
||||
|
||||
你可以使用前置问号 `?` 表示可空类型。仅当描述类型本身或显式的 `null` 值时,才使用可空类型形式。不要将可空形式作为可选参数的标识符。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 获取指定键的配置值(若存在)。若未配置该值则返回 null。
|
||||
*
|
||||
* @param {string} key 要获取的配置键名。
|
||||
*
|
||||
* @return {?*} 配置值(若存在)。
|
||||
*/
|
||||
function getConfigurationValue( key ) {
|
||||
return config.hasOwnProperty( key ) ? config[ key ] : null;
|
||||
}
|
||||
```
|
||||
|
||||
类似地,仅当需要显式的 `undefined` 值时,才使用 `undefined` 类型。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 若下一个 HTML 标签闭合当前标签,则返回 true。
|
||||
*
|
||||
* @param {WPHTMLToken} currentToken 要比对的当前标签。
|
||||
* @param {WPHTMLToken|undefined} nextToken 要比对的下一个标签。
|
||||
*
|
||||
* @return {boolean} 若 `nextToken` 闭合 `currentToken` 则返回 true,否则返回 false。
|
||||
*/
|
||||
```
|
||||
|
||||
若参数为可选参数,请使用[方括号标记法](https://jsdoc.app/tags-param.html#optional-parameters-and-default-values)。如果可选参数具有默认值,且该值可通过函数表达式中的[默认参数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters)语法表示,则无需在 JSDoc 中包含该值。若函数参数的默认值需要复杂逻辑且无法通过默认参数语法表示,可选择在 JSDoc 中包含该默认值。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 渲染工具栏组件。
|
||||
*
|
||||
* @param {Object} props 组件属性。
|
||||
* @param {string} [props.className] 要设置到容器 `<div />` 上的类名。
|
||||
*/
|
||||
```
|
||||
|
||||
当函数不包含 `return` 语句时,称其具有 `void` 返回值。若返回类型为 `void`,则无需包含 `@return` 标签。
|
||||
|
||||
若函数存在多个代码路径,且部分(非全部)条件分支包含 `return` 语句,可将其标注为包含 `void` 类型的联合类型。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 获取指定键的配置值(若存在)。
|
||||
*
|
||||
* @param {string} key 要获取的配置键名。
|
||||
*
|
||||
* @return {*|void} 配置值(若存在)。
|
||||
*/
|
||||
function getConfigurationValue( key ) {
|
||||
if ( config.hasOwnProperty( key ) ) {
|
||||
return config[ key ];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
当标注[函数类型](#泛型类型)时,必须始终包含 `void` 返回值类型,否则该函数会被推断为返回混合值("any"),而非 void 结果。
|
||||
|
||||
```js
|
||||
/**
|
||||
* apiFetch 中间件处理器。在传入获取选项后,中间件需在完成处理时调用 `next` 中间件。
|
||||
*
|
||||
* @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware
|
||||
*/
|
||||
```
|
||||
|
||||
### 示例文档规范
|
||||
|
||||
由于使用 `@wordpress/docgen` 工具生成的文档会包含已定义的 `@example` 标签,因此为函数和组件包含使用示例被视为最佳实践。这对于记录包公开 API 的成员尤为重要。
|
||||
|
||||
编写示例时,请使用 Markdown 的 <code>\`\`\`</code> 代码块标记来界定代码段的起始与结束。示例可跨越多行。
|
||||
|
||||
````js
|
||||
/**
|
||||
* 根据已注册存储的名称,返回该存储选择器对象。选择器函数已预绑定,可自动传递当前状态。
|
||||
* 作为使用者,仅需传递选择器所需参数(若适用)。
|
||||
*
|
||||
* @param {string} name 存储名称。
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* select( 'my-shop' ).getPrice( 'hammer' );
|
||||
* ```
|
||||
*
|
||||
* @return {Record<string,WPDataSelector>} 包含存储选择器的对象。
|
||||
*/
|
||||
````
|
||||
|
||||
## JavaScript
|
||||
|
||||
Gutenberg 中的 JavaScript 采用了 [ECMAScript 语言规范](https://www.ecma-international.org/ecma-262/) 的现代语言特性以及 [JSX 语言语法扩展](https://react.dev/learn/writing-markup-with-jsx)。这些功能通过预设配置组合实现,特别是项目中 [Babel](https://babeljs.io/) 配置使用的预设 [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default)。
|
||||
|
||||
虽然引入新 JavaScript 语言特性的 [分阶段流程](https://tc39.es/process-document/) 提供了在特性尚未完全定型前使用的机会,**但 Gutenberg 项目和 `@wordpress/babel-preset-default` 配置仅支持已达到第 4 阶段("已完成")的提案**。
|
||||
|
||||
### 导入方式
|
||||
|
||||
在 Gutenberg 项目中,我们使用 [ES2015 导入语法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 来创建模块化代码,明确区分特定功能代码、跨 WordPress 功能共享代码以及第三方依赖代码。
|
||||
|
||||
这些区分通过文件顶部的多行注释来标识,这些注释标注了从其他文件或源导入的代码。
|
||||
|
||||
#### 外部依赖
|
||||
|
||||
外部依赖是指不由 WordPress 贡献者维护的第三方代码,而是 [作为默认脚本包含在 WordPress 中](https://developer.wordpress.org/reference/functions/wp_enqueue_script/#default-scripts-included-and-registered-by-wordpress) 或通过外部包管理器(如 [npm](https://www.npmjs.com/))引用的代码。
|
||||
|
||||
示例:
|
||||
|
||||
```js
|
||||
/**
|
||||
* 外部依赖
|
||||
*/
|
||||
import moment from 'moment';
|
||||
```
|
||||
|
||||
#### WordPress 依赖
|
||||
|
||||
为了促进功能间的可复用性,我们的 JavaScript 被拆分为特定领域的模块,这些模块 [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) 一个或多个函数或对象。在 Gutenberg 项目中,我们在顶级目录下区分这些模块。每个模块服务于独立的目的,并且经常在它们之间共享代码。例如,为了本地化其文本,编辑器代码需要包含来自 `i18n` 模块的函数。
|
||||
|
||||
示例:
|
||||
|
||||
```js
|
||||
/**
|
||||
* WordPress 依赖
|
||||
*/
|
||||
import { __ } from '@wordpress/i18n';
|
||||
```
|
||||
|
||||
#### 内部依赖
|
||||
|
||||
在特定功能内,代码被组织到不同的文件和文件夹中。与外部和 WordPress 依赖的情况一样,您可以使用 `import` 关键字将这些代码引入作用域。这里的主要区别在于,当导入内部文件时,应使用相对于您正在使用的顶级目录的特定相对路径。
|
||||
|
||||
示例:
|
||||
|
||||
```js
|
||||
/**
|
||||
* 内部依赖
|
||||
*/
|
||||
import VisualEditor from '../visual-editor';
|
||||
```
|
||||
|
||||
### 遗留实验性 API、仅限插件使用的 API 和私有 API
|
||||
|
||||
#### 遗留实验性 API
|
||||
|
||||
历史上,Gutenberg 使用 `__experimental` 和 `__unstable` 前缀来表示给定的 API 尚未稳定,可能会发生变化。这是一种遗留约定,应避免使用,转而采用下面描述的仅限插件使用的 API 模式或私有 API 模式。
|
||||
|
||||
使用前缀的问题在于这些 API 很少被稳定或移除。截至 2022 年 6 月,WordPress 核心包含了 280 个公开导出的实验性 API,这些 API 是在主要的 WordPress 发布期间从 Gutenberg 插件合并而来的。许多插件和主题开始依赖这些实验性 API 来实现无法通过其他方式访问的关键功能。
|
||||
|
||||
遗留的 `__experimental` API 不能再随意移除。它们已成为 WordPress 公共 API 的一部分,并受 [WordPress 向后兼容性政策](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/) 的约束。移除它们涉及一个弃用过程。对于某些 API 来说可能相对容易,但对于其他 API 可能需要付出努力并跨越多个 WordPress 版本。
|
||||
|
||||
总之,不要为新 API 使用 `__experimental` 前缀。请改用仅限插件使用的 API 和私有 API。
|
||||
|
||||
### 类型导入与导出
|
||||
|
||||
使用 [TypeScript 的 `import` 函数](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types) 可从其他文件或第三方依赖项导入类型声明。
|
||||
|
||||
由于导入的类型声明可能占用过多行长度,并在多次引用时显得冗长,建议在文件顶部的 [`import` 分组](/docs/contributors/code/coding-guidelines.md#imports) 之后,立即使用 `@typedef` 声明为外部类型创建别名。
|
||||
|
||||
```js
|
||||
/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */
|
||||
```
|
||||
|
||||
注意,所有在其他文件中定义的自定义类型均可导入。
|
||||
|
||||
在确定应从 WordPress 包中提供哪些类型时,包入口脚本中的 `@typedef` 语句应视为与其公共 API 等效。了解这一点很重要,既可避免无意中将内部类型暴露在公共接口上,也可作为项目公开类型的一种方式。
|
||||
|
||||
```js
|
||||
// packages/data/src/index.js
|
||||
|
||||
/** @typedef {import('./registry').WPDataRegistry} WPDataRegistry */
|
||||
```
|
||||
|
||||
在此代码片段中,`@typedef` 将支持前例中 `import('@wordpress/data')` 的用法。
|
||||
|
||||
#### 外部依赖
|
||||
|
||||
许多第三方依赖会分发其自带的 TypeScript 类型定义。对于这些依赖,`import` 语义应“直接可用”。
|
||||
|
||||

|
||||
|
||||
如果您的编辑器使用了 [TypeScript 集成](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support),通常可以看到此功能生效,只要类型解析结果不是回退的 `any` 类型即可。
|
||||
|
||||
对于未分发自带 TypeScript 类型的包,如果存在 [DefinitelyTyped](https://definitelytyped.org/) 社区维护的类型定义,欢迎安装并使用。
|
||||
|
||||
### 泛型类型
|
||||
|
||||
在记录泛型类型(如 `Object`、`Function`、`Promise` 等)时,请始终包含有关预期记录类型的详细信息。
|
||||
|
||||
```js
|
||||
// 不推荐:
|
||||
|
||||
/** @type {Object} */
|
||||
/** @type {Function} */
|
||||
/** @type {Promise} */
|
||||
|
||||
// 推荐:
|
||||
|
||||
/** @type {Record<string,number>} */ /* 或 */ /** @type {{[setting:string]:any}} */
|
||||
/** @type {(key:string)=>boolean} */
|
||||
/** @type {Promise<string>} */
|
||||
```
|
||||
|
||||
当对象用作字典时,可通过两种方式定义其类型:可索引接口(`{[setting:string]:any}`)或 `Record`。当对象的键名(如 `setting`)能为开发者提供使用提示时,使用可索引接口;否则,使用 `Record`。
|
||||
|
||||
此处的函数表达式使用了 TypeScript 的函数类型语法,有助于提供有关预期参数名称和类型的更详细信息。更多内容请参考 [TypeScript 的 `@type` 标签函数建议](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#type)。
|
||||
|
||||
在更高级的情况下,您可以使用 [TypeScript 的 `@template` 标签](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template) 将自定义类型定义为泛型类型。
|
||||
|
||||
类似于关于类型联合和字面量值的“自定义类型”建议,您可以考虑创建自定义类型 `@typedef`,以更好地描述对象记录的预期键值,或提取复杂的函数签名。
|
||||
|
||||
```js
|
||||
/**
|
||||
* apiFetch 中间件处理程序。传入获取选项后,中间件应在完成处理时调用 `next` 中间件。
|
||||
*
|
||||
* @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware
|
||||
*/
|
||||
```
|
||||
|
||||
```js
|
||||
/**
|
||||
* 命名的断点尺寸。
|
||||
*
|
||||
* @typedef {"huge"|"wide"|"large"|"medium"|"small"|"mobile"} WPBreakpoint
|
||||
*/
|
||||
|
||||
/**
|
||||
* 断点名称及其生效像素宽度的哈希表。
|
||||
*
|
||||
* @type {Record<WPBreakpoint,number>}
|
||||
*/
|
||||
const BREAKPOINTS = { huge: 1440 /* , ... */ };
|
||||
```
|
||||
|
||||
##### 私有函数、类与变量
|
||||
|
||||
```js
|
||||
// 在 packages/package1/index.js 中:
|
||||
import { lock } from './lock-unlock';
|
||||
|
||||
export const privateApis = {};
|
||||
/* 将私有数据附加到导出对象 */
|
||||
lock( privateApis, {
|
||||
privateCallback: function () {},
|
||||
privateReactComponent: function PrivateComponent() {
|
||||
return <div />;
|
||||
},
|
||||
privateClass: class PrivateClass {},
|
||||
privateVariable: 5,
|
||||
} );
|
||||
```
|
||||
|
||||
```js
|
||||
// 在 packages/package2/index.js 中:
|
||||
import { privateApis } from '@wordpress/package1';
|
||||
import { unlock } from './lock-unlock';
|
||||
|
||||
const {
|
||||
privateCallback,
|
||||
privateReactComponent,
|
||||
privateClass,
|
||||
privateVariable,
|
||||
} = unlock( privateApis );
|
||||
```
|
||||
|
||||
请务必始终在**已注册**的存储上注册私有操作和选择器。
|
||||
|
||||
有时这很简单:
|
||||
|
||||
```js
|
||||
export const store = createReduxStore( STORE_NAME, storeConfig() );
|
||||
// `register` 使用与 `createReduxStore` 创建的相同 `store` 对象
|
||||
register( store );
|
||||
unlock( store ).registerPrivateActions( {
|
||||
// ...
|
||||
} );
|
||||
```
|
||||
|
||||
但某些包可能同时调用 `createReduxStore` **和** `registerStore`。此时请始终选择已注册的存储:
|
||||
|
||||
```js
|
||||
export const store = createReduxStore( STORE_NAME, {
|
||||
...storeConfig,
|
||||
persist: [ 'preferences' ],
|
||||
} );
|
||||
const registeredStore = registerStore( STORE_NAME, {
|
||||
...storeConfig,
|
||||
persist: [ 'preferences' ],
|
||||
} );
|
||||
unlock( registeredStore ).registerPrivateActions( {
|
||||
// ...
|
||||
} );
|
||||
```
|
||||
|
||||
#### 私有函数参数
|
||||
|
||||
若需为稳定函数添加私有参数,需要准备该函数的稳定版本和私有版本。然后导出稳定函数,并将不稳定函数在其内部进行 `lock()` 锁定:
|
||||
|
||||
```js
|
||||
// 在 @wordpress/package1/index.js 中:
|
||||
import { lock } from './lock-unlock';
|
||||
|
||||
// 私有函数包含所有逻辑
|
||||
function privateValidateBlocks( formula, privateIsStrict ) {
|
||||
let isValid = false;
|
||||
// ...不想重复的复杂逻辑...
|
||||
if ( privateIsStrict ) {
|
||||
// ...
|
||||
}
|
||||
// ...不想重复的复杂逻辑...
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// 稳定的公共函数是一个轻量包装器,用于调用禁用私有功能的私有函数
|
||||
export function validateBlocks( blocks ) {
|
||||
privateValidateBlocks( blocks, false );
|
||||
}
|
||||
|
||||
export const privateApis = {};
|
||||
lock( privateApis, { privateValidateBlocks } );
|
||||
```
|
||||
|
||||
```js
|
||||
// 在 @wordpress/package2/index.js 中:
|
||||
import { privateApis as package1PrivateApis } from '@wordpress/package1';
|
||||
import { unlock } from './lock-unlock';
|
||||
|
||||
// 可通过稳定函数"解锁"私有函数:
|
||||
const { privateValidateBlocks } = unlock( package1PrivateApis );
|
||||
privateValidateBlocks( blocks, true );
|
||||
```
|
||||
|
||||
#### 私有React组件属性
|
||||
|
||||
若需为稳定组件添加私有参数,需要准备该组件的稳定版本和私有版本。然后导出稳定函数,并将不稳定函数在其内部进行 `lock()` 锁定:
|
||||
|
||||
```js
|
||||
// 在 @wordpress/package1/index.js 中:
|
||||
import { lock } from './lock-unlock';
|
||||
|
||||
// 私有组件包含所有逻辑
|
||||
const PrivateMyButton = ( { title, privateShowIcon = true } ) => {
|
||||
// ...不想重复的复杂逻辑...
|
||||
|
||||
return (
|
||||
<button>
|
||||
{ privateShowIcon && <Icon src={ someIcon } /> } { title }
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
// 稳定的公共组件是一个轻量包装器,用于调用禁用私有功能的私有组件
|
||||
export const MyButton = ( { title } ) => (
|
||||
<PrivateMyButton title={ title } privateShowIcon={ false } />
|
||||
);
|
||||
|
||||
export const privateApis = {};
|
||||
lock( privateApis, { PrivateMyButton } );
|
||||
```
|
||||
|
||||
```js
|
||||
// 在 @wordpress/package2/index.js 中:
|
||||
import { privateApis } from '@wordpress/package1';
|
||||
import { unlock } from './lock-unlock';
|
||||
|
||||
// 可通过稳定组件"解锁"私有组件:
|
||||
const { PrivateMyButton } = unlock( privateApis );
|
||||
export function MyComponent() {
|
||||
return <PrivateMyButton data={ data } privateShowIcon={ true } />;
|
||||
}
|
||||
```
|
||||
|
||||
#### 插件专用 API
|
||||
|
||||
插件专用 API 是从模块导出的临时值,其存在性可能待未来修订,或仅为达成特定目标而提供的临时方案。
|
||||
|
||||
_面向外部使用者:_
|
||||
|
||||
**插件专用 API 不提供任何支持承诺。** 它们可能在任何时候被移除或变更,且不会提前通知,包括在次要版本或补丁版本中。作为外部使用者,您应避免使用这些 API。
|
||||
|
||||
_面向项目贡献者:_
|
||||
|
||||
**插件专用 API** 是指计划最终对外开放,但尚需进一步实验、测试和讨论的接口。应尽快将其稳定或移除。
|
||||
|
||||
插件专用 API 被排除在 WordPress 核心之外,仅可在 Gutenberg 插件中使用:
|
||||
|
||||
```js
|
||||
// 使用 globalThis.IS_GUTENBERG_PLUGIN 允许 Webpack 将此导出
|
||||
// 从 WordPress 核心中排除:
|
||||
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
|
||||
export { doSomethingExciting } from './api';
|
||||
}
|
||||
```
|
||||
|
||||
这类 API 的公共接口尚未最终确定。除代码内部的引用外,它们不应在任何变更日志中被记录或提及。从外部视角来看,它们实际上应被视为不存在。在大多数情况下,它们仅用于满足本代码库中维护的包之间的内部需求。
|
||||
|
||||
尽管插件专用 API 最终可能稳定成为公共 API,但这并非必然结果。
|
||||
|
||||
#### 私有 API
|
||||
|
||||
每个需要内部访问或暴露私有 API 的 `@wordpress` 包,可通过启用 `@wordpress/private-apis` 来实现:
|
||||
|
||||
```js
|
||||
// 在 packages/block-editor/private-apis.js 中:
|
||||
import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis';
|
||||
export const { lock, unlock } =
|
||||
__dangerousOptInToUnstableAPIsOnlyForCoreModules(
|
||||
'我知晓私有功能不可在主题或插件中使用,否则将在下一版 WordPress 中失效。',
|
||||
'@wordpress/block-editor' // 调用 __dangerousOptInToUnstableAPIsOnlyForCoreModules 的包名称
|
||||
//(非目标访问包的名称)
|
||||
);
|
||||
```
|
||||
|
||||
每个 `@wordpress` 包仅可启用一次。该流程明确告知扩展者不应使用此机制。本文档将重点介绍使用示例,但您可[在其 README.md 中了解更多关于 `@wordpress/private-apis` 包的信息](/packages/private-apis/README.md)。
|
||||
|
||||
启用后,您可使用 `lock()` 和 `unlock()` 工具:
|
||||
|
||||
```js
|
||||
// 假设此对象从包中导出:
|
||||
export const publicObject = {};
|
||||
|
||||
// 但此字符串属于内部内容,不应公开:
|
||||
const privateString = '私有信息';
|
||||
|
||||
// 解决方案:将字符串"锁定"在对象内部:
|
||||
lock( publicObject, privateString );
|
||||
|
||||
// 字符串不会嵌套在对象中,也无法从中提取:
|
||||
console.log( publicObject );
|
||||
// {}
|
||||
|
||||
// 访问字符串的唯一方式是"解锁"该对象:
|
||||
console.log( unlock( publicObject ) );
|
||||
// "私有信息"
|
||||
|
||||
// lock() 接受所有数据类型,不仅限于字符串:
|
||||
export const anotherObject = {};
|
||||
lock( anotherObject, function privateFn() {} );
|
||||
console.log( unlock( anotherObject ) );
|
||||
// function privateFn() {}
|
||||
```
|
||||
|
||||
继续阅读了解如何使用 `lock()` 和 `unlock()` 来避免公开导出各类私有 API。
|
||||
|
||||
##### 私有选择器与操作
|
||||
|
||||
您可将私有选择器和操作附加到公共存储:
|
||||
|
||||
```js
|
||||
// 在 packages/package1/store.js 中:
|
||||
import { privateHasContentRoleAttribute } from './private-selectors';
|
||||
import { privateToggleFeature } from './private-actions';
|
||||
// `lock` 函数从内部 private-apis.js 文件导出
|
||||
// 该文件已调用启用函数
|
||||
import { lock, unlock } from './lock-unlock';
|
||||
|
||||
export const store = registerStore( /* ... */ );
|
||||
// 将私有操作附加到导出的存储:
|
||||
unlock( store ).registerPrivateActions( {
|
||||
privateToggleFeature,
|
||||
} );
|
||||
|
||||
// 将私有选择器附加到导出的存储:
|
||||
unlock( store ).registerPrivateSelectors( {
|
||||
privateHasContentRoleAttribute,
|
||||
} );
|
||||
```
|
||||
|
||||
```js
|
||||
// 在 packages/package2/MyComponent.js 中:
|
||||
import { store } from '@wordpress/package1';
|
||||
import { useSelect } from '@wordpress/data';
|
||||
// `unlock` 函数从内部 private-apis.js 文件导出
|
||||
// 该文件已调用启用函数
|
||||
import { unlock } from './lock-unlock';
|
||||
|
||||
function MyComponent() {
|
||||
const hasRole = useSelect(
|
||||
( select ) =>
|
||||
// 使用私有选择器:
|
||||
unlock( select( store ) ).privateHasContentRoleAttribute()
|
||||
// 注意必须使用 unlock()。以下代码将无效:
|
||||
// select( store ).privateHasContentRoleAttribute()
|
||||
);
|
||||
|
||||
// 使用私有操作:
|
||||
unlock( useDispatch( store ) ).privateToggleFeature();
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 私有编辑器设置
|
||||
|
||||
WordPress扩展开发者无法自行更新私有区块设置。`@wordpress/block-editor`存储库的`updateSettings()`操作会过滤掉所有不属于公共API的设置。实际存储这些设置的唯一方式是通过私有操作`__experimentalUpdateSettings()`。
|
||||
|
||||
若要将区块编辑器设置设为私有,请将其添加到[/packages/block-editor/src/store/actions.js](/packages/block-editor/src/store/actions.js)中的`privateSettings`列表:
|
||||
|
||||
```js
|
||||
const privateSettings = [
|
||||
'inserterMediaCategories',
|
||||
// 在此列出区块编辑器设置以使其私有化
|
||||
];
|
||||
```
|
||||
|
||||
#### 私有block.json与theme.json接口
|
||||
|
||||
截至目前,尚无法将`block.json`和`theme.json`接口限制在Gutenberg代码库内使用。但未来新的私有接口将仅适用于WordPress核心区块,插件和主题将无法访问这些接口。
|
||||
|
||||
#### 在thunk中内联小型操作
|
||||
|
||||
最后,与其创建新的操作生成器,不如考虑使用[thunk](/docs/how-to-guides/thunks.md):
|
||||
|
||||
```js
|
||||
export function toggleFeature( scope, featureName ) {
|
||||
return function ( { dispatch } ) {
|
||||
dispatch( { type: '__private_BEFORE_TOGGLE' } );
|
||||
// ...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### 公开私有接口
|
||||
|
||||
某些私有接口可通过社区反馈获益,因此有必要向WordPress扩展开发者公开。但同时,将其转化为WordPress核心的公共接口并不合理。此时该如何处理?
|
||||
|
||||
您可以将该私有接口重新导出为仅限插件使用的接口,使其仅在Gutenberg插件中公开:
|
||||
|
||||
```js
|
||||
// 此函数在任何上下文中都不能被扩展开发者使用:
|
||||
function privateEverywhere() {}
|
||||
|
||||
// 此函数可由使用Gutenberg插件的扩展开发者使用,但不能在原生WordPress核心中使用:
|
||||
function privateInCorePublicInPlugin() {}
|
||||
|
||||
// Gutenberg在内部将这两个函数都视为私有接口:
|
||||
const privateApis = {};
|
||||
lock( privateApis, { privateEverywhere, privateInCorePublicInPlugin } );
|
||||
|
||||
// privateInCorePublicInPlugin函数被显式导出,
|
||||
// 但由于globalThis.IS_GUTENBERG_PLUGIN检查,该导出不会合并到WordPress核心中。
|
||||
if ( globalThis.IS_GUTENBERG_PLUGIN ) {
|
||||
export const privateInCorePublicInPlugin =
|
||||
unlock( privateApis ).privateInCorePublicInPlugin;
|
||||
}
|
||||
```
|
||||
|
||||
### 对象
|
||||
|
||||
在定义对象属性值时,尽可能使用[简写表示法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015):
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
const a = 10;
|
||||
|
||||
// 不推荐:
|
||||
const object = {
|
||||
a: a,
|
||||
performAction: function () {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
|
||||
// 推荐:
|
||||
const object = {
|
||||
a,
|
||||
performAction() {
|
||||
// ...
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### 字符串
|
||||
|
||||
字符串字面量应使用单引号声明,除非字符串本身包含需要转义的单引号——此时应使用双引号。如果字符串同时包含单引号和双引号,可使用ES6模板字符串来避免转义引号。
|
||||
|
||||
**注意:** 在面向用户的字符串中,切勿将单引号字符(`'`)用作撇号(`’`)(如`it’s`或`haven’t`)。在测试代码中仍鼓励使用真正的撇号。
|
||||
|
||||
通常应避免使用反斜杠转义引号:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// 不推荐:
|
||||
const name = "Matt";
|
||||
// 推荐:
|
||||
const name = 'Matt';
|
||||
|
||||
// 不推荐:
|
||||
const pet = "Matt's dog";
|
||||
// 同样不推荐(未使用撇号):
|
||||
const pet = "Matt's dog";
|
||||
// 推荐:
|
||||
const pet = 'Matt’s dog';
|
||||
// 同样推荐:
|
||||
const oddString = "She said 'This is odd.'";
|
||||
```
|
||||
|
||||
应尽可能使用ES6模板字符串替代字符串拼接:
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
const name = 'Stacey';
|
||||
|
||||
// 不推荐:
|
||||
alert( 'My name is ' + name + '.' );
|
||||
// 推荐:
|
||||
alert( `My name is ${ name }.` );
|
||||
```
|
||||
|
||||
### 可选链
|
||||
|
||||
[可选链](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)是ECMAScript 2020版本引入的新语言特性。虽然该特性对于可能为空值(`null`或`undefined`)的对象属性访问非常方便,但在使用可选链时需要注意一些常见陷阱。未来或许可以通过代码检查或类型检查来避免这些问题。目前需要警惕以下情况:
|
||||
|
||||
- 当对通过可选链求值的值进行取反(`!`)操作时,需注意:如果可选链执行到无法继续的位置,会产生一个[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),该值取反后会转换为`true`。这在多数情况下不符合预期。
|
||||
- 示例:`const hasFocus = ! nodeRef.current?.contains( document.activeElement );` 在`nodeRef.current`未赋值时将返回`true`
|
||||
- 相关issue:[#21984](https://github.com/WordPress/gutenberg/issues/21984)
|
||||
- 类似ESLint规则:[`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation)
|
||||
- 当赋值布尔值时,注意可选链可能产生非严格`false`的[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)(`undefined`、`null`)。当该值以期望为布尔值(`true`或`false`)的方式传递时,可能引发问题。虽然布尔值常出现这种情况——因为布尔值通常用于考虑广义真值性的逻辑中——但当急切假设属性访问链末端的结果类型时,其他类型的可选链也可能出现此类问题。[类型检查](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#typescript)有助于预防这类错误。
|
||||
- 示例:`document.body.classList.toggle( 'has-focus', nodeRef.current?.contains( document.activeElement ) );` 可能错误地添加类,因为[第二个参数是可选的](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle)。如果传入`undefined`,将不会像传入`false`时那样取消设置该类
|
||||
- 示例:`<input value={ state.selected?.value.trim() } />` 可能因在[受控和非受控输入](https://reactjs.org/docs/uncontrolled-components.html)间切换而触发React警告。当急切假设`trim()`始终返回字符串值,却忽略了可选链可能导致求值提前中止并返回`undefined`时,很容易陷入此陷阱
|
||||
309
contributors/code/deprecations.md
Normal file
309
contributors/code/deprecations.md
Normal file
@@ -0,0 +1,309 @@
|
||||
## 3.2.0
|
||||
|
||||
- `wp.data.withRehydratation` 已重命名为 `wp.data.withRehydration`
|
||||
- `wp.editor.ImagePlaceholder` 组件已移除,请改用 `wp.editor.MediaPlaceholder`
|
||||
- `wp.utils.deprecated` 函数已移除,请改用 `wp.deprecated`
|
||||
- `wp.utils.blob` 已移除,请改用 `wp.blob`
|
||||
- `getInserterItems`:移除了 `allowedBlockTypes` 参数,新增了 `parentUID` 参数
|
||||
- `getFrecentInserterItems` 选择器已移除,请改用 `getInserterItems`
|
||||
- `getSupportedBlocks` 选择器已移除,请改用 `canInsertBlockType`
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- `wp.blocks.*` 中的所有组件已移除,请改用 `wp.editor.*`
|
||||
- `wp.blocks.withEditorSettings` 已移除,请使用数据模块访问编辑器设置 `wp.data.select( "core/editor" ).getEditorSettings()`
|
||||
- `wp.utils.*` 中的所有 DOM 工具已移除,请改用 `wp.dom.*`
|
||||
- 区块 API 中的 `isPrivate: true` 已移除,请改用 `supports.inserter: false`
|
||||
- `wp.utils.isExtraSmall` 函数已移除,请改用 `wp.viewport` 模块
|
||||
- `getEditedPostExcerpt` 选择器已移除(`core/editor`),请改用 `getEditedPostAttribute( 'excerpt' )`
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- `wp.blocks.registerCoreBlocks` 函数已移除,请改用 `wp.coreBlocks.registerCoreBlocks`
|
||||
- `RichText` 的原始 TinyMCE 事件处理程序已弃用,请改用[文档化属性](https://github.com/WordPress/gutenberg/blob/v3.0.0/editor/components/rich-text/README.md)、祖先事件处理程序,或通过 onSetup 访问内部编辑器实例事件中心
|
||||
|
||||
## 2.8.0
|
||||
|
||||
- `wp.components.Autocomplete` 中的原始自动补全接口已更新,请改用最新自动补全接口。更多信息请参阅[自动补全文档](https://github.com/WordPress/gutenberg/blob/v2.8.0/components/autocomplete/README.md)
|
||||
- `getInserterItems`:`allowedBlockTypes` 参数现为必填项
|
||||
- `getFrecentInserterItems`:`allowedBlockTypes` 参数现为必填项
|
||||
|
||||
## 2.7.0
|
||||
|
||||
- `wp.element.getWrapperDisplayName` 函数已移除,请改用 `wp.element.createHigherOrderComponent`
|
||||
|
||||
## 2.6.0
|
||||
|
||||
- `wp.blocks.getBlockDefaultClassname` 函数已移除,请改用 `wp.blocks.getBlockDefaultClassName`
|
||||
- `wp.blocks.Editable` 组件已移除,请改用 `wp.blocks.RichText` 组件
|
||||
|
||||
## 2.5.0
|
||||
|
||||
- 不再支持从区块 `save` 返回原始 HTML,请改用 `wp.element.RawHTML` 组件
|
||||
- `wp.data.query` 高阶组件已移除,请改用 `wp.data.withSelect`
|
||||
|
||||
## 2.4.0
|
||||
|
||||
- `wp.blocks.BlockDescription` 组件已移除,请改用 `description` 区块属性
|
||||
- `wp.blocks.InspectorControls.*` 组件已移除,请改用 `wp.components.*` 组件
|
||||
- `wp.blocks.source.*` 匹配器已移除,请改用声明式属性。更多信息请参阅[区块属性文档](/docs/reference-guides/block-api/block-attributes.md)
|
||||
- `wp.data.select( 'selector', ...args )` 已移除,请改用 `wp.data.select( reducerKey' ).*`
|
||||
- `wp.blocks.MediaUploadButton` 组件已移除,请改用 `wp.blocks.MediaUpload` 组件
|
||||
|
||||
```markdown
|
||||
## 3.6.0
|
||||
|
||||
- 已移除 `wp.editor.editorMediaUpload`,请改用 `wp.editor.mediaUpload`
|
||||
- 已移除 `wp.utils.getMimeTypesArray`
|
||||
- 已移除 `wp.utils.mediaUpload`,请改用 `wp.editor.mediaUpload`
|
||||
- 已移除 `wp.utils.preloadImage`
|
||||
- 区块 API 中的 `supports.wideAlign` 已移除,请改用 `supports.alignWide`
|
||||
- 已移除 `wp.blocks.isSharedBlock`,请改用 `wp.blocks.isReusableBlock`
|
||||
- 已移除 fetchSharedBlocks 操作(`core/editor`),请改用 fetchReusableBlocks
|
||||
- 已移除 receiveSharedBlocks 操作(`core/editor`),请改用 receiveReusableBlocks
|
||||
- 已移除 saveSharedBlock 操作(`core/editor`),请改用 saveReusableBlock
|
||||
- 已移除 deleteSharedBlock 操作(`core/editor`),请改用 deleteReusableBlock
|
||||
- 已移除 updateSharedBlockTitle 操作(`core/editor`),请改用 updateReusableBlockTitle
|
||||
- 已移除 convertBlockToSaved 操作(`core/editor`),请改用 convertBlockToReusable
|
||||
- 已移除 getSharedBlock 选择器(`core/editor`),请改用 getReusableBlock
|
||||
- 已移除 isSavingSharedBlock 选择器(`core/editor`),请改用 isSavingReusableBlock
|
||||
- 已移除 isFetchingSharedBlock 选择器(`core/editor`),请改用 isFetchingReusableBlock
|
||||
- 已移除 getSharedBlocks 选择器(`core/editor`),请改用 getReusableBlocks
|
||||
|
||||
## 3.5.0
|
||||
|
||||
- 已移除 `wp.components.ifCondition`,请改用 `wp.compose.ifCondition`
|
||||
- 已移除 `wp.components.withGlobalEvents`,请改用 `wp.compose.withGlobalEvents`
|
||||
- 已移除 `wp.components.withInstanceId`,请改用 `wp.compose.withInstanceId`
|
||||
- 已移除 `wp.components.withSafeTimeout`,请改用 `wp.compose.withSafeTimeout`
|
||||
- 已移除 `wp.components.withState`,请改用 `wp.compose.withState`
|
||||
- 已移除 `wp.element.pure`,请改用 `wp.compose.pure`
|
||||
- 已移除 `wp.element.compose`,请改用 `wp.compose.compose`
|
||||
- 已移除 `wp.element.createHigherOrderComponent`,请改用 `wp.compose.createHigherOrderComponent`
|
||||
- 已移除 `wp.utils.buildTermsTree`
|
||||
- 已移除 `wp.utils.decodeEntities`,请改用 `wp.htmlEntities.decodeEntities`
|
||||
- 所有区块 `uid` 的引用已替换为等效的 `clientId` 属性和选择器
|
||||
- `wp.editor.MediaPlaceholder` 组件的 `onSelectUrl` 属性已重命名为 `onSelectURL`
|
||||
- `wp.editor.UrlInput` 组件已重命名为 `wp.editor.URLInput`
|
||||
- 已移除文本列区块,请改用列区块
|
||||
- 已移除 `InnerBlocks` 分组布局,请改用中间嵌套内部区块。参考列/栏目块的实现方案
|
||||
- 已移除 `RichText` 显式 `element` 格式,请改用兼容的 `children` 格式
|
||||
|
||||
## 3.4.0
|
||||
|
||||
- `Popover` 组件中的 `focusOnMount` 属性已从仅布尔值改为枚举式属性,接受 `"firstElement"`、`"container"` 或 `false`。请将 `<Popover focusOnMount />` 用法转换为 `<Popover focusOnMount="firstElement" />`
|
||||
- 已移除 `wp.utils.keycodes` 工具,请改用 `wp.keycodes`
|
||||
- 已移除 `edit` 函数中的区块 `id` 属性,请改用区块 `clientId` 属性
|
||||
- 已移除 `property` 源,请改用等效的 `text`、`html` 或 `attribute` 源,或注释属性
|
||||
|
||||
## 3.3.0
|
||||
|
||||
- 已从区块 API 中移除 `useOnce: true`,请改用 `supports.multiple: false`
|
||||
- 使用 `componentWillMount` 生命周期方法序列化组件,请改用构造函数
|
||||
- 已移除 `blocks.Autocomplete.completers` 过滤器,请改用 `editor.Autocomplete.completers`
|
||||
- 已移除 `blocks.BlockEdit` 过滤器,请改用 `editor.BlockEdit`
|
||||
- 已移除 `blocks.BlockListBlock` 过滤器,请改用 `editor.BlockListBlock`
|
||||
- 已移除 `blocks.MediaUpload` 过滤器,请改用 `editor.MediaUpload`
|
||||
```
|
||||
|
||||
- PHP 函数 `gutenberg_show_privacy_policy_help_text` 已被移除。
|
||||
- PHP 函数 `gutenberg_common_scripts_and_styles` 已被移除。请改用 [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/)。
|
||||
- PHP 函数 `gutenberg_enqueue_registered_block_scripts_and_styles` 已被移除。请改用 [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/)。
|
||||
- PHP 函数 `gutenberg_meta_box_save` 已被移除。
|
||||
- PHP 函数 `gutenberg_meta_box_save_redirect` 已被移除。
|
||||
- PHP 函数 `gutenberg_filter_meta_boxes` 已被移除。
|
||||
- PHP 函数 `gutenberg_intercept_meta_box_render` 已被移除。
|
||||
- PHP 函数 `gutenberg_override_meta_box_callback` 已被移除。
|
||||
- PHP 函数 `gutenberg_show_meta_box_warning` 已被移除。
|
||||
- PHP 函数 `the_gutenberg_metaboxes` 已被移除。请改用 [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/)。
|
||||
- PHP 函数 `gutenberg_meta_box_post_form_hidden_fields` 已被移除。请改用 [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/)。
|
||||
- PHP 函数 `gutenberg_toggle_custom_fields` 已被移除。
|
||||
- PHP 函数 `gutenberg_collect_meta_box_data` 已被移除。请改用 [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/)。
|
||||
- `window._wpLoadGutenbergEditor` 已被移除。请改用 `window._wpLoadBlockEditor`。注意:这是一个私有 API,不供公共使用,未来可能会被移除。
|
||||
- PHP 函数 `gutenberg_get_script_polyfill` 已被移除。请改用 [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/)。
|
||||
- PHP 函数 `gutenberg_add_admin_body_class` 已被移除。如果需要将样式限定在块编辑器界面,请在样式表中使用 `.block-editor-page` 类选择器。
|
||||
|
||||
## 4.5.0
|
||||
|
||||
- `Dropdown.refresh()` 已被弃用,因为其包含的 `Popover` 现在会自动刷新。
|
||||
- `wp.editor.PostPublishPanelToggle` 已被弃用,请改用 `wp.editor.PostPublishButton`。
|
||||
|
||||
## 4.4.0
|
||||
|
||||
- `wp.date.getSettings` 已被移除。请改用 `wp.date.__experimentalGetSettings`。
|
||||
- `wp.compose.remountOnPropChange` 已被移除。
|
||||
- 以下编辑器存储操作已被移除:`createNotice`、`removeNotice`、`createSuccessNotice`、`createInfoNotice`、`createErrorNotice`、`createWarningNotice`。请使用 `@wordpress/notices` 模块中同名的等效操作。
|
||||
- `wp.nux.DotTip` 的 `id` 属性已被移除。请改用 `tipId` 属性。
|
||||
- `wp.blocks.isValidBlock` 已被移除。请改用 `wp.blocks.isValidBlockContent`,但请注意参数顺序已更改。
|
||||
- `wp.data` 的 `registry.registerReducer` 已被弃用。请改用 `registry.registerStore`。
|
||||
- `wp.data` 的 `registry.registerSelectors` 已被弃用。请改用 `registry.registerStore`。
|
||||
- `wp.data` 的 `registry.registerActions` 已被弃用。请改用 `registry.registerStore`。
|
||||
- `wp.data` 的 `registry.registerResolvers` 已被弃用。请改用 `registry.registerStore`。
|
||||
- `moment` 已从日期模块的公共 API 中移除。
|
||||
|
||||
## 4.3.0
|
||||
|
||||
- `isEditorSidebarPanelOpened` 选择器(`core/edit-post`)已被移除。请改用 `isEditorPanelEnabled`。
|
||||
- `toggleGeneralSidebarEditorPanel` 操作(`core/edit-post`)已被移除。请改用 `toggleEditorPanelOpened`。
|
||||
- `wp.components.PanelColor` 组件已被移除。请改用 `wp.editor.PanelColorSettings`。
|
||||
- `wp.editor.PanelColor` 组件已被移除。请改用 `wp.editor.PanelColorSettings`。
|
||||
|
||||
## 4.2.0
|
||||
|
||||
- 已将异步生成器形式的解析器移除,请改用 controls 插件
|
||||
- `wp.components.AccessibleSVG` 组件已移除,请改用 `wp.components.SVG`
|
||||
- `wp.editor.UnsavedChangesWarning` 组件不再接受 `forceIsDirty` 属性
|
||||
- `setActiveMetaBoxLocations` 操作(`core/edit-post`)已移除
|
||||
- `initializeMetaBoxState` 操作(`core/edit-post`)已移除
|
||||
- `wp.editPost.initializeEditor` 不再返回对象,请使用 `setActiveMetaBoxLocations` 操作(`core/edit-post`)替代原对象的 `initializeMetaBoxes` 函数
|
||||
- `setMetaBoxSavedData` 操作(`core/edit-post`)已移除
|
||||
- `getMetaBoxes` 选择器(`core/edit-post`)已移除,请改用 `getActiveMetaBoxLocations` 选择器(`core/edit-post`)
|
||||
- `getMetaBox` 选择器(`core/edit-post`)已移除,请改用 `isMetaBoxLocationActive` 选择器(`core/edit-post`)
|
||||
- 属性类型强制转换已移除,需省略源以通过序列化注释标记保留类型
|
||||
- `wp.editor.mediaUpload` 的 `onFileChange` 回调所传对象中的 `mediaDetails` 已移除,请改用 `media_details` 属性
|
||||
- `wp.components.CodeEditor` 已移除,请直接使用 `wp.codeEditor`
|
||||
- `wp.blocks.setUnknownTypeHandlerName` 已移除,请改用 `setFreeformContentHandlerName` 和 `setUnregisteredTypeHandlerName`
|
||||
- `wp.blocks.getUnknownTypeHandlerName` 已移除,请改用 `getFreeformContentHandlerName` 和 `getUnregisteredTypeHandlerName`
|
||||
- 可重用块数据 API 标记为实验性功能,未来可能变更
|
||||
|
||||
## 4.1.0
|
||||
|
||||
- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` 已移除,区块重置时会自动进行有效性验证
|
||||
|
||||
## 4.0.0
|
||||
|
||||
- `wp.editor.RichTextProvider` 已移除,请改用 `wp.data.select( 'core/editor' )` 方法
|
||||
- `wp.components.Draggable` 作为 DOM 节点拖拽处理器的功能已移除,请将 `wp.components.Draggable` 作为包装组件用于 DOM 节点拖拽处理器
|
||||
- `wp.i18n.getI18n` 已移除,请改用 `__`、`_x`、`_n` 或 `_nx`
|
||||
- `wp.i18n.dcnpgettext` 已移除,请改用 `__`、`_x`、`_n` 或 `_nx`
|
||||
|
||||
## 3.9.0
|
||||
|
||||
- RichText 的 `getSettings` 属性已移除,如需继续使用请改用 `unstableGetSettings` 属性(强烈不建议使用不稳定 API,此类 API 可能随时被移除)
|
||||
- RichText 的 `onSetup` 属性已移除,如需继续使用请改用 `unstableOnSetup` 属性(强烈不建议使用不稳定 API,此类 API 可能随时被移除)
|
||||
- `wp.editor.getColorName` 已移除,请改用 `wp.editor.getColorObjectByColorValue`
|
||||
- `wp.editor.getColorClass` 已重命名,请改用 `wp.editor.getColorClassName`
|
||||
- `wp.editor.withColors` 所传颜色对象中的 `value` 属性已移除,请改用 color 属性
|
||||
- 子标题区块已移除,请改用段落区块
|
||||
- `wp.blocks.getDefaultBlockForPostFormat` 已移除
|
||||
|
||||
## 3.8.0
|
||||
|
||||
- `wp.components.withContext` 已移除,请改用 `wp.element.createContext`,参见:https://react.dev/reference/react/createContext
|
||||
- `wp.coreBlocks.registerCoreBlocks` 已移除,请改用 `wp.blockLibrary.registerCoreBlocks`
|
||||
- `wp.editor.DocumentTitle` 组件已移除
|
||||
- `getDocumentTitle` 选择器(`core/editor`)已移除
|
||||
|
||||
## 3.7.0
|
||||
|
||||
- `wp.components.withAPIData` 已移除,请直接使用核心数据模块或 `wp.apiFetch`
|
||||
- `wp.data.dispatch("core").receiveTerms` 已弃用,请改用 `wp.data.dispatch("core").receiveEntityRecords`
|
||||
- `getCategories` 解析器已弃用,请改用 `getEntityRecords` 解析器
|
||||
- `wp.data.select("core").getTerms` 已弃用,请改用 `wp.data.select("core").getEntityRecords`
|
||||
- `wp.data.select("core").getCategories` 已弃用,请改用 `wp.data.select("core").getEntityRecords`
|
||||
- `wp.data.select("core").isRequestingCategories` 已弃用,请改用 `wp.data.select("core/data").isResolving`
|
||||
- `wp.data.select("core").isRequestingTerms` 已弃用,请改用 `wp.data.select("core").isResolving`
|
||||
- `wp.data.restrictPersistence`、`wp.data.setPersistenceStorage` 和 `wp.data.setupPersistence` 已移除,请改用数据持久化插件
|
||||
|
||||
# 已弃用功能
|
||||
|
||||
对于Gutenberg插件中包含的功能,弃用政策旨在尽可能支持两个次要插件版本的向后兼容性。WordPress稳定版本中包含的功能和代码不在此弃用时间线内,而是遵循[WordPress项目的版本管理政策](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/)。当前已弃用的功能如下所示,并按*将完全移除这些功能的版本*进行分组。如果您的插件依赖这些行为,则必须在指定版本之前更新至推荐的替代方案。
|
||||
|
||||
## 未发布版本
|
||||
|
||||
- `wp.blocks.isValidBlockContent` 已被移除。请改用 `wp.blocks.validateBlock`。
|
||||
|
||||
## 11.0.0
|
||||
|
||||
- `wp.blocks.registerBlockTypeFromMetadata` 方法已被移除。请改用 `wp.blocks.registerBlockType` 方法。
|
||||
|
||||
## 10.3.0
|
||||
|
||||
- 不再支持向 `ActionItem.Slot` 组件传递带有 `as` 属性的组件元组。请改为传递带有 `as` 属性的组件。示例:
|
||||
```diff
|
||||
<ActionItem.Slot
|
||||
名称="my/slot"
|
||||
标签={ __( '我的插槽' ) }
|
||||
- 组件={ [ MenuGroup, MenuItem ] }
|
||||
+ 组件={ MenuGroup }
|
||||
/>
|
||||
```
|
||||
|
||||
## 9.7.0
|
||||
|
||||
- `InterfaceSkeleton` 组件中的 `leftSidebar` 属性已被移除。请改用 `secondarySidebar` 属性。
|
||||
|
||||
## 8.6.0
|
||||
|
||||
- 更新了与[区块上下文](/docs/reference-guides/block-api/block-context.md)的区块API集成。注册区块时,请在JavaScript文件中使用 `usesContext` 和 `providesContext` 对,在PHP文件中使用 `uses_context` 和 `provides_context` 对,而不是之前的 `context` 和 `providesContext` 对。
|
||||
|
||||
## 8.3.0
|
||||
|
||||
- PHP函数 `gutenberg_get_post_from_context` 已被移除。请改用[区块上下文](/docs/reference-guides/block-api/block-context.md)。
|
||||
- 旧的区块模式API `register_pattern`/`unregister_pattern` 已被移除。请改用[新函数](/docs/reference-guides/block-api/block-patterns.md#register_block_pattern)。
|
||||
|
||||
## 5.5.0
|
||||
|
||||
- PHP函数 `gutenberg_init` 已被移除。
|
||||
- PHP函数 `is_gutenberg_page` 已被移除。请改用 [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/)。
|
||||
- PHP函数 `the_gutenberg_project` 已被移除。
|
||||
- PHP函数 `gutenberg_default_post_format_template` 已被移除。
|
||||
- PHP函数 `gutenberg_get_available_image_sizes` 已被移除。
|
||||
- PHP函数 `gutenberg_get_autosave_newer_than_post_save` 已被移除。
|
||||
- PHP函数 `gutenberg_editor_scripts_and_styles` 已被移除。
|
||||
|
||||
## 5.4.0
|
||||
|
||||
- PHP函数 `gutenberg_load_plugin_textdomain` 已被移除。
|
||||
- PHP函数 `gutenberg_get_jed_locale_data` 已被移除。
|
||||
- PHP函数 `gutenberg_load_locale_data` 已被移除。
|
||||
|
||||
## 5.3.0
|
||||
|
||||
- PHP函数 `gutenberg_redirect_to_classic_editor_when_saving_posts` 已被移除。
|
||||
- PHP函数 `gutenberg_revisions_link_to_editor` 已被移除。
|
||||
- PHP函数 `gutenberg_remember_classic_editor_when_saving_posts` 已被移除。
|
||||
- PHP函数 `gutenberg_can_edit_post_type` 已被移除。请改用 [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/)。
|
||||
- PHP函数 `gutenberg_can_edit_post` 已被移除。请改用 [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/)。
|
||||
|
||||
## 5.2.0
|
||||
|
||||
- PHP函数 `gutenberg_parse_blocks` 已被移除。请改用 [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/)。
|
||||
- PHP函数 `get_dynamic_blocks_regex` 已被移除。
|
||||
- PHP函数 `gutenberg_render_block` 已被移除。请改用 [`render_block`](https://developer.wordpress.org/reference/functions/render_block/)。
|
||||
- PHP函数 `strip_dynamic_blocks` 已被移除。如需用于摘要准备,请考虑改用 [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/)。
|
||||
- PHP函数 `strip_dynamic_blocks_add_filter` 已被移除。
|
||||
- PHP函数 `strip_dynamic_blocks_remove_filter` 已被移除。
|
||||
- PHP函数 `gutenberg_post_has_blocks` 已被移除。请改用 [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/)。
|
||||
- PHP函数 `gutenberg_content_has_blocks` 已被移除。请改用 [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/)。
|
||||
- PHP函数 `gutenberg_register_rest_routes` 已被移除。
|
||||
- PHP函数 `gutenberg_add_taxonomy_visibility_field` 已被移除。
|
||||
- PHP函数 `gutenberg_get_taxonomy_visibility_data` 已被移除。
|
||||
- PHP函数 `gutenberg_add_permalink_template_to_posts` 已被移除。
|
||||
- PHP函数 `gutenberg_add_block_format_to_post_content` 已被移除。
|
||||
- PHP函数 `gutenberg_add_target_schema_to_links` 已被移除。
|
||||
- PHP函数 `gutenberg_register_post_prepare_functions` 已被移除。
|
||||
- PHP函数 `gutenberg_silence_rest_errors` 已被移除。
|
||||
- PHP函数 `gutenberg_filter_post_type_labels` 已被移除。
|
||||
- PHP函数 `gutenberg_preload_api_request` 已被移除。请改用 [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/)。
|
||||
- PHP函数 `gutenberg_remove_wpcom_markdown_support` 已被移除。
|
||||
- PHP函数 `gutenberg_add_gutenberg_post_state` 已被移除。
|
||||
- PHP函数 `gutenberg_bulk_post_updated_messages` 已被移除。
|
||||
- PHP函数 `gutenberg_kses_allowedtags` 已被移除。
|
||||
- PHP函数 `gutenberg_add_responsive_body_class` 已被移除。
|
||||
- PHP函数 `gutenberg_add_edit_link_filters` 已被移除。
|
||||
- PHP函数 `gutenberg_add_edit_link` 已被移除。
|
||||
- PHP函数 `gutenberg_block_bulk_actions` 已被移除。
|
||||
- PHP函数 `gutenberg_replace_default_add_new_button` 已被移除。
|
||||
- PHP函数 `gutenberg_content_block_version` 已被移除。请改用 [`block_version`](https://developer.wordpress.org/reference/functions/block_version/)。
|
||||
- PHP函数 `gutenberg_get_block_categories` 已被移除。请改用 [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/)。
|
||||
- PHP函数 `register_tinymce_scripts` 已被移除。请改用 [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/)。
|
||||
- PHP函数 `gutenberg_register_post_types` 已被移除。
|
||||
- `gutenberg` 主题支持选项已被移除。请改用 [`align-wide`](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#wide-alignment)。
|
||||
- PHP函数 `gutenberg_prepare_blocks_for_js` 已被移除。请改用 [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/)。
|
||||
- PHP函数 `gutenberg_load_list_reusable_blocks` 已被移除。
|
||||
- PHP函数 `_gutenberg_utf8_split` 已被移除。请改用 `_mb_substr`。
|
||||
- PHP函数 `gutenberg_disable_editor_settings_wpautop` 已被移除。
|
||||
- PHP函数 `gutenberg_add_rest_nonce_to_heartbeat_response_headers` 已被移除。
|
||||
- PHP函数 `gutenberg_check_if_classic_needs_warning_about_blocks` 已被移除。
|
||||
- PHP函数 `gutenberg_warn_classic_about_blocks` 已被移除。
|
||||
117
contributors/code/e2e/README.md
Normal file
117
contributors/code/e2e/README.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# 端到端测试
|
||||
|
||||
本文档旨在为 Gutenberg 项目中如何使用 Playwright 编写端到端测试提供操作指南和最佳实践。
|
||||
|
||||
<div class="callout callout-info">
|
||||
|
||||
若您正在使用旧版 Jest + Puppeteer 框架,请参阅专用指南。若需从 Jest + Puppeteer 迁移测试,请查阅<a href="https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/migration.md">迁移指南</a>。
|
||||
|
||||
</div>
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有可用测试
|
||||
npm run test:e2e
|
||||
|
||||
# 以图形界面模式运行
|
||||
npm run test:e2e -- --headed
|
||||
|
||||
# 在特定浏览器中运行测试(支持 `chromium`、`firefox` 或 `webkit`)
|
||||
npm run test:e2e -- --project=webkit --project=firefox
|
||||
|
||||
# 运行单个测试文件
|
||||
npm run test:e2e -- <测试文件路径> # 例如:npm run test:e2e -- site-editor/title.spec.js
|
||||
|
||||
# 调试模式
|
||||
npm run test:e2e -- --debug
|
||||
```
|
||||
|
||||
若您在 Linux 环境下开发,目前需以图形界面模式测试 Webkit 浏览器。若不愿或无法使用图形界面运行(例如无图形界面环境),可在命令前添加 [`xvfb-run`](https://manpages.ubuntu.com/manpages/xenial/man1/xvfb-run.1.html) 在虚拟环境中运行:
|
||||
|
||||
```bash
|
||||
# 运行所有可用测试
|
||||
xvfb-run npm run test:e2e
|
||||
|
||||
# 仅运行 webkit 测试
|
||||
xvfb-run -- npm run test:e2e -- --project=webkit
|
||||
```
|
||||
|
||||
若您正在使用 VS Code 进行编辑,可安装 [Playwright 扩展插件](https://playwright.dev/docs/getting-started-vscode)来辅助运行、编写和调试测试。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
请阅读 Playwright 的[最佳实践指南](https://playwright.dev/docs/best-practices)。
|
||||
|
||||
### 禁止使用 `$`,改用 `locator`
|
||||
|
||||
实际上,任何返回 `ElementHandle` 的 API 均[不推荐使用](https://playwright.dev/docs/api/class-page#page-query-selector),包括 `$`、`$$`、`$eval`、`$$eval` 等。[`Locator`](https://playwright.dev/docs/api/class-locator) 是更优秀的 API,可与 Playwright 的[断言功能](https://playwright.dev/docs/api/class-locatorassertions)配合使用。由于定位器采用惰性求值且不返回 Promise,该特性在页面对象模型中表现尤为出色。
|
||||
|
||||
### 使用可访问性选择器
|
||||
|
||||
尽可能使用 [`getByRole`](https://playwright.dev/docs/locators#locate-by-role) 构建查询。这样既能编写无障碍查询,又无需依赖内部实现细节:
|
||||
|
||||
```js
|
||||
// 选择包含无障碍名称"Hello World"的按钮(不区分大小写)
|
||||
page.getByRole( 'button', { name: 'Hello World' } );
|
||||
```
|
||||
|
||||
还支持链式调用以执行复杂查询:
|
||||
|
||||
```js
|
||||
// 在"区块库"区域下选择名称为"按钮"的选项
|
||||
page.getByRole( 'region', { name: 'Block Library' } )
|
||||
.getByRole( 'option', { name: 'Buttons' } )
|
||||
```
|
||||
|
||||
更多使用技巧请参阅[官方文档](https://playwright.dev/docs/locators)。
|
||||
|
||||
### 选择器默认采用严格模式
|
||||
|
||||
为提升元素查询的最佳实践,选择器默认启用[严格模式](https://playwright.dev/docs/api/class-browser#browser-new-page-option-strict-selectors),当查询结果包含多个元素时将抛出错误。
|
||||
|
||||
### 避免过度封装测试工具,简单工具应内联实现
|
||||
|
||||
`e2e-test-utils` 因包含过多工具函数而变得臃肿。其中大部分函数足够简单,可直接在测试中内联实现。借助可访问性选择器,现在编写简单工具函数更加容易。对于特定页面的工具函数,建议采用页面对象模型(但使用 `requestUtils` 清理状态的情况除外,这类工具更适合放在 `e2e-test-utils` 中)。仅当操作足够复杂且需要重复使用时,才考虑创建工具函数。
|
||||
|
||||
### 优先采用页面对象模型而非工具函数
|
||||
|
||||
如前所述,[页面对象模型](https://playwright.dev/docs/pom)是在特定页面创建可复用工具函数的首选方案。
|
||||
|
||||
使用页面对象模型的核心优势在于能将工具函数按命名空间分组,更便于发现和使用。实际上,`e2e-test-utils-playwright` 包中的 `PageUtils` 本身就是页面对象模型,既避免了全局变量的使用,又支持通过 `this` 实现工具函数间的相互调用。
|
||||
|
||||
### 通过 REST 接口清理或设置状态
|
||||
|
||||
在测试前后手动设置状态效率低下,特别是在测试间需要重复设置时。推荐通过 API 调用来设置状态。请使用 `requestUtils.rest` 和 `requestUtils.batchRest` 调用 [REST API](https://developer.wordpress.org/rest-api/reference/)(如有需要可将其添加到 `requestUtils` 中)。我们仍需为手动设置状态编写测试,但此类测试只需编写一次。
|
||||
|
||||
### 避免使用全局变量
|
||||
|
||||
在之前的 Jest + Puppeteer 端到端测试中,`page` 和 `browser` 被暴露为全局变量。当同一测试中涉及多个页面/标签页,或需要并行运行多个测试时,这种方式会使工作变得困难。`@playwright/test` 引入了[测试固件](https://playwright.dev/docs/test-fixtures)概念,允许我们将 `page`、`browser` 及其他参数注入到测试中。
|
||||
|
||||
### 使用显式断言
|
||||
|
||||
单个测试中可根据需要插入多个断言。尽可能使用显式断言是更佳实践。例如,若需在点击按钮前确认其存在,可在执行 `locator.click()` 前使用 `expect( locator ).toBeVisible()`。这样能使测试流程更清晰易读。
|
||||
|
||||
## 常见误区
|
||||
|
||||
### [过度使用快照](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/overusing-snapshots.md)
|
||||
|
||||
## 跨浏览器测试
|
||||
|
||||
默认情况下测试仅在 Chromium 中运行。您可以通过给测试添加标签来指定运行浏览器。在测试标题任意位置使用 `@browser` 即可在对应浏览器中运行测试。默认情况下测试始终会在 Chromium 中运行,可通过添加 `-chromium` 后缀禁用 Chromium 测试。支持的浏览器包括 `chromium`、`firefox` 和 `webkit`。
|
||||
|
||||
```js
|
||||
test( '我将在 @firefox 和 @webkit 中运行(默认包含 chromium)', async ( { page } ) => {
|
||||
// ...
|
||||
} );
|
||||
|
||||
test( '我仅在 @firefox 中运行(排除 -chromium)', async ( { page } ) => {
|
||||
// ...
|
||||
} );
|
||||
|
||||
test.describe( '测试分组 (@webkit, -chromium)', () => {
|
||||
test( '我仅在 webkit 中运行', async ( { page } ) => {
|
||||
// ...
|
||||
} );
|
||||
} );
|
||||
```
|
||||
37
contributors/code/e2e/migration.md
Normal file
37
contributors/code/e2e/migration.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 迁移指南
|
||||
|
||||
本文档概述了将 Jest + Puppeteer 测试迁移至 Playwright 的典型流程。请注意,迁移过程也是重构或重写部分测试的良好契机。开始迁移前,请先阅读[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)指南。
|
||||
|
||||
## 测试迁移步骤
|
||||
|
||||
1. 在 `packages/e2e-tests/specs` 中选择要迁移的测试套件,将 `.test.js` 重命名为 `.spec.js`,并置于 `test/e2e/specs` 内的相同文件夹结构中。
|
||||
2. 从 `@wordpress/e2e-test-utils-playwright` 引入测试辅助工具:
|
||||
```js
|
||||
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
|
||||
```
|
||||
3. 将所有出现的 `describe`、`beforeAll`、`beforeEach`、`afterEach` 和 `afterAll` 加上 `test.` 前缀。例如,`describe` 变为 `test.describe`。
|
||||
4. 使用[夹具 API](https://playwright.dev/docs/test-fixtures) 引入之前全局的变量,如 `page` 和 `browser`。
|
||||
5. 删除所有对 `e2e-test-utils` 的导入。改为使用夹具 API 直接获取 `admin`、`editor`、`pageUtils` 和 `requestUtils`。(注意:`admin`、`editor` 和 `pageUtils` 不能在 `beforeAll` 和 `afterAll` 中使用,需改用 `requestUtils` 重写。)
|
||||
6. 如果缺少某个工具函数,且步骤较少,可尝试直接在测试中内联实现操作。如果认为有必要作为测试工具函数实现,请参考以下[指南](#测试工具函数迁移步骤)。
|
||||
7. 根据推荐的[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)手动迁移测试中的其他细节。请注意,尽管 Playwright 和 Puppeteer 的 API 差异较小,但仍需进行一些手动调整。
|
||||
|
||||
## 测试工具函数迁移步骤
|
||||
|
||||
在迁移测试工具函数前,请仔细考虑是否必要。Playwright 提供了许多易读且强大的 API,使得许多工具函数不再需要。可先尝试在测试中直接内联实现相同功能。仅当此方法不适用时,再参考以下指南。适合在 `e2e-test-utils-playwright` 包中实现的工具函数示例包括复杂的浏览器 API(如 `pageUtils.dragFiles` 和 `pageUtils.pressKeys`)以及状态设置 API(如 `requestUtils.*`)。
|
||||
|
||||
<div class="callout callout-info">
|
||||
<code>e2e-test-utils-playwright</code> 包并非旨在完全替代 Jest + Puppeteer 的 <code>e2e-test-utils</code> 包。部分工具函数仅为简化迁移过程而创建,并非必需。
|
||||
</div>
|
||||
|
||||
Playwright 工具函数的组织方式与 `e2e-test-utils` 包略有不同。`e2e-test-utils-playwright` 包将工具函数分为以下文件夹:
|
||||
- `admin` - 与 WordPress 后台或 WordPress 后台用户界面相关的工具函数(例如 `visitAdminPage`)。
|
||||
- `editor` - 用于块编辑器的工具函数(例如 `clickBlockToolbarButton`)。
|
||||
- `pageUtils` - 用于与浏览器交互的通用工具函数(例如 `pressKeys`)。
|
||||
- `requestUtils` - 用于发起 REST API 请求的工具函数(例如 `activatePlugin`)。这些工具函数用于测试的初始化和清理。
|
||||
|
||||
1. 复制 `e2e-test-utils` 中的现有文件,根据工具函数类型粘贴到 `e2e-test-utils-playwright` 的 `admin`、`editor`、`page` 或 `request` 文件夹中。
|
||||
2. 更新工具函数中需要重写的部分:
|
||||
- `page` 和 `browser` 变量在 `admin`、`editor` 和 `pageUtils` 中可通过 `this.page` 和 `this.browser` 访问。
|
||||
- 同一类中的所有其他工具函数可通过 `this` 访问,并绑定到同一实例。可移除所有 `import` 语句,改用 `this` 访问。
|
||||
- 如果熟悉 TypeScript,可考虑将工具函数更新为 TypeScript 版本。
|
||||
3. 在 `index.ts` 中导入新迁移的工具函数,并将其作为实例字段放入 `Admin`/`Editor`/`PageUtils`/`RequestUtils` 类中。
|
||||
125
contributors/code/e2e/overusing-snapshots.md
Normal file
125
contributors/code/e2e/overusing-snapshots.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# 过度使用快照测试
|
||||
|
||||
看看下面的代码。你第一眼能理解这个测试想要做什么吗?
|
||||
|
||||
```js
|
||||
await editor.insertBlock( { name: 'core/quote' } );
|
||||
await page.keyboard.type( '1' );
|
||||
await page.keyboard.press( 'Enter' );
|
||||
await page.keyboard.press( 'Enter' );
|
||||
|
||||
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
|
||||
|
||||
await page.keyboard.press( 'Backspace' );
|
||||
await page.keyboard.type( '2' );
|
||||
|
||||
expect( await editor.getEditedPostContent() ).toMatchSnapshot();
|
||||
```
|
||||
|
||||
这段代码改编自Gutenberg项目中的真实代码,移除了测试标题和注释,并重构为Playwright版本。理想情况下,端到端测试应该具备自文档化和最终用户可读性;毕竟它们试图模拟最终用户与应用程序的交互方式。然而,这段代码中存在几个值得警惕的问题。
|
||||
|
||||
## 快照测试存在的问题
|
||||
|
||||
由Jest推广的[快照测试](https://jestjs.io/docs/snapshot-testing)在恰当使用时是个很好的工具。但可能因为它功能强大,开发者经常过度使用。已有许多[文章](https://kentcdodds.com/blog/effective-snapshot-testing)讨论过这个问题。在这个具体案例中,快照测试未能清晰体现开发者的意图。如果不查看其他信息,我们无法明确断言的具体内容。这使得代码难以理解,给除原作者外的所有读者增加了认知负担。作为读者,我们不得不反复查看代码才能完全理解它们。增加的代码复杂性阻碍了贡献者根据需求修改测试的意愿。有时甚至会让原作者感到困惑,导致意外[提交错误的快照](https://github.com/WordPress/gutenberg/pull/42780#discussion_r949865612)。
|
||||
|
||||
以下是包含测试标题和注释的同一测试。现在你明白这些断言的实际含义了:
|
||||
|
||||
```js
|
||||
it( '可以在末尾拆分', async () => {
|
||||
// ...
|
||||
|
||||
// 期望在引用块外有空段落
|
||||
expect( await getEditedPostContent() ).toMatchSnapshot();
|
||||
|
||||
// ...
|
||||
|
||||
// 期望段落被合并到引用块中
|
||||
expect( await getEditedPostContent() ).toMatchSnapshot();
|
||||
} );
|
||||
```
|
||||
|
||||
开发者的意图现在稍微清晰了些,但仍感觉与测试本身脱节。你可能会想尝试[内联快照](https://jestjs.io/docs/snapshot-testing#inline-snapshots),这确实解决了需要在文件间跳转的问题,但它们仍然不具备自文档化特性也不够明确。我们可以做得更好。
|
||||
|
||||
## 解决方案
|
||||
|
||||
与其在注释中写断言说明,不如尝试直接明确地写出来。借助`editor.getBlocks`方法,我们可以将其重写为更简单和原子化的断言:
|
||||
|
||||
```js
|
||||
// ...
|
||||
|
||||
// 期望在引用块外有空段落
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [
|
||||
{
|
||||
name: 'core/quote',
|
||||
innerBlocks: [
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '1' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '' },
|
||||
}
|
||||
] );
|
||||
|
||||
// ...
|
||||
|
||||
// 期望段落被合并到引用块中
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [ {
|
||||
name: 'core/quote',
|
||||
innerBlocks: [
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '1' },
|
||||
},
|
||||
{
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '2' },
|
||||
},
|
||||
],
|
||||
} ] );
|
||||
```
|
||||
|
||||
这些断言更加可读和明确。你可以添加额外的断言或将现有断言拆分成多个,以突出它们的重要性。是否保留注释取决于你,但当代码本身已经足够可读时,通常可以省略注释。
|
||||
|
||||
## 快照测试的变体
|
||||
|
||||
由于Playwright缺乏内联快照功能,一些迁移的测试使用字符串断言(`toBe`)来模拟类似效果,避免创建大量快照文件:
|
||||
|
||||
```js
|
||||
expect( await editor.getEditedPostContent() ).toBe( `<!-- wp:paragraph -->
|
||||
<p>段落</p>
|
||||
<!-- /wp:paragraph -->` );
|
||||
```
|
||||
|
||||
我们可以将这种模式视为快照测试的变体,在编写时应遵循相同的规则。通常最好使用`editor.getBlocks`或其他方法重写它们,以进行明确断言:
|
||||
|
||||
```js
|
||||
await expect.poll( editor.getBlocks ).toMatchObject( [ {
|
||||
name: 'core/paragraph',
|
||||
attributes: { content: '段落' },
|
||||
} ] );
|
||||
```
|
||||
|
||||
## 测试覆盖率怎么办?
|
||||
|
||||
将明确断言与快照测试比较,我们确实在这个测试中损失了一些测试覆盖率。当我们需要断言块的完整序列化内容时,快照测试仍然有用。但幸运的是,集成测试中的一些测试已经对每个核心块的[完整内容](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/README.md)进行了断言。这些测试在Node.js中运行,比在Playwright中重复相同测试要快得多。在我的机器上运行273个测试用例仅需约5.7秒。这类测试在单元或集成级别效果很好,我们可以在不损失测试覆盖率的情况下更快地运行它们。
|
||||
|
||||
## 最佳实践
|
||||
|
||||
在端到端测试中很少需要快照测试,通常有更好的替代方案可以利用明确断言。在确实没有其他合适替代方案时,我们应遵循使用快照测试的最佳实践。
|
||||
|
||||
### 避免巨大的快照
|
||||
|
||||
巨大的快照难以阅读和审查。而且,当所有内容都重要时,实际上没有任何内容是重要的。巨大的快照会阻碍我们关注快照的重要部分。
|
||||
|
||||
### 避免重复的快照
|
||||
|
||||
如果你发现在同一个测试中创建了多个相似内容的快照,这可能表明你应该进行更原子化的断言。重新思考你想要测试什么,如果第一个快照只是第二个的参考,那么你真正需要的是快照之间的**差异**。将第一个结果存储在变量中,然后断言结果之间的差异。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [有效的快照测试 - Kent C. Dodds](https://kentcdodds.com/blog/effective-snapshot-testing)
|
||||
- [常见测试错误 - Kent C. Dodds](https://kentcdodds.com/blog/common-testing-mistakes)
|
||||
254
contributors/code/getting-started-with-code-contribution.md
Normal file
254
contributors/code/getting-started-with-code-contribution.md
Normal file
@@ -0,0 +1,254 @@
|
||||
# 代码贡献入门指南
|
||||
|
||||
以下指南将帮助您设置本地环境,以便为 Gutenberg 项目做出贡献。用于贡献代码的环境与用于扩展 WordPress 区块编辑器的环境存在显著重叠。您可以查阅[开发环境教程](/docs/getting-started/devenv/README.md)获取更多设置信息。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Node.js
|
||||
Gutenberg 是一个 JavaScript 项目,需要安装 [Node.js](https://nodejs.org/)。项目目前基于 Node.js v20 和 npm v10 构建。虽然我们尽力使用 Node.js 的 Active LTS 版本,但并非总能实现。更多详细信息请参考 [Node.js 发布计划](https://github.com/nodejs/Release#release-schedule)。
|
||||
|
||||
我们推荐使用 [Node Version Manager](https://github.com/nvm-sh/nvm) (nvm),这是在 macOS、Linux 和 Windows 10(使用 WSL2)上安装和管理 Node.js 的最简单方式。如需更多安装说明,请参阅[我们的开发工具指南](/docs/getting-started/devenv/README.md#development-tools)或 Node.js 官网。
|
||||
|
||||
- Git
|
||||
Gutenberg 使用 git 进行版本控制。请确保您的计算机上安装了最新版本的 git,并拥有 GitHub 账户。您可以阅读 [Git 工作流程](/docs/contributors/code/git-workflow.md)了解如何在 Gutenberg 中使用 git 和 GitHub。
|
||||
|
||||
- [推荐] Docker Desktop
|
||||
我们推荐使用 [wp-env 包](/packages/env/README.md)在本地设置 WordPress 环境。使用 `wp-env` 需要安装 Docker。更多详细信息请参阅[开发环境教程](/docs/getting-started/devenv/README.md)。
|
||||
> 注意:若要在 Windows 10 家庭版上安装 Docker,请按照 [Docker for Windows with WSL2 的安装说明](https://docs.docker.com/docker-for-windows/wsl/)操作。
|
||||
|
||||
作为 Docker 设置的替代方案,您可以使用 [Local](https://localwp.com/)、[WampServer](https://wampserver.aviatechno.net/) 或 [MAMP](https://www.mamp.info/),甚至可以使用远程服务器。
|
||||
|
||||
- GitHub CLI
|
||||
虽然不是必需,但 [GitHub CLI](https://cli.github.com/) 能极大帮助您在本地检出拉取请求,包括来自 Gutenberg 主仓库和分叉仓库的请求。这在代码审查和测试拉取请求时能显著节省时间。
|
||||
|
||||
## 获取 Gutenberg 代码
|
||||
|
||||
请先 Fork Gutenberg 仓库,然后克隆到您的计算机,并将 WordPress 仓库添加为上游源。
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/您的GitHub用户名/gutenberg.git
|
||||
$ cd gutenberg
|
||||
$ git remote add upstream https://github.com/WordPress/gutenberg.git
|
||||
```
|
||||
|
||||
## 将 Gutenberg 构建为插件
|
||||
|
||||
安装 Gutenberg 依赖项并以开发模式构建代码:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
> 注意:安装脚本要求系统已安装 [Python](https://www.python.org/) 并配置在环境变量中。根据操作系统的不同,Python 可能已默认安装或需要手动下载安装。
|
||||
|
||||
有两种构建代码的方式。在开发过程中,您可能希望使用 `npm run dev` 在源文件更改时自动持续构建。开发构建还包含额外的警告和错误信息,便于开发过程中进行故障排除。完成更改后,可以运行 `npm run build` 创建优化的生产构建。
|
||||
|
||||
构建完成后,Gutenberg 即可作为 WordPress 插件使用!
|
||||
|
||||
## 本地 WordPress 环境
|
||||
|
||||
要测试 WordPress 插件,您需要先安装 WordPress 本体。如果您已设置好 WordPress 本地环境,只需将 gutenberg 目录放入 wp-content/plugins/ 目录即可将构建好的 Gutenberg 作为标准 WordPress 插件使用。
|
||||
|
||||
如果尚未设置本地 WordPress 环境,请按照本节剩余步骤创建环境。
|
||||
|
||||
## 开发者工具
|
||||
|
||||
我们建议将编辑器配置为自动检查语法和代码规范错误。这能帮助您在开发过程中自动修复细微的格式问题,从而节省时间。以下是为 Visual Studio Code(许多核心开发者常用的编辑器)的设置指南,这些工具也适用于其他编辑器。
|
||||
|
||||
### EditorConfig
|
||||
|
||||
[EditorConfig](https://editorconfig.org/) 定义了编辑器的标准配置,例如使用制表符代替空格。您应安装 [VS Code 的 EditorConfig 扩展](https://marketplace.visualstudio.com/items?itemName=editorconfig.editorconfig),它将自动配置您的编辑器以符合 [.editorconfig](https://github.com/WordPress/gutenberg/blob/HEAD/.editorconfig) 中定义的规则。
|
||||
|
||||
### ESLint
|
||||
|
||||
[ESLint](https://eslint.org/) 通过静态分析代码来发现问题。代码规范检查规则已集成到持续集成流程中,必须通过检查才能提交代码。您应为 Visual Studio Code 安装 [ESLint 扩展](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint),其他编辑器的集成方案请参阅 [eslint 文档](https://eslint.org/docs/user-guide/integrations)。
|
||||
|
||||
安装扩展后,ESLint 将使用 Gutenberg 代码库根目录中的 [.eslintrc.js](https://github.com/WordPress/gutenberg/blob/HEAD/.eslintrc.js) 文件作为格式规则。它会在开发时高亮显示问题,您还可以通过以下设置实现在保存时自动修复规范问题:
|
||||
|
||||
```json
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
},
|
||||
```
|
||||
|
||||
### Prettier
|
||||
|
||||
[Prettier](https://prettier.io/) 是一款能够定义规范化格式并自动修复代码以匹配该格式的工具。Prettier 与 ESLint 功能相似,但 Prettier 更侧重于格式和样式,而 ESLint 主要用于检测编码错误。
|
||||
|
||||
若要在 Visual Studio Code 中使用 Prettier,请安装 [Prettier 代码格式化扩展](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)。随后可通过在设置中添加以下配置,将其设为默认格式化工具并实现保存时自动修复问题:
|
||||
**_注意_:根据文档查看环境的不同,方括号可能显示为双括号,实际正确格式应为单层方括号。**
|
||||
|
||||
```json
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
```
|
||||
|
||||
此配置将使用 Gutenberg 代码库根目录中的 `.prettierrc.js` 文件,该配置继承自 [@wordpress/prettier-config](/packages/prettier-config/README.md) 包。
|
||||
|
||||
如果仅希望在 Gutenberg 项目中使用此配置,请在项目根目录创建 `.vscode` 文件夹,并将设置存入其中的 `settings.json` 文件。Visual Studio Code 将其称为工作区设置,且仅适用于当前项目。
|
||||
|
||||
其他编辑器的配置请参阅 [Prettier 编辑器集成文档](https://prettier.io/docs/en/editors.html)。
|
||||
|
||||
### TypeScript
|
||||
|
||||
**TypeScript** 是 JavaScript 语言的类型化超集。Gutenberg 项目通过 JSDoc 使用 TypeScript 来实现 [JavaScript 文件的类型检查](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html)。如果您使用 Visual Studio Code,其已内置 TypeScript 支持,其他编辑器的集成方案请参阅 [TypeScript 编辑器支持](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support)。
|
||||
|
||||
### 使用 Docker 与 wp-env
|
||||
|
||||
[wp-env 工具包](/packages/env/README.md)最初为古腾堡项目开发,旨在通过 Docker 快速创建标准 WordPress 环境。该工具已作为 `@wordpress/env` npm 包发布。
|
||||
|
||||
默认情况下,在插件目录中运行 `wp-env` 即可创建并启动 WordPress 环境,同时自动挂载并激活对应插件。您还可以配置 `wp-env` 以使用现有安装、多插件或主题。完整文档请参阅 [wp-env 工具包](/packages/env/README.md#wp-envjson)。
|
||||
|
||||
确保 Docker 处于运行状态,在古腾堡目录中执行以下命令启动 `wp-env`:
|
||||
|
||||
```bash
|
||||
npm run wp-env start
|
||||
```
|
||||
|
||||
该脚本将在后台创建基于最新 WordPress Docker 镜像的实例,并将本地古腾堡插件代码通过 Docker 卷映射到环境中。这样您在本地对代码的任何修改都会即时同步到 WordPress 实例中。
|
||||
|
||||
> 注意:`npm run` 将使用古腾堡项目内指定的 `wp-env` / `WordPress`?? 版本,确保您运行的是最新的 wp-env 版本。
|
||||
|
||||
停止运行环境:
|
||||
|
||||
```bash
|
||||
npm run wp-env stop
|
||||
```
|
||||
|
||||
若一切正常,终端将显示如下信息:
|
||||
|
||||
```bash
|
||||
WordPress 开发站点已启动:http://localhost:8888/
|
||||
WordPress 测试站点已启动:http://localhost:8889/
|
||||
MySQL 正在监听端口 51220
|
||||
|
||||
✔ 完成!(耗时 261 秒 898 毫秒)
|
||||
```
|
||||
|
||||
通过右键点击菜单栏(Mac)或系统托盘(Linux/Windows)中的 Docker 图标并选择「控制台」,您将看到脚本已下载若干 Docker 镜像,并正在运行包含完整 WordPress 环境的容器:
|
||||

|
||||

|
||||
|
||||
彻底删除安装环境:
|
||||
|
||||
```bash
|
||||
npm run wp-env destroy
|
||||
```
|
||||
|
||||
更多命令请查阅[工具包文档](/packages/env/README.md)。
|
||||
|
||||
#### 访问本地 WordPress 安装
|
||||
|
||||
WordPress 安装现已可通过 `http://localhost:8888` 访问
|
||||
|
||||
管理后台地址:`http://localhost:8888/wp-admin/`,登录凭据为:**用户名**:`admin`,**密码**:`password`。您会注意到古腾堡插件已安装并激活,这正是您的本地构建版本。
|
||||
|
||||
#### 访问 MySQL 数据库
|
||||
|
||||
古腾堡项目默认集成 phpMyAdmin。您可通过以下地址访问 MySQL 数据库:`http://localhost:9000/`。
|
||||
|
||||
若需通过其他工具访问数据库,请先获取连接信息:
|
||||
|
||||
1. 在终端中进入本地古腾堡代码库目录
|
||||
2. 运行 `npm run wp-env start` - 终端将输出 `wp-env` 环境的相关信息
|
||||
3. 在输出信息中查找 _MySQL_ 端口号:
|
||||
例如:
|
||||
|
||||
> MySQL 正在监听端口 {MYSQL端口号}
|
||||
|
||||
4. 复制/记录该端口号(注意此端口号会在每次 `wp-env` 重启时变更)
|
||||
5. 使用以下信息连接 MySQL 实例(请将 `{MYSQL端口号}` 替换为第三步获取的端口号):
|
||||
|
||||
```
|
||||
主机:127.0.0.1
|
||||
用户名:root
|
||||
密码:password
|
||||
数据库:wordpress
|
||||
端口:{MYSQL端口号}
|
||||
```
|
||||
|
||||
**请注意**:MySQL 端口号会在每次 `wp-env` 重启时变更。若发现无法访问数据库,请重复上述步骤获取新端口号以恢复连接。
|
||||
|
||||
**技巧**:[Sequel Ace](https://sequel-ace.com/) 是访问 MySQL 数据库的实用图形化工具。其他工具及其使用方式可参阅 [WordPress 数据库访问指南](https://developer.wordpress.org/advanced-administration/before-install/creating-database/)。
|
||||
|
||||
#### 故障排除
|
||||
|
||||
若遇到问题,请查阅 [`wp-env` 文档中的故障排除章节](/packages/env/README.md#troubleshooting-common-problems)。
|
||||
|
||||
### 使用 Local 或 MAMP
|
||||
|
||||
作为 Docker 和 `wp-env` 的替代方案,您也可以使用 [Local](https://localwp.com/)、[WampServer](https://wampserver.aviatechno.net/) 或 [MAMP](https://www.mamp.info/) 来运行本地 WordPress 环境。为此,请通过创建符号链接或将目录复制到相应的 `wp-content/plugins` 目录中,将 Gutenberg 作为常规插件克隆并安装到您的环境中。
|
||||
|
||||
您还需要进行一些额外配置才能运行端到端测试。
|
||||
|
||||
将当前目录切换到插件文件夹,并为所有端到端测试插件创建符号链接:
|
||||
|
||||
```bash
|
||||
ln -s gutenberg/packages/e2e-tests/plugins/* .
|
||||
```
|
||||
|
||||
如果添加了新插件,您需要再次运行此命令。运行端到端测试的命令如下:
|
||||
|
||||
```bash
|
||||
WP_BASE_URL=http://localhost:8888/gutenberg/ npm run test:e2e
|
||||
```
|
||||
|
||||
#### PHP 文件缓存
|
||||
|
||||
您需要禁用 OPCache 才能正确编辑 PHP 文件。修复方法如下:
|
||||
|
||||
- 转到 **MAMP > 首选项 > PHP**
|
||||
- 在 **缓存** 下选择 **关闭**
|
||||
- 点击 **确定** 确认
|
||||
|
||||
#### 传入连接
|
||||
|
||||
默认情况下,MAMP 启动的 Web 服务器(Apache)会监听所有传入连接,而不仅仅是本地连接。这意味着同一本地网络上的任何人(在某些情况下,甚至是互联网上的任何人)都可以访问您的 Web 服务器。这可能是故意的,对于在其他设备上测试站点很有用,但大多数情况下这可能会引发隐私或安全问题。请记住这一点,不要在此服务器上存储敏感信息。
|
||||
|
||||
虽然可以修复此问题,但您需要自行承担风险,因为它会破坏 MAMP 解析 Web 服务器配置的能力,从而导致 MAMP 认为 Apache 正在监听错误的端口。建议考虑改用其他工具替代 MAMP。否则,您可以按照以下步骤操作:
|
||||
|
||||
- 编辑 `/Applications/MAMP/conf/apache/httpd.conf`
|
||||
- 将 `Listen 8888` 改为 `Listen 127.0.0.1:8888`
|
||||
|
||||
#### 链接到其他目录
|
||||
|
||||
您可能希望在 `plugins` 和 `themes` 目录中创建指向其他文件夹的链接,例如:
|
||||
|
||||
- wp-content/plugins/gutenberg -> ~/projects/gutenberg
|
||||
- wp-content/themes/twentytwenty -> ~/projects/twentytwenty
|
||||
|
||||
如果是这样,您需要配置 Apache 以允许跟随此类链接:
|
||||
|
||||
- 打开或新建文件 `/Applications/MAMP/htdocs/.htaccess`
|
||||
- 添加以下行:`Options +SymLinksIfOwnerMatch`
|
||||
|
||||
#### 使用 WP-CLI
|
||||
|
||||
像 MAMP 这样的工具倾向于将 MySQL 配置为使用非默认端口 3306,通常偏好使用 8889。这可能会影响 WP-CLI,使其在尝试连接数据库后失败。要解决此问题,请编辑 `wp-config.php` 并将 `DB_HOST` 常量从 `define( 'DB_HOST', 'localhost' )` 改为 `define( 'DB_HOST', '127.0.0.1:8889' )`。
|
||||
|
||||
### 在远程服务器上
|
||||
|
||||
您可以通过在本地构建然后将构建的文件作为插件上传到远程服务器,在开发中使用远程服务器。
|
||||
|
||||
构建步骤:打开终端(如果在 Windows 上,则打开命令提示符)并导航到您克隆的代码库。输入 `npm ci` 以设置所有依赖项。完成后,输入 `npm run build`。
|
||||
|
||||
构建完成后,克隆的 Gutenberg 目录包含完整的插件,您可以将整个代码库上传到 `wp-content/plugins` 目录,并在 WordPress 管理后台激活插件。
|
||||
|
||||
另一种构建后上传的方法是运行 `npm run build:plugin-zip` 来创建插件压缩文件——这需要 `bash` 和 `php` 环境。该脚本会创建 `gutenberg.zip`,您可以通过 WordPress 管理后台安装 Gutenberg。
|
||||
|
||||
## Storybook
|
||||
|
||||
> Storybook 是一个开源工具,用于独立开发 React、React Native 等的 UI 组件。它使构建出色的 UI 变得有条理且高效。
|
||||
|
||||
Gutenberg 代码库还集成了 [Storybook](https://storybook.js.org/),允许在与 WordPress 无关的环境中进行测试和开发。这对于开发可复用组件和尝试通用 JavaScript 模块非常有帮助,无需任何后端依赖。
|
||||
|
||||
您可以通过本地运行 `npm run storybook:dev` 启动 Storybook。它会自动在浏览器中打开。
|
||||
|
||||
您还可以在 GitHub Pages 上测试当前 `trunk` 分支的 Storybook:[https://wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/)
|
||||
155
contributors/code/git-workflow.md
Normal file
155
contributors/code/git-workflow.md
Normal file
@@ -0,0 +1,155 @@
|
||||
## 分支命名规范
|
||||
|
||||
命名分支时应使用前缀+简短描述的形式,例如:`[类型]/[变更内容]`
|
||||
|
||||
推荐使用的前缀:
|
||||
|
||||
- `add/` = 新增功能
|
||||
- `try/` = 实验性功能(暂定添加)
|
||||
- `update/` = 更新现有功能
|
||||
- `remove/` = 移除现有功能
|
||||
- `fix/` = 修复现存问题
|
||||
|
||||
例如:`add/gallery-block` 表示您正在开发新增画廊区块功能
|
||||
|
||||
## 保持分支同步
|
||||
|
||||
当多人同时参与项目开发时,拉取请求很容易过时。"过时"的拉取请求是指与主干开发进度脱节的分支,在合并前需要重新同步。
|
||||
|
||||
同步方式有两种:合并与变基。在Gutenberg项目中推荐使用变基操作。变基会将您的修改重写为基于开发主线的增量提交,从而确保提交历史的整洁与线性。在处理拉取请求期间,您可以随时执行变基操作。**请尽早开启拉取请求**并持续保持提交历史的变基状态。
|
||||
|
||||
开发主线即 `trunk` 分支。当拉取请求分支因冲突无法合并至主干时(长期存续的拉取请求常出现此情况),您需要在变基过程中手动解决本地副本的冲突。具体操作请参阅《如何对拉取请求执行变基》中的[「执行变基」章节](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#perform-a-rebase)。
|
||||
|
||||
本地解决冲突后,可通过 `git push --force-with-lease` 更新拉取请求。使用 `--force-with-lease` 参数至关重要,它能有效避免意外覆盖他人代码。
|
||||
|
||||
同步流程可总结为:获取仓库更新 → 基于trunk执行变基 → 推送至远程仓库。具体命令如下:
|
||||
|
||||
```sh
|
||||
git fetch
|
||||
git rebase trunk
|
||||
git push --force-with-lease origin 您的分支名称
|
||||
```
|
||||
|
||||
## 维护派生仓库同步
|
||||
|
||||
参与拉取请求需先派生Gutenberg仓库作为独立工作副本。随着新代码不断并入主仓库,您的派生仓库容易失去同步。这里您的工作仓库称为 `fork`,主Gutenberg仓库称为 `upstream`。在创建新分支 (`git checkout -b my-new-branch`) 进行功能开发前,请务必先更新派生仓库。
|
||||
|
||||
需配置 upstream 远程仓库以保持同步:
|
||||
|
||||
```sh
|
||||
git remote add upstream https://github.com/WordPress/gutenberg.git
|
||||
git remote -v
|
||||
origin git@github.com:您的账户/gutenberg.git (fetch)
|
||||
origin git@github.com:您的账户/gutenberg.git (push)
|
||||
upstream https://github.com/WordPress/gutenberg.git (fetch)
|
||||
upstream https://github.com/WordPress/gutenberg.git (push)
|
||||
```
|
||||
|
||||
同步派生仓库需先获取上游更新并合并至本地:
|
||||
|
||||
```sh
|
||||
git fetch upstream
|
||||
git checkout trunk
|
||||
git merge upstream/trunk
|
||||
```
|
||||
|
||||
本地更新完成后,推送至GitHub完成派生仓库更新:
|
||||
|
||||
```sh
|
||||
git push
|
||||
```
|
||||
|
||||
上述命令将更新您的 `trunk` 分支与上游同步。如需更新其他分支,将 `trunk` 替换为对应分支名即可。
|
||||
|
||||
## 扩展应用
|
||||
|
||||
### 提交追溯
|
||||
|
||||
当需要定位引入特定修改的提交时,忽略仅含样式或格式修改的提交会提升效率。
|
||||
|
||||
新版 `git` 支持在历史记录中跳过指定提交:
|
||||
|
||||
```sh
|
||||
git blame --ignore-rev f63053cace3c02e284f00918e1854284c85b9132 -L 66,73 packages/api-fetch/src/middlewares/media-upload.js
|
||||
```
|
||||
|
||||
Gutenberg仓库通过 `.git-blame-ignore-revs` 文件记录所有样式与格式修订。使用该文件可一次性忽略所有相关提交:
|
||||
|
||||
```sh
|
||||
git blame --ignore-revs-file .git-blame-ignore-revs -L 66,73 packages/api-fetch/src/middlewares/media-upload.js
|
||||
```
|
||||
|
||||
# Git 工作流程
|
||||
|
||||
本文档旨在帮助您开始使用 Git 与 Gutenberg 进行协作。Git 是一款强大的源代码管理工具;若要深入学习 Git,请查阅基于 CC BY-NC-SA 3.0 许可证免费在线提供的 [Pro Git 书籍](https://git-scm.com/book/zh/v2)。
|
||||
|
||||
如果您不熟悉 Git 的使用,值得探索和实践。请尝试 [Git 教程](https://git-scm.com/docs/gittutorial) 以及 [Git 用户手册](https://git-scm.com/docs/user-manual) 来帮助入门。
|
||||
|
||||
Gutenberg 项目遵循标准的拉取请求流程进行贡献。有关拉取请求的更多详细信息,请参阅 GitHub 的[相关文档](https://docs.github.com/zh/github/collaborating-with-issues-and-pull-requests)。
|
||||
|
||||
## 概述
|
||||
|
||||
贡献者的流程概述如下:
|
||||
|
||||
- 复刻(Fork)Gutenberg 代码库。
|
||||
- 克隆复刻后的代码库。
|
||||
- 创建新分支。
|
||||
- 进行代码更改。
|
||||
- 确认测试通过。
|
||||
- 在新创建的分支中提交代码更改。
|
||||
- 将分支推送到复刻的代码库。
|
||||
- 向 Gutenberg 代码库提交拉取请求。
|
||||
|
||||
有关 Gutenberg 项目如何使用 GitHub 的更多信息,请参阅[代码库管理文档](/docs/contributors/repository-management.md)。
|
||||
|
||||
## Git 工作流程详解
|
||||
|
||||
代码和文档的工作流程是相同的,因为两者都在 GitHub 中进行管理。您可以观看[贡献文档的视频详解](https://wordpress.tv/2020/09/02/marcus-kazmierczak-contribute-developer-documentation-to-gutenberg/)以及相应的[贡献给 Gutenberg 的教程](https://mkaz.blog/wordpress/contribute-developer-documentation-to-gutenberg/)。
|
||||
|
||||
以下是 Git 工作流程的视觉概览:
|
||||
|
||||

|
||||
|
||||
**步骤 1**:前往 GitHub 上的 Gutenberg 代码库,点击 Fork。这将在您的账户下创建主 Gutenberg 代码库的副本。
|
||||
|
||||

|
||||
|
||||
**步骤 2**:在本地克隆您复刻的代码库。其地址为:`https://github.com/您的用户名/gutenberg`。克隆操作会将所有文件复制到您的计算机上。打开终端并运行:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/您的用户名/gutenberg
|
||||
```
|
||||
|
||||
这将创建一个名为 `gutenberg` 的目录,其中包含项目的所有文件。由于需要下载 Gutenberg 项目的完整历史记录,此过程可能需要几分钟。
|
||||
|
||||
**步骤 3**:为您的更改创建一个分支(分支命名规则见下文)。在此示例中,分支名称为完整字符串:`update/my-branch`
|
||||
|
||||
```bash
|
||||
git switch -c update/my-branch
|
||||
```
|
||||
|
||||
**步骤 4**:进行代码更改。全面构建、确认并测试您的更改。请参阅[编码指南](/docs/contributors/code/coding-guidelines.md)和[测试概述](/docs/contributors/code/testing-overview.md)以获取指导。
|
||||
|
||||
**步骤 5**:使用[良好的提交信息](https://make.wordpress.org/core/handbook/best-practices/commit-messages/)提交您的更改。这会将您的更改提交到本地的代码库副本中。
|
||||
|
||||
```bash
|
||||
git commit -m "您的良好提交信息" 路径/到/文件
|
||||
```
|
||||
|
||||
**步骤 6**:将您的更改推送到 GitHub。更改将被推送到您在 GitHub 上复刻的代码库中。
|
||||
|
||||
```bash
|
||||
git push -u origin update/my-branch
|
||||
```
|
||||
|
||||
**步骤 7**:前往您在 GitHub 上复刻的代码库——它将自动检测到更改,并为您提供创建拉取请求的链接。
|
||||
|
||||

|
||||
|
||||
**步骤 8**:创建拉取请求。这将在 WordPress Gutenberg 代码库上创建请求,以集成来自您复刻代码库的更改。
|
||||
|
||||
**步骤 9**:关注拉取请求上的新动态。如果要求进行任何额外的更改或更新,请在本地进行更改并按照步骤 4-6 推送它们。
|
||||
|
||||
请勿为更新创建新的拉取请求;通过将更改推送到您的代码库,它将更新同一个 PR。从这个意义上说,PR 是 WordPress Gutenberg 代码库指向您副本的指针。因此,当您更新副本时,PR 也会相应更新。
|
||||
|
||||
就是这样!一旦获得批准并合并,您的更改将被纳入主代码库。🎉
|
||||
83
contributors/code/how-to-get-your-pull-request-reviewed.md
Normal file
83
contributors/code/how-to-get-your-pull-request-reviewed.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 如何让你的拉取请求获得审阅?
|
||||
|
||||
有时我们发布了拉取请求,却无人[审阅](/docs/contributors/repository-management.md#code-review)我们的工作。该怎么办?
|
||||
|
||||
吸引审阅主要不在于代码本身——而在于让审阅过程变得轻松。
|
||||
|
||||
如果你发布的拉取请求未获得任何评论或审阅,可以尝试核心贡献者们使用的策略:
|
||||
|
||||
## 创建最合理的精简PR
|
||||
|
||||
审批一个2000行的PR需要数月时间且令人望而生畏。
|
||||
|
||||
审批一个50行的PR仅需数日或数小时且轻松自如。
|
||||
|
||||
大规模提交会拖慢进度。将工作拆分成小模块进行提交,才能更快合并代码、加速学习进程。
|
||||
|
||||
## 提供相关背景信息:
|
||||
|
||||
请阐明:
|
||||
* 你要解决什么问题?
|
||||
* 你的PR如何解决该问题?
|
||||
* 你需要什么反馈?
|
||||
* 哪些内容不在讨论范围?
|
||||
* 哪些设计不符合直觉?
|
||||
* 如何进行测试?
|
||||
|
||||
总结所有相关议题和PR。
|
||||
|
||||
这比让他人自行摸索要简单得多。
|
||||
|
||||
## 让你的PR引人注目
|
||||
|
||||
所有贡献都在争夺注意力。让你的作品脱颖而出。
|
||||
|
||||
最简单的方法?说明其重要性:
|
||||
|
||||
❌ 一个获取数据的新React钩子
|
||||
✅ `useEntityRecord`:用减少90%模板代码的方式获取数据
|
||||
|
||||
然后用代码示例、可视化效果和屏幕录像证明其价值。
|
||||
|
||||
## 展示你的工作
|
||||
|
||||
在相关议题和PR中发布你的PR链接。
|
||||
|
||||
提醒相关议题的评论者、先前提交者和技术负责人关注。
|
||||
|
||||
在WordPress.org Slack的#core-editor频道中提出。获取反馈最便捷的方式是在每周[核心编辑器会议](/docs/getting-started/README.md)的[自由发言环节](https://make.wordpress.org/core/tag/core-editor-agenda/)主动发声。
|
||||
|
||||
分配相关标签、里程碑和项目(或请他人协助分配)。
|
||||
|
||||
## 审阅他人的工作
|
||||
|
||||
这是进入他人视野的最简单途径。
|
||||
|
||||
查阅相关议题评论者、先前提交者和技术负责人的PR,然后进行审阅。
|
||||
|
||||
对他们的工作不熟悉?可以:
|
||||
|
||||
* 花时间理解内容
|
||||
* 提议结对编程环节
|
||||
* 跳过此项,审阅下一个PR
|
||||
|
||||
## 通过清晰表述降低风险
|
||||
|
||||
风险会增加阻力——当前的批准可能在日后产生反效果。
|
||||
|
||||
清晰的阐述如同润滑剂。请明确记录:
|
||||
|
||||
* 涉及哪些风险?为何要承担这些风险?
|
||||
* 为何此PR是最佳解决方案?
|
||||
* 如何将风险最小化?
|
||||
* 已尝试过哪些其他方案?
|
||||
|
||||
## 关注热点领域
|
||||
|
||||
某些PR天然比其他PR更容易获得关注。
|
||||
|
||||
请重点投入这些领域。
|
||||
|
||||
部分议题比其他议题更具时效性(例如列入下一版发布目标的议题),因此能获得更多关注。专注于这些议题将更容易吸引审阅者。
|
||||
|
||||
如何快速切入?参与WordPress路线图中的活跃项目提供协助
|
||||
7
contributors/code/managing-packages.md
Normal file
7
contributors/code/managing-packages.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# 包管理
|
||||
|
||||
本代码库采用 [npm 工作区](https://docs.npmjs.com/cli/v10/using-npm/workspaces)来管理 WordPress 软件包,并使用 [lerna](https://lerna.js.org/) 将这些软件包发布至 [npm](https://www.npmjs.com/)。这一机制在工作流程中设定了特定步骤,具体说明详见[软件包](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md)文档。
|
||||
|
||||
维护数十个 npm 软件包颇具挑战——追踪变更内容尤为困难。因此我们为每个软件包配置 `CHANGELOG.md` 文件来简化发布流程。作为贡献者,当您提交涉及生产环境的代码时,请按照[维护更新日志](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#maintaining-changelogs)章节的说明,在前述文件中添加对应条目。
|
||||
|
||||
通过与双周发布的 Gutenberg 插件 RC1 版本保持同步,实现了 WordPress 软件包发布至 npm 的自动化流程。您可以在[Gutenberg 发布流程文档](/docs/contributors/code/release.md#packages-releases-to-npm-and-wordpress-core-updates)中了解此过程及其他发布 npm 软件包新版本的方式。
|
||||
35
contributors/code/react-native/README.md
Normal file
35
contributors/code/react-native/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# React Native 移动编辑器
|
||||
|
||||
Gutenberg 代码库包含了基于 [React Native](https://reactnative.dev/) 的移动端编辑器源码。
|
||||
|
||||
## 移动端注意事项
|
||||
|
||||
贡献者需确保在代码重构期间更新所有受影响的本地移动文件,因为我们目前还无法依赖自动化工具完成这一工作。例如,重命名函数或属性时也需在原生模块中同步修改,否则移动客户端将出现故障。我们已在 PR 中设置了移动端专项 CI 测试作为防护机制,但仍有诸多待完善之处。感谢您的理解与支持。❤️🙇
|
||||
|
||||
## 移动端专属文件
|
||||
|
||||
与移动端共享的代码大多位于相同的 JavaScript 模块和 SASS 样式文件中。当代码路径需要区分时,会创建 `.native.js` 或 `.native.scss` 格式的文件变体。某些情况下还可找到针对 Android (`.android.js`) 或 iOS (`.ios.js`) 的平台专属文件。
|
||||
|
||||
## 在 Android 和 iOS 上运行 Gutenberg Mobile
|
||||
|
||||
如需了解如何在 Android 或 iOS 上运行 **Gutenberg Mobile 演示应用**,请参阅 [React Native 移动版 Gutenberg 入门指南](/docs/contributors/code/react-native/getting-started-react-native.md)
|
||||
|
||||
此外,移动客户端通过[官方 WordPress 应用](https://wordpress.org/mobile/)进行打包和发布。虽然构建流程与移动演示应用略有不同,且目前存放在独立代码库中([此处为移动端原生代码库](https://github.com/wordpress-mobile/gutenberg-mobile)),但其源代码直接取自本代码库及“网页”端代码路径。
|
||||
|
||||
## 持续集成中的移动端端到端测试
|
||||
|
||||
若在拉取请求中遇到 Android/iOS 测试失败,建议采取以下步骤:
|
||||
|
||||
1. 重新运行失败的 GitHub Action 任务([重新运行指南](https://docs.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#viewing-your-workflow-history))—— 多数情况下可解决测试失败问题
|
||||
2. 按照[端到端测试文档](/packages/react-native-editor/__device-tests__/README.md)中的步骤在本地运行测试,验证是否会出现相同故障
|
||||
3. 除了查看端到端测试日志外,还可从 GitHub 任务的 Artifacts 区域下载视频记录以获取更多有效信息
|
||||
4. 检查 PR 中的变更是否需要对 `.native.js` 格式的文件进行相应修改
|
||||
5. 若最终仍无法解决移动测试失败问题,欢迎通过 Slack 在 #mobile 或 #core-editor 频道联系贡献者([免费加入](https://make.wordpress.org/chat/))
|
||||
|
||||
## 调试移动端单元测试
|
||||
|
||||
需要时可按照[移动端原生测试指南](/docs/contributors/code/react-native/integration-test-guide.md)中的说明在本地调试移动端单元测试。
|
||||
|
||||
## 国际化 (i18n)
|
||||
|
||||
关于此主题的更多信息请参阅 [React Native 国际化指南](/docs/contributors/code/react-native/internationalization-guide.md)。
|
||||
148
contributors/code/react-native/getting-started-react-native.md
Normal file
148
contributors/code/react-native/getting-started-react-native.md
Normal file
@@ -0,0 +1,148 @@
|
||||
# React Native 版移动端 Gutenberg 入门指南
|
||||
|
||||
欢迎阅读!本文档是针对 Android 和 iOS 设备的区块编辑器原生移动端移植版的入门指南。总体而言,这是一个可在全新项目或现有项目中使用的 React Native 库。请继续阅读了解如何构建、测试和运行。
|
||||
|
||||
## 环境准备
|
||||
|
||||
为了获得与项目维护者相近的开发体验,请确保安装以下工具:
|
||||
|
||||
- git
|
||||
- [nvm](https://github.com/nvm-sh/nvm)
|
||||
- Node.js 和 npm(使用 nvm 安装)
|
||||
- [Android Studio](https://developer.android.com/studio/)(用于编译 Android 版应用)
|
||||
- [Xcode](https://developer.apple.com/xcode/)(用于编译 iOS 应用)
|
||||
- CocoaPods(通过 `sudo gem install cocoapods` 安装)用于获取 React 及第三方依赖
|
||||
|
||||
请注意,维护者使用的操作系统平台是 macOS,但相关工具和设置也应适用于其他平台。
|
||||
|
||||
## 克隆项目
|
||||
|
||||
```sh
|
||||
git clone https://github.com/WordPress/gutenberg.git
|
||||
```
|
||||
|
||||
## 环境设置
|
||||
|
||||
请注意,此处描述的命令应在克隆项目的顶层目录中运行。在运行演示应用之前,需要下载并安装项目依赖。通过以下命令完成:
|
||||
|
||||
```sh
|
||||
nvm install
|
||||
npm ci
|
||||
npm run native preios
|
||||
```
|
||||
|
||||
## 运行项目
|
||||
|
||||
```sh
|
||||
npm run native start:reset
|
||||
```
|
||||
|
||||
该命令将在开发模式下运行打包程序(Metro)。打包程序会持续运行,向请求的客户端提供应用包。
|
||||
|
||||
在打包程序运行的同时,打开另一个终端窗口,使用以下命令编译并运行 Android 应用:
|
||||
|
||||
```sh
|
||||
npm run native android
|
||||
```
|
||||
|
||||
此时应用应在连接的设备或运行的模拟器中打开,并从正在运行的打包程序获取 JavaScript 代码。
|
||||
|
||||
要使用默认模拟器设备编译并运行 iOS 版本的应用,请使用:
|
||||
|
||||
```sh
|
||||
npm run native ios
|
||||
```
|
||||
|
||||
如果您使用的是 Mac 并已安装相关环境,该命令将尝试在 iOS 模拟器中打开您的应用。
|
||||
|
||||
### 在其他 iOS 设备模拟器上运行
|
||||
|
||||
要使用其他设备模拟器编译并运行应用,请使用以下命令。注意使用双 `--` 将模拟器选项传递给 `react-native` CLI:
|
||||
|
||||
```sh
|
||||
npm run native ios -- -- --simulator="设备名称"
|
||||
```
|
||||
|
||||
例如,如果要在 iPhone Xs Max 上运行,请尝试:
|
||||
|
||||
```sh
|
||||
npm run native ios -- -- --simulator="iPhone Xs Max"
|
||||
```
|
||||
|
||||
要查看所有可用 iOS 设备列表,请使用 `xcrun simctl list devices`。
|
||||
|
||||
### 自定义演示编辑器
|
||||
|
||||
默认情况下,演示编辑器会渲染大多数受支持的核心区块。这有助于展示编辑器的功能,但在专注于特定区块或功能时可能会分散注意力。可以通过在 `packages/react-native-editor/src/setup-local.js` 文件中使用 `native.block_editor_props` 钩子来自定义编辑器的初始状态。
|
||||
|
||||
<details><summary>setup-local.js 示例</summary>
|
||||
|
||||
```js
|
||||
/**
|
||||
* WordPress 依赖
|
||||
*/
|
||||
import { addFilter } from '@wordpress/hooks';
|
||||
|
||||
export default () => {
|
||||
addFilter(
|
||||
'native.block_editor_props',
|
||||
'core/react-native-editor',
|
||||
( props ) => {
|
||||
return {
|
||||
...props,
|
||||
initialHtml,
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const initialHtml = `
|
||||
<!-- wp:heading -->
|
||||
<h2 class="wp-block-heading">仅一个标题</h2>
|
||||
<!-- /wp:heading -->
|
||||
`;
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### 故障排除
|
||||
|
||||
如果 Android 模拟器无法正常启动,或编译失败并显示 `Could not initialize class org.codehaus.groovy.runtime.InvokerHelper` 或类似错误,建议根据 [React Native 文档](https://reactnative.dev/docs/environment-setup)中的最新要求仔细检查开发环境设置。例如,使用 Android Studio 时,需要配置 `ANDROID_HOME` 环境变量并确保 JDK 版本符合最新要求。
|
||||
|
||||
有时,特别是在调整 `package.json`、Babel 配置(`.babelrc`)或 Jest 配置(`jest.config.js`)中的任何内容时,您的更改可能似乎未按预期生效。此时,可能需要停止 Metro 打包进程并使用 `npm run native start:reset` 重新启动。其他时候,您可能需要重新安装 NPM 包,这时 `npm run native clean:install` 脚本会很有用。
|
||||
|
||||
## 使用 Visual Studio Code 进行开发
|
||||
|
||||
虽然不要求必须使用 Visual Studio Code 来开发 gutenberg-mobile,但它是推荐的 IDE,我们为其提供了一些配置。
|
||||
|
||||
首次在 Visual Studio 中打开项目时,系统会提示安装一些推荐的扩展。这将有助于类型检查和调试等工作。
|
||||
|
||||
我们使用的扩展之一是 [React Native Tools](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native)。它允许您从 VSCode 运行打包程序,或在 iOS 或 Android 上启动应用程序。它还添加了一些调试配置,以便您可以直接在 VSCode 中设置断点和调试应用程序。有关更多详细信息,请参阅[扩展文档](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native)。
|
||||
|
||||
## 单元测试
|
||||
|
||||
使用以下命令运行测试套件:
|
||||
|
||||
```sh
|
||||
npm run test:native
|
||||
```
|
||||
|
||||
该命令将在您的测试上运行 [jest](https://github.com/facebook/jest) 测试运行器。测试在桌面上针对 Node.js 运行。
|
||||
|
||||
要在调试器支持下运行测试,请使用以下 CLI 命令启动:
|
||||
|
||||
```sh
|
||||
npm run test:native:debug
|
||||
```
|
||||
|
||||
然后,在 Chrome 中打开 `chrome://inspect` 以附加调试器(查看“远程目标”部分)。在测试/开发过程中,可以随意在代码中的任何位置添加 `debugger` 语句,以便在调试器中中断。
|
||||
|
||||
## 编写和运行单元测试
|
||||
|
||||
本项目配置使用 [jest](https://jestjs.io/) 进行测试。您可以配置任何喜欢的测试策略,但 jest 可以开箱即用。在名为 `__tests__` 的目录中创建测试文件,或使用 `.test.js` 扩展名,以便 jest 加载这些文件。请参阅[此处的示例测试](https://github.com/WordPress/gutenberg/blob/HEAD/packages/react-native-editor/src/test/api-fetch-setup.test.js)。[jest 文档](https://jestjs.io/docs/getting-started)和 [React Native 测试教程](https://jestjs.io/docs/tutorial-react-native)也是极好的资源。
|
||||
|
||||
## 端到端测试
|
||||
|
||||
除了单元测试之外,Mobile Gutenberg(MG)项目还依赖端到端(E2E)测试,在类似于最终用户的环境中自动化测试关键流程。我们通常更倾向于单元测试,因为它们速度快且易于维护。但是,对于需要操作系统级功能(例如复杂手势、文本选择)或视觉回归测试(例如深色模式、对比度级别)的断言,我们使用 E2E 测试。
|
||||
|
||||
E2E 测试位于 [`packages/react-native-editor/__device-tests__`](/packages/react-native-editor/__device-tests__) 目录中。有关运行和贡献这些测试的其他文档,请参阅[测试目录](/packages/react-native-editor/__device-tests__#readme)。
|
||||
362
contributors/code/react-native/integration-test-guide.md
Normal file
362
contributors/code/react-native/integration-test-guide.md
Normal file
@@ -0,0 +1,362 @@
|
||||
### `waitFor` 超时设置
|
||||
|
||||
`waitFor` 函数的默认超时时间设定为 1000 毫秒。目前该值足以满足我们测试的所有渲染逻辑,但若在测试过程中发现某个元素需要更长的渲染时间,则应适当增加该设定值。
|
||||
|
||||
### 替换现有 UI 单元测试
|
||||
|
||||
部分组件已具备覆盖组件渲染的单元测试。虽然并非强制要求,但在这些情况下,建议评估将其迁移为集成测试的可能性。
|
||||
|
||||
若需同时保留两种测试类型,我们将在集成测试文件名中加入"integration"字样以避免命名冲突,具体示例可参考:[packages/block-library/src/missing/test/edit-integration.native.js](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/missing/test/edit-integration.native.js)。
|
||||
|
||||
### 平台选择配置
|
||||
|
||||
默认情况下,Jest 中的所有测试均在 Android 平台环境下运行。如需测试特定平台的相关行为,则需要支持多平台测试文件。
|
||||
|
||||
若仅需测试由 Platform 对象控制的逻辑,可通过以下代码模拟模块(本例将平台切换为 iOS):
|
||||
|
||||
```js
|
||||
jest.mock( 'Platform', () => {
|
||||
const Platform = jest.requireActual( 'Platform' );
|
||||
Platform.OS = 'ios';
|
||||
Platform.select = jest.fn().mockImplementation( ( select ) => {
|
||||
const value = select[ Platform.OS ];
|
||||
return ! value ? select.default : value;
|
||||
} );
|
||||
return Platform;
|
||||
} );
|
||||
```
|
||||
|
||||
### 打开区块设置
|
||||
|
||||
点击"打开设置"按钮即可访问区块设置,以下是一个示例:
|
||||
|
||||
```js
|
||||
fireEvent.press( block );
|
||||
|
||||
const settingsButton = await findByLabelText( '打开设置' );
|
||||
fireEvent.press( settingsButton );
|
||||
```
|
||||
|
||||
#### 使用作用域组件方法
|
||||
|
||||
当采用作用域组件方法时,我们需要先渲染 `SlotFillProvider` 和 `BottomSheetSettings`(注意我们通过传递 `isVisible` 属性强制显示底部面板)以及区块:
|
||||
|
||||
```js
|
||||
<SlotFillProvider>
|
||||
<BlockEdit isSelected name={ name } clientId={ 0 } { ...props } />
|
||||
<BottomSheetSettings isVisible />
|
||||
</SlotFillProvider>
|
||||
```
|
||||
|
||||
参考示例:
|
||||
|
||||
- [封面区块](https://github.com/WordPress/gutenberg/blob/b403b977b029911f46247012fa2dcbc42a5aa3cf/packages/block-library/src/cover/test/edit.native.js#L37-L42)
|
||||
|
||||
### FlatList 项
|
||||
|
||||
`FlatList` 组件根据滚动位置、视图和内容尺寸来渲染其项。这意味着在渲染此组件时,某些项可能因尚未渲染而无法被查询到。为解决此问题,我们需要显式触发事件使 `FlatList` 渲染所有项。
|
||||
|
||||
以下是插入器菜单中用于渲染区块列表的 FlatList 示例:
|
||||
|
||||
```js
|
||||
const blockList = getByTestId( '插入器界面-区块' );
|
||||
// 通过 onScroll 事件强制 FlatList 渲染所有项
|
||||
fireEvent.scroll( blockList, {
|
||||
nativeEvent: {
|
||||
contentOffset: { y: 0, x: 0 },
|
||||
contentSize: { width: 100, height: 100 },
|
||||
layoutMeasurement: { width: 100, height: 100 },
|
||||
},
|
||||
} );
|
||||
```
|
||||
|
||||
### 滑块控件
|
||||
|
||||
在底部面板中的滑块应使用其 `testID` 进行查询:
|
||||
|
||||
```js
|
||||
const radiusSlider = await findByTestId( '滑块 边框圆角' );
|
||||
fireEvent( radiusSlider, 'valueChange', '30' );
|
||||
```
|
||||
|
||||
注意滑块的 `testID` 是"滑块 " + 标签文本。因此对于标签为"边框圆角"的滑块,其 `testID` 即为"滑块 边框圆角"。
|
||||
|
||||
### 选择内部区块
|
||||
|
||||
添加区块时需注意:如果区块包含内部区块,这些内部区块默认不会渲染。以下示例展示如何让按钮区块渲染其内部的按钮区块(假设已获得按钮区块的引用 `buttonsBlock`):
|
||||
|
||||
```js
|
||||
const innerBlockListWrapper = await within( buttonsBlock ).findByTestId(
|
||||
'区块列表包装器'
|
||||
);
|
||||
fireEvent( innerBlockListWrapper, 'layout', {
|
||||
nativeEvent: {
|
||||
layout: {
|
||||
width: 100,
|
||||
},
|
||||
},
|
||||
} );
|
||||
|
||||
const buttonInnerBlock = await within( buttonsBlock ).findByLabelText(
|
||||
/按钮区块\. 第1行/
|
||||
);
|
||||
fireEvent.press( buttonInnerBlock );
|
||||
```
|
||||
|
||||
## 工具
|
||||
|
||||
### 使用无障碍检查器
|
||||
|
||||
如果难以定位元素的标识符,建议使用 Xcode 的无障碍检查器。大多数标识符是跨平台的,因此即使测试默认在 Android 上运行,仍可通过无障碍检查器查找正确的标识符。
|
||||
|
||||
<img src="https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/assets/xcode-accessibility-inspector-screenshot.png" alt="Xcode无障碍检查器应用截图。截图展示了如何在设备下拉菜单中选择正确目标、启用目标模式,以及点击屏幕元素后定位无障碍标签的方法"/>
|
||||
|
||||
## 常见陷阱与注意事项
|
||||
|
||||
### 省略 `waitFor` 前的 `await` 会导致误判
|
||||
|
||||
在 `waitFor` 前省略 `await` 可能导致测试通过但未验证预期行为的情况。例如,若使用 `toBeDefined` 来断言 `waitFor` 的调用结果,由于 `waitFor` 始终会返回值,断言将通过——即使该值并非我们想要检查的 `ReactTestInstance`。因此建议使用自定义匹配器 `toBeVisible`,可有效防范此类误判情况。
|
||||
|
||||
### 使用 `find` 类查询
|
||||
|
||||
组件渲染或事件触发后,可能因状态更新产生副作用,导致目标元素尚未渲染。此时需要等待元素可用,为此可使用查询函数的 `find*` 版本,这些函数内部采用 `waitFor` 机制周期性检测元素是否出现。
|
||||
|
||||
示例如下:
|
||||
|
||||
```js
|
||||
const mediaLibraryButton = await findByText( 'WordPress媒体库' );
|
||||
```
|
||||
|
||||
```js
|
||||
const missingBlock = await findByLabelText( /不支持的块\. 第1行/ );
|
||||
```
|
||||
|
||||
```js
|
||||
const radiusSlider = await findByTestId( '滑块圆角半径' );
|
||||
```
|
||||
|
||||
多数情况下我们会使用 `find*` 函数,但需注意应仅限于真正需要等待元素出现的查询场景。
|
||||
|
||||
### `within` 查询
|
||||
|
||||
通过 `within` 函数可查询其他元素内部包含的元素,示例如下:
|
||||
|
||||
```js
|
||||
const missingBlock = await findByLabelText( /不支持的块\. 第1行/ );
|
||||
const translatedTableTitle = within( missingBlock ).getByText( '表格' );
|
||||
```
|
||||
|
||||
## 触发事件
|
||||
|
||||
除了查询元素,触发事件模拟用户交互同样重要。为此可使用 `fireEvent` 函数([文档](https://callstack.github.io/react-native-testing-library/docs/api#fireevent))。
|
||||
|
||||
点击事件示例:
|
||||
|
||||
**点击事件:**
|
||||
|
||||
```js
|
||||
fireEvent.press( settingsButton );
|
||||
```
|
||||
|
||||
我们也可以触发任意类型事件(包括自定义事件)。以下示例展示如何触发滑块组件的 `onValueChange` 事件([代码参考](https://github.com/WordPress/gutenberg/blob/520cbd9d2af4bbc275d388edf92a6cadb685de56/packages/components/src/mobile/bottom-sheet/range-cell.native.js#L227)):
|
||||
|
||||
**自定义事件 – onValueChange:**
|
||||
|
||||
```js
|
||||
fireEvent( heightSlider, 'valueChange', '50' );
|
||||
```
|
||||
|
||||
## 验证元素行为
|
||||
|
||||
完成元素查询和事件触发后,需验证逻辑是否符合预期。可使用与单元测试相同的 Jest `expect` 函数,推荐使用自定义匹配器 `toBeVisible` 来确保元素已定义、属于有效React元素且可见。
|
||||
|
||||
示例如下:
|
||||
|
||||
```js
|
||||
const translatedTableTitle = within( missingBlock ).getByText( '表格' );
|
||||
expect( translatedTableTitle ).toBeVisible();
|
||||
```
|
||||
|
||||
当渲染完整编辑器时,还可验证HTML输出是否符合预期:
|
||||
|
||||
```js
|
||||
expect( getEditorHtml() ).toBe(
|
||||
'<!-- wp:spacer {"height":50} -->\n<div style="height:50px" aria-hidden="true" class="wp-block-spacer"></div>\n<!-- /wp:spacer -->'
|
||||
);
|
||||
```
|
||||
|
||||
## 清理操作
|
||||
|
||||
最后需要清理可能影响后续测试的修改。以下是注册区块后的典型清理示例(需取消注册所有区块):
|
||||
|
||||
```js
|
||||
afterAll( () => {
|
||||
// 清理已注册区块
|
||||
getBlockTypes().forEach( ( block ) => {
|
||||
unregisterBlockType( block.name );
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
## 辅助工具
|
||||
|
||||
为简化原生版本集成测试的编写,可在[此README文件](https://github.com/WordPress/gutenberg/blob/HEAD/test/native/integration-test-helpers/README.md)中查看辅助函数列表。
|
||||
|
||||
## 常见流程
|
||||
|
||||
### 查询区块
|
||||
|
||||
通过无障碍访问标签查询区块是常见方式,示例如下:
|
||||
|
||||
```js
|
||||
const spacerBlock = await waitFor( () =>
|
||||
getByLabelText( /间距区块\. 第1行/ )
|
||||
);
|
||||
```
|
||||
|
||||
关于区块无障碍访问标签的更多信息,可查阅 [`getAccessibleBlockLabel` 函数](https://github.com/WordPress/gutenberg/blob/520cbd9d2af4bbc275d388edf92a6cadb685de56/packages/blocks/src/api/utils.js#L167-L234)的代码实现。
|
||||
|
||||
### 添加区块
|
||||
|
||||
以下是插入段落区块的示例:
|
||||
|
||||
```js
|
||||
// 打开插入器菜单
|
||||
fireEvent.press( await findByLabelText( '添加区块' ) );
|
||||
|
||||
const blockList = getByTestId( 'InserterUI-区块列表' );
|
||||
// 通过onScroll事件强制FlatList渲染所有项
|
||||
fireEvent.scroll( blockList, {
|
||||
nativeEvent: {
|
||||
contentOffset: { y: 0, x: 0 },
|
||||
contentSize: { width: 100, height: 100 },
|
||||
layoutMeasurement: { width: 100, height: 100 },
|
||||
},
|
||||
} );
|
||||
|
||||
// 插入段落区块
|
||||
fireEvent.press( await findByText( `段落` ) );
|
||||
```
|
||||
|
||||
# React Native 集成测试指南
|
||||
|
||||
## 什么是集成测试?
|
||||
|
||||
集成测试被定义为将不同部分作为整体进行测试的一种测试类型。在我们的场景中,需要测试的部件是指为特定区块或编辑器逻辑所需渲染的不同组件。最终它们与单元测试非常相似,因为它们都是使用 Jest 库通过相同命令运行的。主要区别在于,对于集成测试,我们将使用特定库 [`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/) 来测试编辑器如何渲染不同组件。
|
||||
|
||||
## 集成测试的结构
|
||||
|
||||
测试可以包含以下部分:
|
||||
|
||||
- [设置](#设置)
|
||||
- [渲染](#渲染)
|
||||
- [查询元素](#查询元素)
|
||||
- [触发事件](#触发事件)
|
||||
- [验证元素行为](#验证元素行为)
|
||||
- [清理](#清理)
|
||||
|
||||
我们还在后续章节中提供了常见任务示例和技巧:
|
||||
|
||||
- [辅助工具](#辅助工具)
|
||||
- [常见流程](#常见流程)
|
||||
- [工具](#工具)
|
||||
- [常见陷阱与注意事项](#常见陷阱与注意事项)
|
||||
|
||||
## 设置
|
||||
|
||||
这部分通常通过使用 Jest 回调函数 `beforeAll` 和 `beforeEach` 来完成,目的是准备测试可能需要的所有内容,比如注册区块或模拟部分逻辑。
|
||||
|
||||
以下是一个预期所有核心区块可用时的常见模式示例:
|
||||
|
||||
```js
|
||||
beforeAll( () => {
|
||||
// 注册所有核心区块
|
||||
registerCoreBlocks();
|
||||
} );
|
||||
```
|
||||
|
||||
## 渲染
|
||||
|
||||
在引入测试逻辑之前,我们需要先渲染要测试的组件。根据是否使用作用域组件方法或完整编辑器方法,这部分会有所不同。
|
||||
|
||||
### 使用作用域组件方法
|
||||
|
||||
以下是渲染封面区块的示例(摘自[此代码](https://github.com/WordPress/gutenberg/blob/86cd187873984f80ddeeec3e82454b486dd1860f/packages/block-library/src/cover/test/edit.native.js#L82-L91)):
|
||||
|
||||
```js
|
||||
// 此导入指向区块的索引文件
|
||||
import { metadata, settings, name } from '../index';
|
||||
|
||||
...
|
||||
|
||||
const setAttributes = jest.fn();
|
||||
const attributes = {
|
||||
backgroundType: IMAGE_BACKGROUND_TYPE,
|
||||
focalPoint: { x: '0.25', y: '0.75' },
|
||||
hasParallax: false,
|
||||
overlayColor: { color: '#000000' },
|
||||
url: 'mock-url',
|
||||
};
|
||||
|
||||
...
|
||||
|
||||
// 在插槽内渲染封面编辑器的简化树结构
|
||||
const CoverEdit = ( props ) => (
|
||||
<SlotFillProvider>
|
||||
<BlockEdit isSelected name={ name } clientId={ 0 } { ...props } />
|
||||
<BottomSheetSettings isVisible />
|
||||
</SlotFillProvider>
|
||||
);
|
||||
|
||||
const { getByText, findByText } = render(
|
||||
<CoverEdit
|
||||
attributes={ {
|
||||
...attributes,
|
||||
url: undefined,
|
||||
backgroundType: undefined,
|
||||
} }
|
||||
setAttributes={ setAttributes }
|
||||
/>
|
||||
);
|
||||
```
|
||||
|
||||
### 使用完整编辑器方法
|
||||
|
||||
以下是渲染按钮区块的示例(摘自[此代码](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/buttons/test/edit.native.js#L32-L39)):
|
||||
|
||||
```js
|
||||
const initialHtml = `<!-- wp:buttons -->
|
||||
<div class="wp-block-buttons"><!-- wp:button {"style":{"border":{"radius":"5px"}}} -->
|
||||
<div class="wp-block-button"><a class="wp-block-button__link" style="border-radius:5px" >Hello</a></div>
|
||||
<!-- /wp:button --></div>
|
||||
<!-- /wp:buttons -->`;
|
||||
const { getByLabelText } = initializeEditor( {
|
||||
initialHtml,
|
||||
} );
|
||||
```
|
||||
|
||||
## 查询元素
|
||||
|
||||
组件渲染完成后,就可以进行元素查询。关于这个主题的一个重要注意事项是:我们应该从用户角度进行测试,这意味着理想情况下应该通过用户可访问的文本或无障碍标签来查询元素。
|
||||
|
||||
查询时应遵循以下优先级顺序:
|
||||
|
||||
1. `getByText`:通过文本查询是最接近用户视角的操作流程,因为文本是用户识别元素的视觉线索。
|
||||
2. `getByLabelText`:在某些情况下,我们需要查询不提供文本的元素,这时可以回退到使用无障碍标签。
|
||||
3. `getByTestId`:如果前面的选项都不适用,并且没有任何可依赖的视觉元素,就必须回退到特定的测试ID,这可以通过 `testID` 属性来定义(参见[此示例](https://github.com/WordPress/gutenberg/blob/e5b387b19ffc50555f52ea5f0b415ab846896def/packages/block-editor/src/components/block-types-list/index.native.js#L80))。
|
||||
|
||||
以下是一些示例:
|
||||
|
||||
```js
|
||||
const mediaLibraryButton = getByText( 'WordPress媒体库' );
|
||||
```
|
||||
|
||||
```js
|
||||
const missingBlock = getByLabelText( /不支持的区块\. 第1行/ );
|
||||
```
|
||||
|
||||
```js
|
||||
const radiusSlider = getByTestId( '圆角滑块' );
|
||||
```
|
||||
|
||||
请注意,这些查询可以传入纯字符串或正则表达式。正则表达式最适合查询部分字符串(例如,任何包含"不支持的区块. 第1行"的无障碍标签元素)。注意特殊字符如 `.` 需要进行转义。
|
||||
85
contributors/code/react-native/internationalization-guide.md
Normal file
85
contributors/code/react-native/internationalization-guide.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# React Native 国际化指南
|
||||
|
||||
编辑器原生版本涉及两种类型的字符串:
|
||||
1. 在网页和原生平台共同使用的字符串
|
||||
2. 仅限原生平台使用的字符串
|
||||
|
||||
对于第一类字符串,其翻译流程与网页版本遵循相同的[操作指南](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/internationalization.md),但第二类字符串需要您自行提供翻译方案。
|
||||
|
||||
## 提取仅限原生平台使用的字符串
|
||||
|
||||
要识别这类字符串,您可以使用位于 `packages/react-native-editor/bin/extract-used-strings.js` 的 [`extract-used-strings`](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/bin/extract-used-strings.js) 脚本生成 JSON 对象,该对象包含所有被引用的字符串及其使用平台和引用文件信息。格式示例如下:
|
||||
```
|
||||
{
|
||||
"gutenberg": {
|
||||
"<字符串>": {
|
||||
"string": 字符串值,
|
||||
"stringPlural": 包含复数形式的字符串值,[可选]
|
||||
"comments": 给译者的注释,[默认值为空字符串]
|
||||
"reference": 包含引用该字符串的源文件路径的数组,
|
||||
"platforms": 包含字符串使用平台的数组,可选值为 "android" | "ios" | "web"
|
||||
},
|
||||
...
|
||||
},
|
||||
"其他域插件": {
|
||||
...
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
该命令还支持传入额外插件参数,适用于编辑器生成的 React Native 包包含其他插件的情况。
|
||||
|
||||
需要注意的是,该 JSON 对象包含所有已使用的字符串,因此要识别仅限原生平台使用的字符串,您需要自行编写脚本/流程进行提取。这可以通过遍历字符串并过滤掉包含 "web" 平台的字符串来实现。
|
||||
|
||||
### NPM 命令
|
||||
|
||||
提取已使用字符串:
|
||||
```sh
|
||||
npm run native i18n:extract-used-strings -- "$PWD/used-strings.json"
|
||||
```
|
||||
|
||||
***注意:** 需要传入绝对路径,否则会以 `packages/react-native-editor` 作为相对路径的根目录*
|
||||
|
||||
提取包含额外插件的已使用字符串:
|
||||
```sh
|
||||
npm run native i18n:extract-used-strings -- "$PWD/used-strings.json" "域插件-1" <插件1源路径> "域插件-2" <插件2源路径> ...
|
||||
```
|
||||
|
||||
## 提供自有翻译(针对仅限原生平台使用的字符串)
|
||||
|
||||
获取原生平台使用的字符串列表后,需要对字符串进行翻译。但此过程不在原生版本支持范围内,需要您自行提供翻译方案。
|
||||
|
||||
通过编辑器初始化时传递的 `translations` 初始属性注入翻译数据:
|
||||
- [Android 参考](https://github.com/WordPress/gutenberg/blob/72854b4d6b09bd7fb7f996a5c55dd3cc0613ddf8/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt#L34)
|
||||
- [iOS 参考](https://github.com/WordPress/gutenberg/blob/72854b4d6b09bd7fb7f996a5c55dd3cc0613ddf8/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift#L39-L43)
|
||||
|
||||
由于移动客户端集成方案具有特异性且实现方式多样,本文不赘述如何通过 `translations` 初始属性集成翻译数据。但需确保通过该属性提供翻译,因为编辑器会负责将其与内置翻译进行合并。
|
||||
|
||||
**注意:** 与编辑器内置字符串相匹配的翻译将被覆盖。
|
||||
|
||||
## 获取翻译文件(针对跨平台使用的字符串)
|
||||
|
||||
翻译文件本质上是包含每个字符串翻译键值对的 JSON 对象。这些内容从 [translate.wordpress.org](https://translate.wordpress.org/) 获取,该网站存储了 WordPress 及 Gutenberg 等插件的翻译数据。
|
||||
|
||||
这些文件可缓存至指定文件夹并进行优化。同时会生成作为导入入口点的索引文件。
|
||||
|
||||
获取的翻译文件包含插件所有可翻译字符串,包括编辑器原生版本未使用的字符串。但可通过已使用字符串 JSON 文件过滤未引用的字符串来减小文件体积。
|
||||
|
||||
默认情况下,在安装依赖项时,如果缓存不存在,可能会下载未优化的 Gutenberg 翻译文件并存放于 `i18n-cache` 文件夹。
|
||||
|
||||
编辑器初始化时会导入这些翻译文件中的字符串([参考](https://github.com/WordPress/gutenberg/blob/154918b5770ac07c851169eaa35961c636eac5ba/packages/react-native-editor/src/index.js#L43-L49)),这些字符串将与通过 `translations` 初始属性提供的额外翻译进行合并。
|
||||
|
||||
### NPM 命令
|
||||
|
||||
获取未优化翻译:
|
||||
```sh
|
||||
npm run native i18n:fetch-translations -- "gutenberg" <输出路径>
|
||||
```
|
||||
|
||||
***注意:** 需要传入绝对路径,否则会以 `packages/react-native-editor` 作为相对路径的根目录*
|
||||
|
||||
获取优化翻译:
|
||||
```sh
|
||||
npm run native i18n:fetch-translations -- "gutenberg" <输出路径> <已使用字符串文件>
|
||||
```
|
||||
273
contributors/code/react-native/osx-setup-guide.md
Normal file
273
contributors/code/react-native/osx-setup-guide.md
Normal file
@@ -0,0 +1,273 @@
|
||||
### 运行演示应用
|
||||
|
||||
启动 Metro 打包工具:
|
||||
|
||||
```
|
||||
npm run native start:reset
|
||||
```
|
||||
|
||||
在另一个终端中运行以下命令,在 Android 模拟器中启动演示应用(如果模拟器尚未运行,此命令也会自动启动模拟器):
|
||||
|
||||
```
|
||||
npm run native android
|
||||
```
|
||||
|
||||
稍等片刻后,我们将看到类似以下界面:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/android-simulator.png" width="700px" alt="Android模拟器中区块编辑器的截图">
|
||||
|
||||
## 单元测试
|
||||
|
||||
```sh
|
||||
npm run test:native
|
||||
```
|
||||
|
||||
## 集成测试
|
||||
|
||||
[Appium](https://appium.io/) 自带诊断工具。通过以下命令运行:
|
||||
|
||||
```sh
|
||||
npx appium-doctor
|
||||
```
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.20.16.png" width="700px" alt="终端中运行的appium-doctor工具截图">
|
||||
|
||||
请解决所有必需的依赖项。
|
||||
|
||||
### iOS 集成测试
|
||||
|
||||
若能确保 iOS 本地环境正常运行,iOS 端到端测试将十分简单。首先停止所有正在运行的 Metro 进程(之前通过 `npm run native start:reset` 启动)。
|
||||
|
||||
然后在终端中输入:
|
||||
|
||||
```sh
|
||||
npm run native test:e2e:ios:local
|
||||
```
|
||||
|
||||
通过指定文件名可运行部分测试用例:
|
||||
|
||||
```sh
|
||||
npm run native test:e2e:ios:local gutenberg-editor-paragraph.test.js
|
||||
```
|
||||
|
||||
若一切顺利,将呈现如下效果:
|
||||
|
||||
<video src="https://user-images.githubusercontent.com/1270189/137403353-2a8ded47-5c7c-4f99-b2cc-fa6def4b4990.mp4" data-canonical-src="https://user-images.githubusercontent.com/1270189/137403353-2a8ded47-5c7c-4f99-b2cc-fa6def4b4990.mp4" controls="controls" muted="muted" class="d-block rounded-bottom-2 width-fit" style="max-height:640px;" alt="iOS模拟器中区块编辑器集成测试演示视频"></video>
|
||||
|
||||
### Android 集成测试
|
||||
|
||||
**创建新的虚拟设备**,需与 [packages/react-native-editor/**device-tests**/helpers/caps.js](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/__device-tests__/helpers/caps.js#L30) 中指定的设备参数匹配。截至本文撰写时,需使用 Pixel 3 XL 镜像配合 Android 9(API 28)系统。
|
||||
|
||||
首先启动虚拟设备:点击手机图标进入 AVD 管理器,然后点击绿色启动按钮。
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/adv-integration.png" width="700px" alt="启动Android模拟器的操作截图">
|
||||
|
||||
确保没有 Metro 进程正在运行(之前通过 `npm run native start:reset` 启动)。
|
||||
|
||||
然后在终端中运行:
|
||||
|
||||
```sh
|
||||
npm run native test:e2e:android:local
|
||||
```
|
||||
|
||||
通过指定文件名可运行部分测试用例:
|
||||
|
||||
```
|
||||
npm run native test:e2e:android:local gutenberg-editor-paragraph.test.js
|
||||
```
|
||||
|
||||
稍等片刻后应显示:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/CleanShot-2021-10-27-at-15.28.22.png" width="700px" alt="Android模拟器中区块编辑器集成测试截图">
|
||||
|
||||
# React Native 开发环境配置指南(macOS 版)
|
||||
|
||||
是否对移动端原生编辑器开发感兴趣?本指南将手把手带您完成开发环境配置!
|
||||
|
||||
请注意,本文说明主要针对 macOS 环境。若需配置其他环境,请参考 [React Native 快速入门文档](https://reactnative.dev/docs/environment-setup) 获取相关指引和步骤。
|
||||
|
||||
## 克隆 Gutenberg 项目
|
||||
|
||||
```sh
|
||||
git clone git@github.com:WordPress/gutenberg.git
|
||||
```
|
||||
|
||||
### 安装 node 与 npm
|
||||
|
||||
若您同时参与多个 JS 项目,建议使用 node 版本管理器。管理器可让您自由切换不同的 node 和 npm 版本。
|
||||
|
||||
我们推荐使用 [nvm](https://github.com/nvm-sh/nvm)。
|
||||
|
||||
安装 nvm 后,在克隆项目的根目录下执行:
|
||||
|
||||
```sh
|
||||
nvm install 'lts/*'
|
||||
nvm alias default 'lts/*' # 设置为新终端打开时的默认版本
|
||||
nvm use # 切换至项目配置版本
|
||||
```
|
||||
|
||||
随后安装依赖:
|
||||
|
||||
```
|
||||
npm ci
|
||||
```
|
||||
|
||||
### 已有旧版 Gutenberg 代码?
|
||||
|
||||
若您已持有 Gutenberg 代码,请务必彻底清理 `node_modules` 并重新安装依赖。
|
||||
这将有助于避免后续出现错误。
|
||||
|
||||
```sh
|
||||
npm run distclean
|
||||
npm ci
|
||||
```
|
||||
|
||||
## iOS 环境配置
|
||||
|
||||
### CocoaPods 依赖管理
|
||||
|
||||
需要安装 [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) 来获取 React 及第三方依赖。安装步骤因 Ruby 管理方式而异。
|
||||
|
||||
#### 系统自带 Ruby
|
||||
|
||||
若使用 MacOS 默认 Ruby,需通过 `sudo` 命令安装 Cocoapods:
|
||||
|
||||
```
|
||||
sudo gem install cocoapods
|
||||
```
|
||||
|
||||
注意:Mac M1 芯片与 Cocoapods 存在兼容性问题。若遇安装问题,可尝试执行以下命令安装 ffi 包(确保以正确架构安装 pods):
|
||||
|
||||
```
|
||||
sudo arch -x86_64 gem install ffi
|
||||
arch -x86_64 pod install
|
||||
```
|
||||
|
||||
#### Ruby 版本管理器
|
||||
|
||||
若使用 Ruby 版本管理器,可能无需手动安装 Cocoapods 或 `ffi` 包。请参照所选管理器的文档进行操作。
|
||||
|
||||
若需在 [WordPress iOS 应用](https://github.com/wordpress-mobile/WordPress-iOS) 中运行 Gutenberg(而非仅演示应用),推荐使用 [`rbenv`](https://github.com/rbenv/rbenv) 管理器。
|
||||
|
||||
### 配置 Xcode
|
||||
|
||||
通过 App Store 安装 [Xcode](https://developer.apple.com/xcode/) 后启动:
|
||||
|
||||
- 接受许可协议
|
||||
- 确认 `Xcode > 偏好设置 > 位置 > 命令行工具` 指向当前 Xcode 版本
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/xcode-command-line-tools.png" width="700px" alt="Xcode 命令行工具设置界面截图">
|
||||
|
||||
### 环境诊断工具
|
||||
|
||||
可通过 [react-native doctor](https://reactnative.dev/blog/2019/11/18/react-native-doctor) 检测开发环境缺失项。在 Gutenberg 项目根目录或 `/packages/react-native-editor` 文件夹内运行:
|
||||
|
||||
```sh
|
||||
npx @react-native-community/cli doctor
|
||||
```
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-doctor.png" width="700px" alt="终端中运行的 react-native-community/cli doctor 工具截图">
|
||||
|
||||
尝试让 `doctor` 工具修复所有“通用”和“iOS”问题(此时“Android”项显示❌无需担心,后续会处理!)
|
||||
|
||||
### 运行演示应用
|
||||
|
||||
当所有通用和 iOS 问题解决后,尝试运行:
|
||||
|
||||
```
|
||||
npm run native start:reset # 启动 metro 打包器
|
||||
```
|
||||
|
||||
另开终端窗口执行:
|
||||
|
||||
```
|
||||
npm run native ios
|
||||
```
|
||||
|
||||
等待构建完成后,演示应用将在 iOS 模拟器中运行:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/iOS-Simulator.png" width="700px" alt="iOS 模拟器中的区块编辑器运行截图" />
|
||||
|
||||
## Android 环境配置
|
||||
|
||||
### Java开发工具包(JDK)
|
||||
|
||||
[React Native文档](https://reactnative.dev/docs/environment-setup)推荐的JDK名为Azul Zulu。可通过[Homebrew](https://brew.sh/)安装。安装Homebrew后,在终端执行以下命令:
|
||||
|
||||
```
|
||||
brew tap homebrew/cask-versions
|
||||
brew install --cask zulu11
|
||||
```
|
||||
|
||||
若系统已安装JDK,需确保版本为JDK 11或更高。
|
||||
|
||||
### 配置Android Studio
|
||||
|
||||
编译Android应用需先[下载Android Studio](https://developer.android.com/studio)。
|
||||
|
||||
打开现有项目,选择已克隆的Gutenberg文件夹。
|
||||
|
||||
点击下图高亮的立方体图标进入SDK管理器,也可通过`工具 > SDK管理器`进入:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-package-manager.png" width="700px" alt="Android Studio中包管理器按钮位置示意图">
|
||||
|
||||
在此界面可下载SDK平台、软件包及其他工具。需勾选“显示包详细信息”查看特定版本,因为构建过程对E2E测试和开发环境有特定版本要求:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-show-package-details.png" width="700px" alt="Android Studio包管理器界面,突出显示包详细信息复选框">
|
||||
|
||||
根据[build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle)勾选所有相关软件包,点击“应用”开始下载。node_modules中的build.gradle文件可能包含其他依赖项。
|
||||
|
||||
若不想逐行查阅文件,系统会在堆栈跟踪中提示缺失包,但此方法需多次尝试。
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-editor-build-gradle.png" width="700px" alt="build.gradle配置文件截图">
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk.png" width="700px" alt="包管理器显示SDK平台界面">
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-sdk-tools.png" width="700px" alt="包管理器显示SDK工具界面">
|
||||
|
||||
### 更新路径配置
|
||||
|
||||
导出以下环境变量并更新$PATH。若终端使用zsh可添加到`~/.zshrc`文件,若使用bash则添加到`~/.bash_profile`:
|
||||
|
||||
```sh
|
||||
### Android Studio自带的Java:
|
||||
export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jre/Contents/Home
|
||||
### Android Home可在Android Studio中配置:偏好设置 > 系统设置 > Android SDK
|
||||
export ANDROID_HOME=$HOME/Library/Android/sdk
|
||||
export PATH=$PATH:$ANDROID_HOME/emulator
|
||||
export PATH=$PATH:$ANDROID_HOME/tools
|
||||
export PATH=$PATH:$ANDROID_HOME/tools/bin
|
||||
export PATH=$PATH:$ANDROID_HOME/platform-tools
|
||||
```
|
||||
|
||||
保存后执行source命令或重启终端使配置生效:
|
||||
|
||||
```sh
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
或
|
||||
|
||||
```sh
|
||||
source ~/.bash_profile
|
||||
```
|
||||
|
||||
若找不到SDK路径,可通过Android Studio > 偏好设置 > 系统设置 > Android SDK验证位置:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/sdk-path.png" width="700px" alt="Android Studio中SDK路径定位示意图">
|
||||
|
||||
### 创建设备镜像
|
||||
|
||||
点击右下角带Android标识的手机图标创建虚拟设备镜像:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-android-device-manager-button.png" width="700px" alt="Android设备管理器按钮位置示意图">
|
||||
|
||||
进入“Android虚拟设备管理器(AVD)”,点击“创建虚拟设备”,选择手机类型:
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-android-select-hardware.png" width="700px" alt="虚拟设备配置界面截图">
|
||||
|
||||
选择目标SDK版本(对应[build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle)中的targetSdkVersion设置):
|
||||
|
||||
<img src="https://developer.wordpress.org/files/2021/10/react-native-adv-system-image.png" width="700px" alt="设备管理器中选择系统镜像界面">
|
||||
|
||||
可根据需要调整高级设置(可选),最后点击完成。
|
||||
23
contributors/code/release/README.md
Normal file
23
contributors/code/release/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Gutenberg 发布流程
|
||||
|
||||
GitHub 上的 [Gutenberg 代码库](https://github.com/WordPress/gutenberg) 用于执行多种类型的发布。本页概述了不同的发布流程,并为您提供每种类型对应的文档指引。
|
||||
|
||||
## 前置条件
|
||||
|
||||
在开始任何发布流程之前,需满足以下要求才能成功发布稳定版的 Gutenberg 插件:
|
||||
|
||||
- 成为 [Gutenberg 开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams) 的成员。这将使您具备启动与发布流程相关的 GitHub 操作以及将拉取请求(PR)回溯到发布分支的权限。
|
||||
- 拥有 [Make WordPress Core](https://make.wordpress.org/core) 博客的写权限。这将允许您起草发布文稿。
|
||||
- 获得 Gutenberg 核心团队成员的批准,以便将新版本的 Gutenberg 上传至 WordPress.org 插件目录。
|
||||
|
||||
## 插件发布
|
||||
|
||||
插件发布涉及创建新版本的 Gutenberg 插件并将其发布到 WordPress.org 插件目录。此流程包括创建候选版本、测试以及最终发布。
|
||||
|
||||
有关如何执行插件发布的详细说明,请参阅 [Gutenberg 插件发布](https://developer.wordpress.org/block-editor/contributors/code/release/plugin-release/)。
|
||||
|
||||
## 软件包发布
|
||||
|
||||
软件包发布涉及将更新后的 WordPress 软件包版本发布到 npm。此流程包括与插件发布、WordPress 核心更新以及独立的错误修复发布同步。
|
||||
|
||||
有关软件包发布和 WordPress 核心更新的完整说明,请参阅 [发布软件包至 NPM 及 WordPress 核心更新](https://developer.wordpress.org/block-editor/contributors/code/release/package-release-and-core-updates/)。
|
||||
156
contributors/code/release/package-release-and-core-updates.md
Normal file
156
contributors/code/release/package-release-and-core-updates.md
Normal file
@@ -0,0 +1,156 @@
|
||||
- 系统会多次要求您输入一次性密码(OTP),即您使用的双因素认证应用生成的验证码。根据待发布软件包的数量,您可能需要输入多个OTTP,因为这些代码往往在所有软件包发布前就会失效。
|
||||
- 若发布流程意外中断(可能因超时或输错OTP导致),可通过执行 [`npx lerna publish from-package`](https://lerna.js.org/docs/features/version-and-publish#from-package) 命令恢复操作。
|
||||
|
||||
6. 最后,当 npm 软件包发布完成后,请将 Lerna 生成的提交记录("发布"和更新日志更新)遴选合并到 Gutenberg 的 `trunk` 分支。
|
||||
|
||||
## 开发版发布
|
||||
|
||||
如[同步 Gutenberg 插件](#synchronizing-the-gutenberg-plugin)章节所述,软件包每两周会从 `wp/latest` 分支发布一次。此外,开发者可随时通过开发版来测试 `trunk` 分支即将推出的变更。我们利用[软件包分发标签](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag)功能,使得根据 npm 指南使用未来版本代码成为可能:
|
||||
|
||||
> 默认情况下,npm 使用 `latest` 标签标识软件包当前版本,执行 `npm install <pkg>`(不附带任何 `@<version>` 或 `@<tag>` 限定符)时会安装 `latest` 标签对应的版本。通常项目仅将 `latest` 标签用于稳定版本,其他标签则用于预发布版等不稳定版本。
|
||||
|
||||
在本项目中,我们使用 `next` 分发标签标识开发版代码。开发者如需安装该版本的软件包,需输入:
|
||||
|
||||
```bash
|
||||
npm install @wordpress/components@next
|
||||
```
|
||||
|
||||
若要启动 npm 软件包开发版的发布流程,请前往 Gutenberg 的 GitHub 仓库的 Actions 标签页,找到["发布 npm 软件包"操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意标有“此工作流由 `workflow_dispatch` 事件触发”的蓝色横幅,展开其右侧的“运行工作流”下拉菜单。
|
||||
|
||||

|
||||
|
||||
如需将开发版软件包发布至 npm,请从“发布类型”下拉菜单选择 `development`,并保持“WordPress 主版本号”输入框为空。最后点击绿色“运行工作流”按钮。这将触发 npm 发布任务,该任务需要经过 Gutenberg 核心团队成员的批准。请找到当前发布任务对应的["发布 npm 软件包"操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。
|
||||
|
||||
幕后流程中,发布操作完全通过 `./bin/plugin/cli.js npm-next` 命令实现自动化。该命令会确保 `wp/next` 分支与为 Gutenberg 插件创建的最新发布分支(`release/X.Y`)保持同步。为避免软件包版本冲突,我们始终会加入最新提交的 SHA 值,例如:`@wordpress/block-editor@5.2.10-next.645224df70.0`。
|
||||
|
||||
[插件代码库]: https://plugins.trac.wordpress.org/browser/gutenberg/
|
||||
[软件包发布流程]: https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#releasing-packages
|
||||
|
||||
# 发布到 NPM 的软件包与 WordPress 核心更新
|
||||
|
||||
Gutenberg 代码库遵循 [WordPress SVN 代码库](https://make.wordpress.org/core/handbook/about/release-cycle/)的分支策略,适用于每个主要的 WordPress 版本发布。除此之外,它还包含另外两个特殊的控制 npm 发布流程的分支:
|
||||
|
||||
- `wp/latest` 分支包含与通过 `latest` 分发标签发布到 npm 的相同版本的软件包。此分支的目标是与最新的 Gutenberg 插件版本保持同步,唯一的例外是计划外的 [Bugfix 发布](#独立错误修复软件包发布)。
|
||||
- `wp/next` 分支包含与通过 `next` 分发标签发布到 npm 的相同版本的软件包。它始终与 `trunk` 分支保持同步。项目应仅将这些软件包用于开发或测试目的。
|
||||
- 针对特定 WordPress 主要版本(包括其后续的次要版本更新)的 Gutenberg 分支 `wp/X.Y`(例如 `wp/6.2`)会在计划包含在下一个主要 WordPress 版本中的最后一次发布后不久,基于当前的 Gutenberg 插件发布分支 `release/X.Y`(例如 `release/15.1`)创建。
|
||||
|
||||
发布类型及其时间表:
|
||||
|
||||
- [同步 Gutenberg 插件](#同步-gutenberg-插件)(`latest` 分发标签)—— 基于新创建的 `release/X.Y`(例如 `release/12.8`)分支,每两周自动发布一次,该分支包含 Gutenberg 插件的 RC1 版本。
|
||||
- [WordPress 发布](#wordpress-发布)(`wp-X.Y` 分发标签,例如 `wp-6.2`)—— 根据需求从 `wp/X.Y`(例如 `wp/6.2`)分支触发发布。一旦达到 WordPress 主要发布周期中的某个时间点(在 Beta 1 之前不久),我们仅从 Gutenberg 代码库中挑选提交到 WordPress 核心,此时我们使用 `wp/X.Y` 分支(从 `release/X.Y` 分支创建,例如 `release/15.1`)通过 `wp-X.Y` 分发标签进行 npm 发布。也可以使用旧分支将错误或安全修复反向移植到相应旧版本的 WordPress 核心。
|
||||
- [开发版本发布](#开发版本发布)(`next` 分发标签)—— 也可以在需要测试即将到来的更改时随时执行开发版本发布。
|
||||
|
||||
还可以选择随时执行[独立错误修复软件包发布](#独立错误修复软件包发布)。这应仅用于在常规周期之外必须发布到 _npm_ 的关键错误修复或安全发布。
|
||||
|
||||
## 同步 Gutenberg 插件
|
||||
|
||||
对于每个 Gutenberg 插件发布,我们还会将更新后的 WordPress 软件包版本发布到 npm。这是通过处理 Gutenberg 插件发布的[发布工具](https://github.com/WordPress/gutenberg/blob/trunk/.github/workflows/build-plugin-zip.yml)自动完成的。成功的 RC1 发布会触发 npm 发布任务,这需要由 Gutenberg 核心团队成员批准。找到新版本的 ["构建 Gutenberg 插件 Zip" 工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml),并[批准](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)它。
|
||||
|
||||
我们特意在 Gutenberg RC1 发布时,使用 Gutenberg 发布分支 `release/X.Y`(例如 `release/12.7`)的内容更新 Gutenberg 代码库中的 `wp/latest` 分支。这样做是为了确保 `wp/latest` 分支尽可能接近最新版本的 Gutenberg 插件。同时,这实际上也消除了在将提交反向移植到 `trunk` 时,因发布过程中应用到 `package.json` 和 `CHANGELOG.md` 文件的更新而产生冲突的可能性。过去,当我们在常规 Gutenberg 发布一周后进行 npm 发布时,在这方面遇到了许多问题。在将新的软件包版本发布到 npm 时,我们至少选择 `minor` 版本升级,以便为未来的错误修复或安全发布留出空间。
|
||||
|
||||
在幕后,所有步骤都通过 `./bin/plugin/cli.js npm-latest` 命令自动完成。作为记录,手动过程将非常接近以下步骤:
|
||||
|
||||
1. 确保 WordPress `trunk` 分支对增强功能开放。
|
||||
2. 使用 `git fetch` 获取最后发布的 Gutenberg 发布分支。
|
||||
3. 检出 `wp/latest` 分支。
|
||||
4. 从当前分支中删除所有文件:`git rm -r .`。
|
||||
5. 从发布分支检出所有文件:`git checkout release/x.x -- .`。
|
||||
6. 使用 `git commit -m "Merge changes published in the Gutenberg plugin vX.X release"` 将所有更改提交到 `wp/latest` 分支,并推送到代码库。
|
||||
7. 使用计算出的新发布版本更新软件包的 `CHANGELOG.md` 文件,并提交到 `wp/latest` 分支。假设软件包版本使用 `major.minor.patch` 格式编写,请确保至少应用 `minor` 版本升级。例如,如果要发布的软件包的 CHANGELOG 指示下一个未发布版本是 `5.6.1`,则在 `minor` 版本的情况下选择 `5.7.0` 作为版本。这一点很重要,因为如果需要为次要的 WordPress 发布进行错误修复,应保留补丁版本号(见下文)。
|
||||
8. 通过控制台登录 npm:`npm login`。请注意,您应启用双因素认证(2FA)。
|
||||
9. 从 `wp/latest` 分支,使用 `npm ci` 安装 npm 依赖项。
|
||||
10. 运行脚本 `npx lerna publish --no-private`。
|
||||
- 当被要求选择每个软件包的版本号时,请选择更新的 CHANGELOG 文件中的值。
|
||||
- 您将被多次要求输入一次性密码(OTP)。这是您使用的 2FA 验证器应用程序中的代码。根据要发布的软件包数量,您可能需要输入多次 OTP,因为它们往往在所有软件包发布之前就过期了。
|
||||
- 如果发布过程未完成(可能是因为超时或输入了错误的 OTP),您可以通过 [`npx lerna publish from-package`](https://lerna.js.org/docs/features/version-and-publish#from-package) 恢复它。
|
||||
11. 最后,既然 npm 软件包已发布,将 lerna 创建的提交("Publish" 和 CHANGELOG 更新)挑选到 Gutenberg 的 `trunk` 分支中。
|
||||
|
||||
## WordPress 版本发布
|
||||
|
||||
当需要将错误修复或安全补丁移植到 WordPress 核心时,需遵循以下工作流程。适用场景包括:
|
||||
|
||||
- 在 WordPress 发布周期的 `beta` 和 `RC` 阶段,当发布分支 `wp/X.Y`(例如 `wp/5.7`)已存在时。
|
||||
- 针对 WordPress 小版本和安全版本发布(例如 `5.1.1`)。
|
||||
|
||||
1. 检出对应的 WordPress 主版本分支(若小版本为 `5.2.1`,则检出 `wp/5.2`)。
|
||||
2. 基于该分支创建功能分支,并将所需错误修复的合并提交拣选到该分支。可使用 [`npm run other:cherry-pick`](/docs/contributors/code/auto-cherry-picking.md) 脚本自动完成拣选操作。
|
||||
3. 基于此分支创建拉取请求,目标分支选择前述 WordPress 主版本分支。
|
||||
4. 点击 "Rebase and Merge" 按钮合并拉取请求以保留提交历史。
|
||||
|
||||
此时 `wp/X.Y` 分支已准备就绪可发布 npm 软件包。请前往 Gutenberg 的 GitHub 仓库 Actions 标签页,找到 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意蓝色横幅提示 "此工作流程具有 `workflow_dispatch` 事件触发器",展开右侧的 "Run workflow" 下拉菜单。
|
||||
|
||||

|
||||
|
||||
要为 WordPress 主版本发布 npm 软件包,请选择 `trunk` 作为运行工作流的分支(这意味着运行工作流所用的脚本来自 trunk 分支,但软件包本身将通过下文选择正确的"发布类型"后从发布分支发布),然后在"发布类型"下拉菜单中选择 `wp`,并在"WordPress 主版本"输入框中填入 `X.Y`(例如 `5.2`)。最后点击绿色 "Run workflow" 按钮。这将触发 npm 发布任务,需经 Gutenberg 核心团队成员批准。请找到本次发布的 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。
|
||||
|
||||
备案记录:手动操作流程如下:
|
||||
|
||||
1. 检出之前使用的 WordPress 分支(例如 `wp/5.2`)。
|
||||
2. 执行 `git pull`。
|
||||
3. 运行 `npx lerna publish patch --no-private --dist-tag wp-5.2` 命令(详见[软件包发布流程]),但在选择各软件包版本号时(假设版本号格式为 `主版本.次版本.修订号`),请确保仅提升 `修订号`。例如,若该 WordPress 分支最后发布的软件包版本为 `5.6.0`,则选择 `5.6.1` 作为新版本。
|
||||
|
||||
**注意:** 对于 WordPress `5.0` 和 `5.1` 版本,曾采用不同的发布流程。这意味着针对这两个版本选择 npm 软件包版本时,可能无法使用下一个 `修订号` 版本(因其可能已被占用)。此时应使用"元数据"修饰符。例如,若该 WordPress 分支最后发布的软件包版本为 `5.6.1`,则选择 `5.6.1+patch.1` 作为版本号。
|
||||
|
||||
3. (可选)更新已发布软件包的 `CHANGELOG.md` 文件,添加新发布版本信息,并提交至对应分支(例如 `wp/5.2`)。
|
||||
4. 将 CHANGELOG 更新提交(如有)拣选至 Gutenberg 的 `trunk` 分支。
|
||||
|
||||
至此 npm 软件包应已准备就绪,可以创建补丁并提交至对应的 WordPress SVN 分支。
|
||||
|
||||
## 独立错误修复包发布
|
||||
|
||||
当软件包需要在常规发布周期外向 _npm_ 发布错误修复或安全更新时,需遵循以下工作流程。
|
||||
|
||||
注意:`trunk` 和 `wp/latest` 分支均为受限分支,仅 Gutenberg 核心团队具有 _推送_ 权限。
|
||||
|
||||
识别需要从代码库 `trunk` 分支移植到 `wp/latest` 分支的拉取请求对应的提交哈希值。
|
||||
|
||||
接下来需要准备 `wp/latest` 分支以发布软件包至 _npm_。
|
||||
|
||||
打开终端并执行以下步骤:
|
||||
|
||||
1. `git checkout trunk`
|
||||
2. `git pull`
|
||||
3. `git checkout wp/latest`
|
||||
4. `git pull`
|
||||
|
||||
在移植提交前,请检查 `wp/latest` 分支是否存在待发布的软件包:
|
||||
|
||||
1. `git checkout wp/latest`
|
||||
2. `npx lerna updated`
|
||||
|
||||
现在将提交从 `trunk` 分支 _拣选_ 到 `wp/latest` 分支,若提交为拉取请求的合并提交,请使用 `-m 1 commithash`:
|
||||
|
||||
1. `git cherry-pick -m 1 cb150a2`
|
||||
2. `git push`
|
||||
|
||||
在等待 GitHub Actions 构建 [通过 `wp/latest` 分支检查](https://github.com/WordPress/gutenberg/actions?query=branch%3Awp%2Ftrunk) 期间,识别并开始更新 `CHANGELOG.md` 文件:
|
||||
|
||||
1. `git checkout wp/latest`
|
||||
2. `npx lerna updated`
|
||||
示例:
|
||||
```shell
|
||||
npx lerna updated
|
||||
@wordpress/e2e-tests
|
||||
@wordpress/jest-preset-default
|
||||
@wordpress/scripts
|
||||
lerna success found 3 packages ready to publish
|
||||
```
|
||||
|
||||
检查当前 `CHANGELOG.md` 文件中列出的版本,浏览软件包提交历史(例如 [@wordpress/scripts](https://github.com/WordPress/gutenberg/commits/HEAD/packages/scripts)),注意查找 _"chore(release): publish"_ 和 _"Update changelogs"_ 提交以确定最近的版本更新,然后查看自最近发布以来的提交记录,有助于了解自上次发布以来的变更内容。
|
||||
|
||||
注意:您可能会发现某些软件包的当前版本不是最新的,如遇此情况,建议更新先前发布的版本。
|
||||
|
||||
此时 `wp/latest` 分支已准备就绪可发布 npm 软件包。请前往 Gutenberg 的 GitHub 仓库 Actions 标签页,找到 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意蓝色横幅提示 "此工作流程具有 `workflow_dispatch` 事件触发器",展开右侧的 "Run workflow" 下拉菜单。
|
||||
|
||||

|
||||
|
||||
要发布包含错误修复的 npm 软件包,请在"发布类型"下拉菜单中选择 `bugfix`,并保持"WordPress 主版本"输入框为空。最后点击绿色 "Run workflow" 按钮。这将触发 npm 发布任务,需经 Gutenberg 核心团队成员批准。请找到本次发布的 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。
|
||||
|
||||
在后台,后续流程由 `./bin/plugin/cli.js npm-bugfix` 命令自动完成。备案记录:手动操作流程与以下步骤高度相似:
|
||||
|
||||
1. 检出 `wp/latest` 分支。
|
||||
2. 使用计算得出的新发布版本更新各软件包的 `CHANGELOG.md` 文件,并提交至 `wp/latest` 分支。
|
||||
3. 通过控制台登录 npm:`npm login`。注意应启用双因素认证。
|
||||
4. 在 `wp/latest` 分支上,运行 `npm ci` 安装 npm 依赖项。
|
||||
5. 运行脚本 `npx lerna publish --no-private`。
|
||||
- 当提示选择各软件包版本号时,请选择已更新的 CHANGELOG 文件中的对应值。
|
||||
431
contributors/code/release/plugin-release.md
Normal file
431
contributors/code/release/plugin-release.md
Normal file
@@ -0,0 +1,431 @@
|
||||
### 发布 @wordpress 软件包至 NPM
|
||||
|
||||
作为版本发布流程的一部分,所有 @wordpress 软件包都会发布到 NPM。当[构建 Gutenberg 插件压缩包](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)操作完成草稿版本的创建后,你可能会看到一条提示,要求具备相应权限的人员触发[发布 npm 软件包](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)操作。
|
||||
|
||||
这条提示存在误导性。你无需手动执行任何操作来发布 @wordpress 软件包至 NPM。该过程已实现自动化,会在发布说明正式发布后自动运行。
|
||||
|
||||
### 查看版本草稿
|
||||
|
||||
工作流完成后,你可以在 [Gutenberg 发布页面](https://github.com/WordPress/gutenberg/releases)找到版本草稿。该草稿会预填充基于此版本先前候选版(RC)的更新日志条目,以及后续精选至发布分支的任何更改。因此,当发布某个系列的首个稳定版本时,请删除所有仅作为参考信息的候选版标题,并将最新更改移至正确章节(详见下文)。
|
||||
|
||||
### 整理版本更新日志
|
||||
|
||||
整理更新日志的最佳时机是在候选版工作流首次创建时。此时会触发更新日志自动化流程,生成初始版本的更新日志。虽然该过程主要依赖自动化,但正如前文所述,其效果很大程度上取决于里程碑中 PR 的标签是否准确。
|
||||
|
||||
稳定版发布流程会提取各候选版的更新日志并整合至稳定版。但需特别注意:稳定版仅会保留首次生成的更新日志内容(即 RC1 发布时的版本),后续对 RC1 更新日志的修改将不会同步至稳定版。
|
||||
|
||||
这意味着如果你在发布 RC1 前就完成了整个更新日志的整理,那么除了后续 RC2 或 RC3 版本中少量需要追加至稳定版的内容外,稳定版发布时无需再额外整理更新日志。
|
||||
|
||||
当版本更新日志出现在草稿中后,请仔细审阅并编辑内容,确保其清晰准确。此环节不必急于求成,重点在于保证发布说明的条理性,你可以随时保存草稿后续继续完善。
|
||||
|
||||
若担心在整理更新日志期间他人无法获取候选版本,可通过 Slack 频道 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) 分享发布构件,这样他人即可在你完善更新日志时使用候选版本。
|
||||
|
||||
以下是一些编写清晰简明更新日志的补充建议:
|
||||
|
||||
- 将`杂项`分类下的所有条目移至更合适的章节
|
||||
- 修正拼写错误并优化表述,确保面向插件用户或持续关注开发进展的受众时易于理解
|
||||
- 适时创建新分组并调整 PR 归属
|
||||
- 当多个 PR 关联同一任务时(如后续修正的 PR),尝试将其合并为单一条目。典型范例包括:为性能优化移除 Lodash 的系列 PR、用 Playwright 替代 Puppeteer 的端到端测试更新、或将公共组件迁移至 TypeScript 的相关工作
|
||||
- 若关联 PR 集的子任务内容充实,可考虑采用嵌套列表进行组织
|
||||
- 删除同一版本中相互抵消的 PR(即代码净变化为零的还原类 PR)
|
||||
- 移除所有仅涉及移动端应用的 PR(唯一例外是同时影响网页端功能的移动端 PR)
|
||||
- 若某子标题下仅列有一条 PR,则移除该子标题并将 PR 移至包含多条目的相邻子标题
|
||||
|
||||
### 撰写版本发布公告
|
||||
|
||||
版本管理员需依据[谷歌文档模板](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit)起草版本发布公告。鉴于公告内容的特殊性,若事先达成共识,相关工作可拆分委托给团队成员完成。草案完成后需进行同行评审。
|
||||
|
||||
### 发布版本公告
|
||||
|
||||
内容准备就绪后,拥有[make.wordpress.org/core](https://make.wordpress.org/core/)发布权限的作者将创建新草稿并导入内容。公告需包含以下标签:
|
||||
|
||||
- [#block-editor](https://make.wordpress.org/core/tag/block-editor/)
|
||||
- [#core-editor](https://make.wordpress.org/core/tag/core-editor/)
|
||||
- [#gutenberg](https://make.wordpress.org/core/tag/gutenberg/)
|
||||
- [#gutenberg-new](https://make.wordpress.org/core/tag/gutenberg-new/)
|
||||
|
||||
作者需开启公告的公开预览功能,并申请最终同行评审。这一流程符合[make/core发布指南](https://make.wordpress.org/core/handbook/best-practices/post-comment-guidelines/#peer-review)的要求。
|
||||
|
||||
最终发布应在稳定版于WordPress.org上线后进行,此举有助于外部媒体同步传播版本信息。
|
||||
|
||||
## 招募下个版本的志愿者
|
||||
|
||||
完成版本发布后,请在#core-editor Slack频道发布信息,招募负责下个Gutenberg版本的志愿者。
|
||||
|
||||
具体示例可[参考此处](https://wordpress.slack.com/archives/C02QB2JS7/p1751595983193709)。
|
||||
|
||||
## 创建次要版本
|
||||
|
||||
有时需要为插件创建次要版本(即X.Y.**Z**),通常用于快速修复严重回归问题或程序缺陷。虽然`Backport to Gutenberg Minor Release`标签常用于标识需纳入次要版本的PR,但作为版本协调员,您也可能通过Slack收到非正式通知。即便如此,仍需确保所有相关PR都正确标注。
|
||||
|
||||
需要注意:次要版本不会单独创建分支(例如`release/12.5.0`),而是以[标签](https://github.com/WordPress/gutenberg/releases/tag/v12.5.1)形式存在。
|
||||
|
||||
次要版本的发布流程与主插件版本基本一致(参见上文),但存在重要差异。请务必通读完整指南后再进行操作。
|
||||
|
||||
### 更新发布分支
|
||||
|
||||
次要版本应仅包含必要的特定提交。操作时需在本地检出前一个主要稳定版本(非RC版本)分支(如`release/12.5`),然后精选所需提交至该分支。
|
||||
|
||||
<div class="callout callout-alert">
|
||||
若新版本已存在RC版本,您必须将相同提交精选至对应发布分支,因为它们不会自动包含。例如:若您正准备发布12.5的新次要版本,仅向<code>release/12.5</code>分支精选了提交,但12.6.0-rc.1已发布,则需将相同提交精选至<code>release/12.6</code>分支,否则这些提交不会包含在后续12.6版本中!通常建议与下个版本的发布协调员协同处理此流程。
|
||||
</div>
|
||||
|
||||
精选过程可通过[`npm run cherry-pick`](/docs/contributors/code/auto-cherry-picking.md)脚本自动化执行,但运行脚本时请确保使用`Backport to Gutenberg Minor Release`标签。
|
||||
|
||||
同时必须确保所有纳入的PR都已分配至该次要版本对应的GitHub里程碑。需注意:PR合并时会自动分配至下一个稳定版本的里程碑,因此您需要在GitHub中逐一手动调整PR的里程碑归属。
|
||||
|
||||
例如,若您正在发布`12.5.4`版本,所有精选至该版本的PR必须从`12.6`里程碑移除,并重新分配至`12.5`里程碑。
|
||||
|
||||
完成精选后,可移除PR上的`Backport to Gutenberg Minor Release`标签。
|
||||
|
||||
当稳定发布分支整理就绪且PR分配正确里程碑后,即可将分支推送至GitHub,并通过GitHub网站图形界面继续发布流程。
|
||||
|
||||
### 运行小版本发布
|
||||
|
||||

|
||||
|
||||
前往 Gutenberg 的 GitHub 仓库的 Actions 标签页,找到 [“构建 Gutenberg 插件压缩包” 操作](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)。现在,你需要**仔细**根据当前插件发布版本的信息选择下一步操作:
|
||||
|
||||
**如果**上一个发布版本是**稳定版**(`X.Y.Z`,例如 `12.5.0`、`12.5.1` 等),请将 `Use workflow from` 字段保留为 `trunk`,并在文本输入框中指定 `stable`。工作流将自动创建一个小版本,根据需要递增 z 值(`x.y.(z+1)`)。
|
||||
|
||||
**但如果**上一个发布版本是 **RC 版本**(例如 `X.Y.0-rc.1`),你需要在创建发布时**手动**选择**稳定版本的发布分支**(例如 `12.5.0`)。如果不这样做,工作流将发布下一个主要的**稳定**版本(例如 `12.6.0`),而这并不是你想要的。
|
||||
|
||||
为此,在运行工作流时,从 `Use workflow from` 下拉菜单中选择适当的 `release/` 分支(例如 `release/12.5`),并在文本输入框中指定 `stable`。
|
||||
|
||||
#### 为之前的稳定版本创建小版本
|
||||
|
||||
即使已经发布了更新的稳定版本,也可以为任何发布分支创建小版本。这可以用于**任何**之前的发布分支,从而更灵活地向用户提供更新。过去,用户必须等待下一个稳定版本,可能需要几天时间。现在,可以根据需要迅速将修复推送到任何之前的发布分支。
|
||||
|
||||
该过程与上述已有 RC 版本时的流程相同:选择一个之前的发布分支,输入 `stable`,然后点击 “Run workflow”。发布将在 Gutenberg 的 GitHub 发布页面和 WordPress 核心仓库的 SVN 中以 `tag` 的形式发布到 [https://plugins.svn.wordpress.org/gutenberg/tags/](https://plugins.svn.wordpress.org/gutenberg/tags/)。SVN 的 `trunk` 目录不会被修改。
|
||||
|
||||
**重要提示:** 在发布由 [“构建插件压缩包” 工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) 创建的草稿时,请确保**取消勾选** “Set as last release” 复选框。如果不小心勾选了,[“将 Gutenberg 插件上传到 WordPress.org 插件仓库” 工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) 仍会正确将其**作为标签(并且不会替换 `trunk` 版本)** 上传到 WordPress 插件仓库的 SVN——工作流会执行一些版本计算以确定插件的发布方式——但你仍然需要在 GitHub 上通过将正确的发布设置为 `latest` 来修复状态,具体操作在 [发布页面](https://github.com/WordPress/gutenberg/releases/) 上进行!
|
||||
|
||||
### 故障排除
|
||||
|
||||
> 发布草稿已创建,但内容为空/包含错误消息
|
||||
|
||||
如果你忘记将正确的里程碑分配给你精选的 PR,那么生成的更新日志可能不符合预期。
|
||||
|
||||
务必手动验证更新日志中显示的 PR 是否与精选到发布分支的 PR 一致。
|
||||
|
||||
此外,如果发布仅包含一个 PR,但未将该 PR 分配到正确的里程碑,则在生成更新日志时会显示错误。在这种情况下,你可以编辑发布说明,手动添加缺失的 PR 的详细信息(格式可参考之前的发布)。
|
||||
|
||||
如果由于某种原因里程碑已关闭,你可以为了发布的目的重新打开它。
|
||||
|
||||
> 发布草稿仅包含 1 个资源文件,而其他发布包含 3 个。
|
||||
|
||||
这是正常现象。发布草稿仅包含插件压缩包。只有在发布后,其他资源才会生成并添加到发布中。
|
||||
|
||||
> 是否需要将点发布版本发布到 WordPress.org?
|
||||
|
||||
是的。方法与主要的插件发布流程相同。你需要 Gutenberg 核心团队或 Gutenberg 发布团队的成员批准发布工作流。
|
||||
|
||||
> 发布过程未能将版本更新提交精选到 `trunk` 分支。
|
||||
|
||||
首先,通过检查 `trunk` 上的最新提交是否包含版本更新提交来确认步骤失败。然后在发布分支上回退版本更新提交——使用命令 `git revert --no-edit {commitHash}`。最后,推送更改并重新开始发布过程。
|
||||
|
||||
# Gutenberg 插件发布指南
|
||||
|
||||
## 快速参考
|
||||
|
||||
### 时间安排
|
||||
|
||||
- 在里程碑日期(通常为周三)发布 RC1 版本
|
||||
- 下周三发布正式稳定版
|
||||
|
||||
### 常规发布流程
|
||||
|
||||
#### 步骤 1:准备工作
|
||||
|
||||
- 使用模板创建[发布工单](https://github.com/WordPress/gutenberg/issues/new?template=New_release.md)(可选操作,但有助于逐步执行流程)
|
||||
- 审核里程碑中的所有 PR 并添加适当标签(`[Type] Bug`、`[Type] Enhancement` 等)
|
||||
- 测试更新日志:`npm run other:changelog -- --milestone="Gutenberg X.Y"`
|
||||
|
||||
#### 步骤 2:构建发布版本
|
||||
|
||||
- 在 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack 频道发布公告
|
||||
- 前往 GitHub Actions → [构建插件压缩包工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)
|
||||
- 保持 `Use workflow from` 选项为 `trunk`(默认值)
|
||||
- 输入 `rc`(候选版本)或 `stable`(正式版本)
|
||||
- 点击 `Run workflow`
|
||||
- 当[GitHub Releases](https://github.com/WordPress/gutenberg/releases)中生成发布草稿后,立即发布以继续工作流
|
||||
- 仅限稳定版:等待团队批准上传至 WordPress.org——这是工作流的最后一步,用于将插件部署到插件目录([示例](https://github.com/WordPress/gutenberg/actions/runs/18559811968))
|
||||
|
||||
#### 步骤 3:编辑发布说明
|
||||
|
||||
- 在[GitHub Releases](https://github.com/WordPress/gutenberg/releases)中找到草稿
|
||||
- 清理更新日志:修正拼写错误,调整分类不当的条目,合并相关 PR
|
||||
- 移除仅限移动端的更改和已回滚的 PR
|
||||
|
||||
#### 步骤 4:撰写发布文章
|
||||
|
||||
- 使用[谷歌文档模板](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit)
|
||||
- 重点介绍本次发布的 3-5 项关键功能
|
||||
- 稳定版发布后,在 [make.wordpress.org/core](https://make.wordpress.org/core/) 上发布文章
|
||||
|
||||
### 额外候选版本与次要版本 (X.Y.Z)
|
||||
|
||||
针对 RC1 后的紧急修复或主要版本间的关键错误修复:
|
||||
|
||||
#### 遴选错误修复
|
||||
|
||||
- 新 RC 版本:使用标有 `Backport to Gutenberg RC` 的 PR
|
||||
- 次要版本:使用标有 `Backport to Gutenberg Minor Release` 的 PR
|
||||
- 切换到相应发布分支:`git checkout release/X.Y`
|
||||
- 运行:`npm run other:cherry-pick "[Label Name]"`
|
||||
- 在运行工作流**之前**,将 PR 重新分配到正确的里程碑(例如从 `12.6` 改为 `12.5`)
|
||||
|
||||
#### 运行发布工作流
|
||||
|
||||
- 前往[构建插件压缩包工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)
|
||||
- 从 `Use workflow from` 下拉菜单中选择发布分支
|
||||
- 继续执行上述步骤 2-4
|
||||
|
||||
---
|
||||
|
||||
## 详细流程
|
||||
|
||||
发布 Gutenberg 插件稳定版的第一步是在 Gutenberg 代码库中[创建工单](https://github.com/WordPress/gutenberg/issues/new?template=New_release.md)。该工单模板名为 "Gutenberg Release",包含从候选版本到更新日志整理、遴选代码、稳定版发布及发布文章的完整清单。[Gutenberg 21.2](https://github.com/WordPress/gutenberg/issues/70662) 的工单就是一个很好的范例。
|
||||
|
||||
该清单可帮助您与参与发布流程的开发人员及其他团队协调,确保所有必要步骤均已完成,且所有人员都清楚时间安排和重要里程碑。
|
||||
|
||||
## 发布周期
|
||||
|
||||
Gutenberg 的主要版本大约每两周发布一次。当前及后续版本会在 [GitHub 里程碑](https://github.com/WordPress/gutenberg/milestones) 中追踪,同时标注每个版本的发布日期。
|
||||
|
||||
**在当前里程碑日期**(也称为标记日期),Gutenberg 的首个候选版本(RC)将发布。这是插件的预发布版本,供插件作者和用户测试。如果发现任何回归问题,可以发布新的 RC 版本。
|
||||
|
||||
候选版本按增量方式编号,从 `-rc.1` 开始,然后是 `-rc.2`,依此类推。一旦首个 RC(RC1)发布,发布文章的准备工作随即启动。
|
||||
|
||||
**RC1 发布一周后**,基于最后一个 RC 及必要的回归修复创建稳定版本。稳定版发布后,发布文章将同步上线。
|
||||
|
||||
如果在插件的稳定版本中发现关键错误,可随时发布修补版本。
|
||||
|
||||
## 版本管理
|
||||
|
||||
每个主要的 Gutenberg 版本由一位版本管理员(也称为版本负责人)负责运作。该负责人或小型团队在更广泛的 [Gutenberg 开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams)支持下,负责 Gutenberg 版本的发布工作。
|
||||
|
||||
版本管理员负责启动所有发布活动,任何对发布计划的更改都需要他们的批准。在紧急情况下或版本管理员无法参与时,其他团队成员可以采取适当行动,但应随时向版本管理员通报情况。
|
||||
|
||||
<div class="callout callout-tip">
|
||||
如果您是 <a href="https://developer.wordpress.org/block-editor/contributors/repository-management/#teams">Gutenberg 开发团队</a>成员,并且有兴趣主导 Gutenberg 版本发布,请在 <a href="https://wordpress.slack.com/messages/C02QB2JS7">#core-editor</a> Slack 频道中联系我们。
|
||||
</div>
|
||||
|
||||
## 准备发布
|
||||
|
||||
插件发布流程大部分是自动化的,并在 GitHub 上进行。您无需在本地计算机上执行任何步骤。不过,建议在本地保留一份 Gutenberg 副本,用于准备更新日志、进行常规测试,以及应对可能需要多个候选版本的情况。关于这一点,后续会详细说明。
|
||||
|
||||
这里有一个[11分钟的视频](https://youtu.be/TnSgJd3zpJY),演示了插件发布流程。如果您对该流程不熟悉,建议先观看视频。以下段落也记录了该流程,并提供了更详细的说明。
|
||||
|
||||
### 组织和标记里程碑 PR
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>快速参考</strong>
|
||||
<ul>
|
||||
<li>确保所有 PR 都正确标记。</li>
|
||||
<li>每个 PR 必须有一个以 <code>[Type]</code> 为前缀的标签。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
准备 Gutenberg 版本的第一步是组织所有分配给当前[里程碑](https://github.com/WordPress/gutenberg/milestones)的 PR,并确保每个 PR 都正确标记。[标签](https://github.com/WordPress/gutenberg/labels)用于自动生成更新日志,更改 PR 上的标签比事后在发布部分重新组织现有更新日志要快得多。
|
||||
|
||||
要测试将在发布工作流中运行的更新日志自动化功能,您可以在本地 Gutenberg 副本中使用以下命令,并指定您正在处理的稳定版本里程碑:
|
||||
|
||||
```
|
||||
npm run other:changelog -- --milestone="Gutenberg 16.2"
|
||||
```
|
||||
|
||||
该命令的输出是所提供里程碑的更新日志,上述示例中是 Gutenberg 16.2。您可以将输出复制并粘贴到 Markdown 文档中,这样可以更方便地查看,并允许您跟踪每个 PR 的链接。
|
||||
|
||||
所有 PR 都应有一个以 `[Type]` 为前缀的标签以及子类别标签。两个最常见的标签是 `[Type] Bug` 和 `[Type] Enhancement`。在查看生成的更新日志时,请特别注意以下内容:
|
||||
|
||||
- **增强功能:** 查找没有附加任何子类别的 PR。
|
||||
- **错误修复:** 同样查找没有附加任何子类别的 PR。
|
||||
- **其他:** 此部分中的 PR 没有任何标签。
|
||||
|
||||
根据需要更新每个 PR 的标签。您可以继续生成更新日志,直到您满意为止。现在,您可以开始候选版本工作流了。
|
||||
|
||||
<div class="callout callout-tip">
|
||||
您可以在 <a href="https://github.com/WordPress/gutenberg/blob/trunk/bin/plugin/commands/changelog.js">changelog.js</a> 文件中查看如何根据 PR 标签生成更新日志。
|
||||
</div>
|
||||
|
||||
### 运行发布工作流
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>快速参考</strong>
|
||||
<ul>
|
||||
<li>
|
||||
在 <a href="https://wordpress.slack.com/messages/C02QB2JS7">#core-editor</a> 中宣布您即将开始发布工作流。
|
||||
</li>
|
||||
<li>
|
||||
运行 <a href="https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml">构建 Gutenberg 插件 Zip</a> 工作流。
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
在开始之前,请在 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack 频道中宣布您即将开始工作流,并说明您是要发布 Gutenberg 的稳定版本还是候选版本。
|
||||
|
||||
然后转到 Gutenberg 仓库,点击 Actions 标签页,找到 [构建 Gutenberg 插件 Zip](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) 操作。注意蓝色的横幅,上面写着“此工作流有一个 `workflow_dispatch` 事件触发器。” 展开其右侧的“Run workflow”下拉菜单。
|
||||
|
||||

|
||||
|
||||
要发布插件的候选版本,请在文本字段中输入 `rc`。要发布稳定版本,请输入 `stable`。在每种情况下,按下“Run workflow”按钮。
|
||||
|
||||
这将触发一个 GitHub Actions (GHA) 工作流,该工作流将提升插件版本、构建 Gutenberg 插件 `.zip` 文件、创建发布草稿并附加插件 `.zip` 文件。这部分过程通常需要大约六分钟。工作流将出现在列表顶部,紧挨着蓝色横幅下方。完成后,工作流的状态图标将从黄色圆点变为绿色对勾。您可以通过点击工作流来查看更详细的视图。
|
||||
|
||||
## 排查发布问题
|
||||
|
||||
> 插件已成功发布至 WordPress.org 插件目录,但工作流却显示失败。
|
||||
|
||||
此类情况偶有发生,例如可参考[该案例](https://github.com/WordPress/gutenberg/actions/runs/16325916698/job/46115920213)。
|
||||
|
||||
请务必确认以下事项:
|
||||
|
||||
- 插件在目录中功能正常
|
||||
- ZIP 包内容(参见[下载页面](https://plugins.trac.wordpress.org/browser/gutenberg/))完整无误(无明显文件缺失)
|
||||
- [Gutenberg SVN 仓库](https://plugins.trac.wordpress.org/browser/gutenberg/)包含两次新提交(查看[提交记录](https://plugins.trac.wordpress.org/browser/gutenberg/)):
|
||||
- `trunk` 目录应包含 "Committing version X.Y.Z" 提交
|
||||
- 新增的 `tags/X.Y.Z` 目录内容与 `trunk` 一致,且最新提交为 "Tagging version X.Y.Z"
|
||||
|
||||
最可能的情况是标签目录未能自动创建。这是[已知问题](https://github.com/WordPress/gutenberg/issues/55295),可[通过手动操作修复](https://github.com/WordPress/gutenberg/issues/55295#issuecomment-1759292978)。
|
||||
|
||||
请将 `SVN_USERNAME`、`SVN_PASSWORD` 和 `VERSION` 替换为实际值,或先将其设为全局环境变量:
|
||||
|
||||
```sh
|
||||
# 检出代码库
|
||||
svn checkout https://plugins.svn.wordpress.org/gutenberg/trunk --username "$SVN_USERNAME" --password "$SVN_PASSWORD" gutenberg-svn
|
||||
|
||||
# 进入本地目录
|
||||
cd gutenberg-svn
|
||||
|
||||
# 若本地已存在代码库
|
||||
# 且未执行检出操作,请确保更新至最新版本
|
||||
svn up .
|
||||
|
||||
# 将当前主干内容复制至新标签目录
|
||||
svn copy https://plugins.svn.wordpress.org/gutenberg/trunk https://plugins.svn.wordpress.org/gutenberg/tags/$VERSION -m 'Tagging version $VERSION' --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD"
|
||||
```
|
||||
|
||||
操作过程中如需帮助,请及时向团队求助。
|
||||
|
||||
## 发布文档撰写
|
||||
|
||||
发布文档由发布经理主导,并依托[古腾堡开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams)成员协作完成。该流程包含一系列顺序步骤,因涉及人员众多且需协调配合,需在发布候选版与正式版之间的时间窗口内严格执行。古腾堡正式版于每周三发布,距首个候选版间隔一周。
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>时间线</strong>
|
||||
<ol>
|
||||
<li>复制<a href="https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit">发布公告谷歌文档模板</a>——周三至周五</li>
|
||||
<li>选定发布亮点——周五至周一</li>
|
||||
<li>亮点确定后向设计团队申请素材(图片、视频)——周五至周一</li>
|
||||
<li>起草发布公告并申请同行评审——周一至周三</li>
|
||||
<li>正式版发布后公开公告——周三</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
### 选定发布亮点
|
||||
|
||||
整理完更新日志后,下一步是选取若干重要变更作为发布公告的亮点。这些亮点通常聚焦于新功能与体验优化(包括性能与无障碍改进),也可包含重要的 API 变更或关键缺陷修复。
|
||||
|
||||
鉴于古腾堡涉及面广且每个里程碑周期会合并大量 PR,时有值得重点介绍的变更被疏漏。因此本环节需要发布经理与其他古腾堡开发团队成员协同完成。若不确定如何选择,可随时向团队成员寻求建议。
|
||||
|
||||
### 发布素材准备
|
||||
|
||||
发布公告需配置若干视觉素材。公告特色图片建议沿用上一版本使用的图片(媒体库中文件名应为 'gb-featured')。
|
||||
|
||||
公告正文中的横幅可通过名为「Gutenberg What's New Banner」的同步模式插入。使用该模式后请将版本号更新为正确数值。
|
||||
|
||||
重点功能还需配套视觉素材。针对核心功能可向设计团队申请专业素材,其他功能若操作熟练也可自行制作。申请设计素材时,请在 Slack 的 [#design](https://wordpress.slack.com/archives/C02S78ZAL) 频道中提出请求,可参考[15.8 版本申请范例](https://wordpress.slack.com/archives/C02S78ZAL/p1684161811926279)。素材将存放于专为该版本创建的[谷歌网盘文件夹](https://drive.google.com/drive/folders/1U8bVbjOc0MekWjpMjNaVFVhHFEzQkYLB)中。
|
||||
|
||||
制作 WordPress 发布素材时,可使用动画(视频或 GIF)或静态图片展示亮点。参考[往期发布公告](https://make.wordpress.org/core/tag/gutenberg-new/)的呈现方式,注意动画更适合演示操作流程,而直接的功能亮点用静态图片即可。创作过程中请避免使用版权素材,并移除浏览器画布中可见的插件标识。
|
||||
|
||||
### 创建候选发布补丁(代码拣选)
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>快速参考</strong>
|
||||
<ul>
|
||||
<li>确保所有需要拣选的PR都标记有<code>Backport to Gutenberg RC</code>标签。</li>
|
||||
<li>在本地克隆的Gutenberg仓库中,切换到发布分支:<code>git checkout release/X.Y</code></li>
|
||||
<li>使用自动化脚本拣选所有已合并的PR:<code>npm run other:cherry-pick "Backport to Gutenberg RC"</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
在RC版本发布后、最终稳定版发布前,一些与发布相关的错误可能会被修复并提交到`trunk`分支。稳定版本不会自动包含这些修复。需要手动将这些修复纳入,这个过程称为代码拣选。
|
||||
|
||||
作为发布经理,您可能会通过以下几种方式了解到这些错误:
|
||||
|
||||
- 贡献者可能会在已关闭的PR上添加`Backport to Gutenberg RC`标签。[在发布最终版本前,请搜索这些PR](https://github.com/WordPress/gutenberg/pulls?q=is%3Apr+label%3A%22Backport+to+Gutenberg+RC%22+is%3Aclosed)。
|
||||
- 您可能会在[#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack频道收到直接消息或通知,告知您需要纳入RC的PR。即使在这种情况下,也应在PR上添加`Backport to Gutenberg RC`标签。
|
||||
|
||||
#### 自动化代码拣选
|
||||
|
||||
代码拣选过程可以通过Gutenberg中包含的`npm run other:cherry-pick "[Insert Label]"`脚本自动完成。运行命令时需要使用`Backport to Gutenberg RC`标签,并确保所有需要拣选的PR都已分配该标签。
|
||||
|
||||
<div class="callout callout-warning">
|
||||
要拣选PR,您必须克隆(而不是分叉)Gutenberg仓库并具有写入权限。只有<a href="https://developer.wordpress.org/block-editor/contributors/repository-management/#teams">Gutenberg开发团队</a>的成员才有执行此操作的必要权限。</div>
|
||||
|
||||
将Gutenberg仓库克隆到本地开发环境后,首先切换到发布分支:
|
||||
|
||||
```
|
||||
git checkout release/X.Y
|
||||
```
|
||||
|
||||
接下来,使用适当的反向移植标签拣选所有已合并的PR:
|
||||
|
||||
```
|
||||
npm run other:cherry-pick "Backport to Gutenberg RC"
|
||||
```
|
||||
|
||||
在后台,该脚本将执行以下操作:
|
||||
|
||||
- 拣选所有带有`Backport to Gutenberg RC`标签的PR
|
||||
- 将它们添加到发布里程碑
|
||||
- 将所有更改`git push`到发布分支
|
||||
- 在PR上添加评论,表明它已被拣选
|
||||
- 从PR中移除`Backport to Gutenberg RC`标签
|
||||
|
||||
以下是该过程的截图:
|
||||
|
||||

|
||||
|
||||
#### 手动代码拣选
|
||||
|
||||
如果您需要逐个、逐步处理代码拣选,可以手动按照以下顺序操作。在检出相应的发布分支后:
|
||||
|
||||
1. 使用`git cherry-pick [SHA]`按时间顺序拣选每个PR。
|
||||
2. 完成后,使用`git push`将更改推送到GitHub。
|
||||
3. 移除所有已拣选PR的`Backport to Gutenberg RC`标签,并将里程碑更新为当前发布版本。
|
||||
|
||||
要查找拉取请求的`[SHA]`,请打开PR,您将在末尾附近看到“`[用户名]`将提交`[SHA]`合并到`trunk`”的消息。
|
||||
|
||||

|
||||
|
||||
如果拣选的修复值得在稳定版本发布前再发布一个候选版本,请按照上述说明创建一个。在[#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack频道中告知其他贡献者已发布新的候选版本。
|
||||
|
||||
### 发布版本
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>快速参考</strong>
|
||||
<ul>
|
||||
<li>在发布草稿中,点击“发布版本”按钮。</li>
|
||||
<li>如果发布稳定版本,需获得<a href="https://github.com/orgs/WordPress/teams/gutenberg-release">Gutenberg发布团队</a>、<a href="https://github.com/orgs/WordPress/teams/gutenberg-core">Gutenberg核心团队</a>或<a href="https://github.com/orgs/WordPress/teams/wordpress-core">WordPress核心团队</a>成员的批准,才能将新插件版本上传到WordPress.org插件仓库(SVN)。</li>
|
||||
<li>上传后,确认可以从WordPress插件仪表盘下载和更新最新版本。</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
只有当您对发布草稿中的更新日志内容满意时,才点击“发布版本”按钮。
|
||||
|
||||
请注意,您无需更改按钮上方的复选框。如果您发布的是RC版本,“设置为预发布版本”将自动被选中;如果您发布的是稳定版本,“设置为最新版本”将被选中。
|
||||
|
||||

|
||||
|
||||
发布版本将为该版本创建一个`git`标签,发布版本,并触发[另一个GHA工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml),其目的有两个:
|
||||
|
||||
1. 使用您刚刚编辑的发布说明更新`changelog.txt`;
|
||||
2. 将新插件版本上传到WordPress.org插件仓库(SVN)(仅当您发布稳定版本时)。
|
||||
|
||||
最后一步需要[Gutenberg发布团队](https://github.com/orgs/WordPress/teams/gutenberg-release)、[Gutenberg核心团队](https://github.com/orgs/WordPress/teams/gutenberg-core)或[WordPress核心团队](https://github.com/orgs/WordPress/teams/wordpress-core)成员的批准。这些团队在发布准备就绪时会收到通知邮件,但如果时间紧迫,您可以在`#core-editor` Slack频道中询问或通知[Gutenberg发布团队](https://github.com/orgs/WordPress/teams/gutenberg-release)以加速流程。建议在启动发布流程之前提前联系,以便有人准备好批准。找到新版本的[“将Gutenberg插件上传到WordPress.org插件仓库”工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml),并[批准](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)它。
|
||||
|
||||
一旦获得批准,新版本的Gutenberg将对全球的WordPress用户可用。上传后,请确认可以从WordPress插件仪表盘下载和更新最新版本。
|
||||
|
||||
最后一步是在[make.wordpress.org/core](https://make.wordpress.org/core/)上撰写发布文章。您可以在下面找到一些相关提示。
|
||||
75
contributors/code/scripts.md
Normal file
75
contributors/code/scripts.md
Normal file
@@ -0,0 +1,75 @@
|
||||
## Polyfill 脚本
|
||||
|
||||
编辑器还为某些可能并非所有现代浏览器都支持的功能提供了 polyfill。
|
||||
|
||||
建议使用主要的 `wp-polyfill` 脚本句柄,它会负责加载以下所有提到的 polyfill。
|
||||
|
||||
| 脚本名称 | 句柄 | 描述 |
|
||||
| ------------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
| [Babel Polyfill](https://babeljs.io/docs/en/babel-polyfill) | wp-polyfill | 模拟完整的 ES2015+ 环境。主脚本,用于加载以下所有附加的 polyfill |
|
||||
| [Fetch Polyfill](https://www.npmjs.com/package/whatwg-fetch) | wp-polyfill-fetch | 实现标准 Fetch 规范子集的 polyfill |
|
||||
| [Promise Polyfill](https://www.npmjs.com/package/promise-polyfill) | wp-polyfill-promise | 适用于浏览器和 Node 的轻量级 ES6 Promise polyfill |
|
||||
| [Formdata Polyfill](https://www.npmjs.com/package/formdata-polyfill) | wp-polyfill-formdata | 有条件地替换原生实现的 polyfill |
|
||||
| [Node Contains Polyfill](https://www.npmjs.com/package/polyfill-library) | wp-polyfill-node-contains | 用于 Node.contains 的 polyfill |
|
||||
| [Element Closest Polyfill](https://www.npmjs.com/package/element-closest) | wp-polyfill-element-closest | 返回 DOM 树中与选择器匹配的最接近的元素 |
|
||||
|
||||
## 打包与代码共享
|
||||
|
||||
当使用如 [webpack](https://webpack.js.org/) 这样的 JavaScript 打包工具时,此处提到的脚本可以从打包文件中排除,并通过 WordPress 以脚本依赖的形式提供,参见 [`wp_enqueue_script`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/#default-scripts-included-and-registered-by-wordpress)。
|
||||
|
||||
[`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin) 提供了一个 webpack 插件,用于帮助从打包文件中提取 WordPress 依赖项。`@wordpress/scripts` 的 [`build`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/scripts#build) 脚本默认包含此插件。
|
||||
|
||||
| [NUX](/packages/nux/README.md) | wp-nux | 包含用于引导新用户熟悉WordPress管理界面的组件及wp.data相关方法 |
|
||||
| [Plugins](/packages/plugins/README.md) | wp-plugins | WordPress插件管理模块 |
|
||||
| [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | 用于生成器协程的Redux中间件 |
|
||||
| [Rich Text](/packages/rich-text/README.md) | wp-rich-text | 实现HTML/DOM树与富文本值相互转换的辅助函数 |
|
||||
| [Shortcode](/packages/shortcode/README.md) | wp-shortcode | WordPress短代码模块 |
|
||||
| [Token List](/packages/token-list/README.md) | wp-token-list | 可构造的纯JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList)实现,支持非浏览器运行时环境 |
|
||||
| [URL](/packages/url/README.md) | wp-url | 用于操作URL的实用工具集 |
|
||||
| [Viewport](/packages/viewport/README.md) | wp-viewport | 用于响应浏览器视口尺寸变化的模块 |
|
||||
| [Wordcount](/packages/wordcount/README.md) | wp-wordcount | WordPress字数统计工具 |
|
||||
|
||||
## 第三方脚本
|
||||
|
||||
编辑器还使用了一些流行的第三方程序包和脚本。插件开发者同样可以直接使用这些脚本,无需将其打包到代码中(避免增加文件体积)。
|
||||
|
||||
| 脚本名称 | 注册句柄 | 功能描述 |
|
||||
| ---------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------- |
|
||||
| [React](https://reactjs.org) | react | 用于构建用户界面的JavaScript库 |
|
||||
| [React Dom](https://reactjs.org/docs/react-dom.html) | react-dom | 作为React的DOM和服务器渲染器入口点,需与React配合使用 |
|
||||
| [Moment](https://momentjs.com/) | moment | 用于解析、验证、操作和显示JavaScript中的日期时间 |
|
||||
| [Lodash](https://lodash.com) | lodash | 提供通用编程任务工具函数的JavaScript库 |
|
||||
|
||||
# 脚本
|
||||
|
||||
编辑器为插件开发者提供了多个供应商和内部脚本。下表记录了脚本名称、句柄和描述。
|
||||
|
||||
## WordPress 脚本
|
||||
|
||||
编辑器包含多个功能包以实现各种功能。插件开发者可以利用它们来创建区块、编辑器插件或通用插件。
|
||||
|
||||
| 脚本名称 | 句柄 | 描述 |
|
||||
| -------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| [Blob](/packages/blob/README.md) | wp-blob | Blob 实用工具 |
|
||||
| [区块库](/packages/block-library/README.md) | wp-block-library | 编辑器的区块库 |
|
||||
| [区块](/packages/blocks/README.md) | wp-blocks | 区块创建 |
|
||||
| [区块序列化默认解析器](/packages/block-serialization-default-parser/README.md) | wp-block-serialization-default-parser | WordPress 文档的默认区块序列化解析器实现 |
|
||||
| [区块序列化规范解析器](/packages/block-serialization-spec-parser/README.md) | wp-block-serialization-spec-parser | WordPress 文章的语法文件 (grammar.pegjs) |
|
||||
| [组件](/packages/components/README.md) | wp-components | 用于创建通用 UI 元素的通用组件 |
|
||||
| [组合](/packages/compose/README.md) | wp-compose | 一系列便捷的高阶组件 (HOCs) |
|
||||
| [核心数据](/packages/core-data/README.md) | wp-core-data | 简化和操作核心 WordPress 实体的访问 |
|
||||
| [数据](/packages/data/README.md) | wp-data | 数据模块作为管理插件和 WordPress 本身应用状态的枢纽 |
|
||||
| [日期](/packages/date/README.md) | wp-date | WordPress 的日期模块 |
|
||||
| [弃用](/packages/deprecated/README.md) | wp-deprecated | 用于记录消息以通知开发者某个功能已弃用的实用工具 |
|
||||
| [DOM](/packages/dom/README.md) | wp-dom | WordPress 的 DOM 实用工具模块 |
|
||||
| [DOM 就绪](/packages/dom-ready/README.md) | wp-dom-ready | DOM 加载完成后执行回调 |
|
||||
| [编辑器](/packages/editor/README.md) | wp-editor | WordPress 编辑器的构建块 |
|
||||
| [编辑文章](/packages/edit-post/README.md) | wp-edit-post | WordPress 的编辑文章模块 |
|
||||
| [元素](/packages/element/README.md) | wp-element | 元素简单来说是基于 [React](https://reactjs.org/) 的抽象层 |
|
||||
| [HTML 转义](/packages/escape-html/README.md) | wp-escape-html | HTML 转义实用工具 |
|
||||
| [钩子](/packages/hooks/README.md) | wp-hooks | 一个轻量高效的 JavaScript 事件管理器 |
|
||||
| [HTML 实体](/packages/html-entities/README.md) | wp-html-entities | WordPress 的 HTML 实体实用工具 |
|
||||
| [国际化](/packages/i18n/README.md) | wp-i18n | 客户端本地化的国际化实用工具 |
|
||||
| [浅比较](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | 用于对两个对象或数组执行浅比较的函数 |
|
||||
| [键码](/packages/keycodes/README.md) | wp-keycodes | WordPress 的键码实用工具,用于检查 `onKeyDown` 等事件中按下的键 |
|
||||
| [列出可重用区块](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | 用于在可重用区块列表页面添加导入/导出链接的包 |
|
||||
647
contributors/code/testing-overview.md
Normal file
647
contributors/code/testing-overview.md
Normal file
@@ -0,0 +1,647 @@
|
||||
#### 最佳实践
|
||||
|
||||
若你正着手重构,快照会是个不错的选择。你可以将其作为分支的首个提交,并观察其演变过程。
|
||||
|
||||
快照本身并不体现我们的预期目标。快照最适合与描述期望的其他测试结合使用,如下例所示:
|
||||
|
||||
```jsx
|
||||
test( '当启用planets属性时应包含mars', () => {
|
||||
const { container } = render( <SolarSystem planets /> );
|
||||
|
||||
// 快照将捕获意外变更
|
||||
expect( container ).toMatchSnapshot();
|
||||
|
||||
// 这才是我们测试中真正期望发现的内容
|
||||
expect( screen.getByText( /mars/i ) ).toBeInTheDocument();
|
||||
} );
|
||||
```
|
||||
|
||||
另一项实用技巧是使用 `toMatchDiffSnapshot` 函数(由 [`snapshot-diff` 包](https://github.com/jest-community/snapshot-diff)提供),该函数支持仅对DOM两种状态间的差异生成快照。这种方法适用于测试属性变更对DOM产生的影响,同时生成更精简的快照,示例如下:
|
||||
|
||||
```jsx
|
||||
test( '当启用isShady属性时应渲染更深背景色', () => {
|
||||
const { container } = render( <CardBody>正文</CardBody> );
|
||||
const { container: containerShady } = render(
|
||||
<CardBody isShady>正文</CardBody>
|
||||
);
|
||||
expect( container ).toMatchDiffSnapshot( containerShady );
|
||||
} );
|
||||
```
|
||||
|
||||
类似地,`toMatchStyleDiffSnapshot` 函数支持仅记录组件两种状态间关联样式的差异,如下例所示:
|
||||
|
||||
```jsx
|
||||
test( '应渲染外边距', () => {
|
||||
const { container: spacer } = render( <Spacer /> );
|
||||
const { container: spacerWithMargin } = render( <Spacer margin={ 5 } /> );
|
||||
expect( spacerWithMargin ).toMatchStyleDiffSnapshot( spacer );
|
||||
} );
|
||||
```
|
||||
|
||||
#### 故障排查
|
||||
|
||||
某些使用refs的组件场景需要进行模拟。查阅以下文档了解更多:
|
||||
|
||||
- 了解为何在React中需使用[模拟Refs进行快照测试](https://reactjs.org/blog/2016/11/16/react-v15.4.0.html#mocking-refs-for-snapshot-testing)。
|
||||
|
||||
遇到此类情况时,可能会在尝试访问 `ref.current` 属性的代码行看到Jest报出的测试失败及 `TypeError`。
|
||||
|
||||
### 调试Jest单元测试
|
||||
|
||||
执行 `npm run test:unit:debug` 将启动调试模式的测试,以便[节点检查器客户端](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients)能够连接至进程并监控执行情况。在[wp-scripts文档](/packages/scripts/README.md#debugging-jest-unit-tests)中可查看使用Google Chrome或Visual Studio Code作为检查器客户端的指南。
|
||||
|
||||
## 原生移动端测试
|
||||
|
||||
单元测试套件包含一组基于React Native开发、用于验证原生移动代码路径的Jest测试。由于这些测试运行在Node环境,可直接在开发机本地运行,无需安装特定的Android或iOS原生开发工具或SDK。这也意味着可以使用常规开发工具进行调试。具体调试指南如下:
|
||||
|
||||
### 调试原生移动端单元测试
|
||||
|
||||
本地调试模式运行测试需遵循以下步骤:
|
||||
|
||||
0. 确保已执行 `npm ci` 安装所有依赖包
|
||||
1. 在CLI中进入Gutenberg根目录,运行 `npm run test:native:debug`。此时Node正在等待调试器连接
|
||||
2. 在Chrome浏览器中打开 `chrome://inspect`
|
||||
3. 在“Remote Target”区域找到 `../../node_modules/.bin/jest` 目标项,点击“inspect”链接。这将打开附接到进程的新Chrome DevTools调试窗口,并暂停在 `jest.js` 文件起始处。若未显示目标项,可点击同一页面中的 `Open dedicated DevTools for Node` 链接
|
||||
4. 可在代码(包括测试代码)中设置断点或 `debugger;` 语句来暂停执行并检查
|
||||
5. 点击“Play”按钮继续执行
|
||||
6. 尽享原生移动端单元测试的调试过程!
|
||||
|
||||
# 测试概述
|
||||
|
||||
Gutenberg 项目包含 PHP 和 JavaScript 代码,并鼓励对两者进行测试和代码规范检查。
|
||||
|
||||
## 为何需要测试?
|
||||
|
||||
除了测试将为生活带来的乐趣之外,测试的重要性不仅在于确保应用程序按预期运行,还在于它们提供了如何使用代码的简明示例。
|
||||
|
||||
测试也是代码库的一部分,这意味着我们对测试代码采用与应用程序代码相同的标准。
|
||||
|
||||
与所有代码一样,测试也需要维护。为了测试而编写测试并非目标——我们应努力在覆盖预期与非预期行为、执行速度与代码维护之间找到平衡点。
|
||||
|
||||
编写测试时请考虑以下问题:
|
||||
|
||||
- 我们正在测试什么行为?
|
||||
- 运行此代码时可能出现哪些错误?
|
||||
- 测试是否验证了我们想要验证的内容?还是产生了误报/漏报?
|
||||
- 测试是否具备可读性?其他贡献者能否通过查看对应测试来理解代码行为?
|
||||
|
||||
## JavaScript 测试
|
||||
|
||||
JavaScript 测试使用 [Jest](https://jestjs.io/) 作为测试运行器,并采用其提供的 [全局 API](https://jestjs.io/docs/en/api.html)(`describe`、`test`、`beforeEach` 等)、[断言方法](https://jestjs.io/docs/en/expect.html)、[模拟函数](https://jestjs.io/docs/en/mock-functions.html)、[监视器](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) 和 [模拟函数 API](https://jestjs.io/docs/en/mock-function-api.html)。如需测试 React 组件,还可使用 [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)。
|
||||
|
||||
_需要注意的是,过去我们使用 [Enzyme](https://github.com/airbnb/enzyme) 进行 React 组件单元测试。但现在所有现有及新增测试均已改用 React Testing Library (RTL)。_
|
||||
|
||||
若已按照 [指南](/docs/contributors/code/getting-started-with-code-contribution.md) 安装 Node 和项目依赖,可通过 NPM 在命令行中运行测试:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
代码规范检查是通过静态代码分析来强制执行编码标准并避免潜在错误。本项目使用 [ESLint](https://eslint.org/) 和 [TypeScript 的 JavaScript 类型检查](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) 来发现此类问题。虽然上述 `npm test` 会同时执行单元测试和代码检查,但也可通过运行 `npm run lint` 单独进行代码规范验证。部分 JavaScript 问题可通过运行 `npm run lint:js:fix` 自动修复。
|
||||
|
||||
为提升开发效率,建议配置编辑器集成检查功能。详细信息请参阅 [入门文档](/docs/contributors/code/getting-started-with-code-contribution.md)。
|
||||
|
||||
若需仅运行单元测试而不执行规范检查,请使用 `npm run test:unit`。
|
||||
|
||||
### 目录结构
|
||||
|
||||
请将测试文件保存在工作目录的 `test` 文件夹中。测试文件应与被测试文件同名。
|
||||
|
||||
```
|
||||
+-- test
|
||||
| +-- bar.js
|
||||
+-- bar.js
|
||||
```
|
||||
|
||||
只有测试文件(至少包含一个测试用例)应直接存放在 `/test` 目录下。如需添加外部模拟数据或固定装置,请将其置于子文件夹中,例如:
|
||||
|
||||
- `test/mocks/[文件名].js`
|
||||
- `test/fixtures/[文件名].js`
|
||||
|
||||
### 测试导入
|
||||
|
||||
根据上述目录结构,在导入 **被测试代码** 时请尽量使用相对路径,而非项目路径。
|
||||
|
||||
**推荐做法**
|
||||
|
||||
`import { bar } from '../bar';`
|
||||
|
||||
**不推荐做法**
|
||||
|
||||
`import { bar } from 'components/foo/bar';`
|
||||
|
||||
这将使您在决定将代码迁移至应用目录其他位置时更加轻松。
|
||||
|
||||
### 测试描述
|
||||
|
||||
使用 `describe` 代码块对测试用例进行分组。每个测试用例理想情况下应仅描述一种行为。
|
||||
|
||||
在测试用例中,请尝试用通俗语言描述预期行为。对于 UI 组件,这可能需要从用户角度描述预期行为,而非解释代码内部实现。
|
||||
|
||||
**推荐写法**
|
||||
|
||||
```javascript
|
||||
describe( 'CheckboxWithLabel', () => {
|
||||
test( '勾选复选框应禁用表单提交按钮', () => {
|
||||
...
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
**不推荐写法**
|
||||
|
||||
```javascript
|
||||
describe( 'CheckboxWithLabel', () => {
|
||||
test( '勾选复选框应将 this.state.disableButton 设置为 `true`', () => {
|
||||
...
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
### 快照测试
|
||||
|
||||
本文概述了[快照测试]及其最佳实践方法。
|
||||
|
||||
#### 快速指南:快照失败处理
|
||||
|
||||
当快照测试失败时,仅表示组件的渲染结果发生了变化。若这是意外变动,则快照测试成功拦截了一个程序缺陷 😊
|
||||
|
||||
但若属于预期变更,请按以下步骤更新快照:
|
||||
```sh
|
||||
# 使用 --testPathPattern 可加速测试过程(仅运行匹配的测试用例)
|
||||
npm run test:unit -- --updateSnapshot --testPathPattern 测试路径
|
||||
|
||||
# 更新端到端测试的快照
|
||||
npm run test:e2e -- --update-snapshots 测试规范路径
|
||||
```
|
||||
|
||||
1. 仔细核对差异内容,确认变更符合预期
|
||||
2. 提交更新后的快照文件
|
||||
|
||||
#### 快照本质解析
|
||||
|
||||
快照是测试生成数据结构的序列化记录。这些快照文件与测试代码共同存储在版本库中。执行测试时,系统会将实时生成的数据结构与存储的快照版本进行比对。
|
||||
|
||||
创建快照非常简单:
|
||||
```js
|
||||
test( 'foobar测试示例', () => {
|
||||
const foobar = { foo: 'bar' };
|
||||
|
||||
expect( foobar ).toMatchSnapshot();
|
||||
} );
|
||||
```
|
||||
对应生成的快照文件内容:
|
||||
```js
|
||||
exports[ `测试foobar测试示例 1` ] = `
|
||||
对象 {
|
||||
"foo": "bar",
|
||||
}
|
||||
`;
|
||||
```
|
||||
注意:严禁手动创建或修改快照文件,它们必须通过测试流程自动生成和更新。
|
||||
|
||||
#### 优势分析
|
||||
|
||||
- 测试代码编写极其简洁
|
||||
- 有效预防意外变更
|
||||
- 操作流程简单直观
|
||||
- 无需启动应用即可探查内部结构
|
||||
|
||||
#### 局限说明
|
||||
|
||||
- 缺乏表达能力
|
||||
- 仅能检测发生变更的问题
|
||||
- 对非确定性场景支持不佳
|
||||
|
||||
#### 适用场景
|
||||
|
||||
快照测试主要应用于组件测试领域。它能敏锐感知组件结构变化,因此特别适合重构场景。若能在系列提交中持续维护快照,其差异记录将清晰展现组件结构的演进历程,堪称精妙 😎
|
||||
|
||||
```jsx
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import SolarSystem from 'solar-system';
|
||||
|
||||
describe( '太阳系组件', () => {
|
||||
test( '基础渲染测试', () => {
|
||||
const { container } = render( <SolarSystem /> );
|
||||
|
||||
expect( container ).toMatchSnapshot();
|
||||
} );
|
||||
|
||||
test( '启用行星参数时应包含火星', () => {
|
||||
const { container } = render( <SolarSystem planets /> );
|
||||
|
||||
expect( container ).toMatchSnapshot();
|
||||
expect( screen.getByText( /mars/i ) ).toBeInTheDocument();
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
Reducer测试同样适合快照方案。这类测试常涉及大规模复杂数据结构,且不应发生意外变动——这正是快照技术的优势所在!
|
||||
|
||||
#### 实践技巧
|
||||
|
||||
持续集成环境可能因快照不匹配而中断测试。若变更为预期修改,请按指南[更新快照]。最快捷的方式是使用Jest的`--updateSnapshot`参数:
|
||||
```sh
|
||||
npm run test:unit -- --updateSnapshot --test路径模式
|
||||
```
|
||||
虽然`--testPathPattern`非必选参数,但指定路径能通过限定测试范围显著提升效率。
|
||||
|
||||
建议开发时在后台持续运行`npm run test:unit:watch`。Jest会自动运行与变更文件相关的测试,当快照测试失败时,仅需按下`u`键即可即时更新快照!
|
||||
|
||||
#### 注意事项
|
||||
|
||||
非确定性测试可能产生不一致的快照,需要特别警惕。在处理随机数、时间相关或其他非确定性因素时,快照测试会面临挑战。
|
||||
|
||||
连接组件的测试需要特殊处理。要对已连接的组件进行快照测试,建议导出未连接状态的原始组件:
|
||||
```js
|
||||
// 组件文件 my-component.js
|
||||
export { MyComponent };
|
||||
export default connect( mapStateToProps )( MyComponent );
|
||||
|
||||
// 测试文件 test/my-component.js
|
||||
import { MyComponent } from '..';
|
||||
// 对原始MyComponent执行测试...
|
||||
```
|
||||
需要手动提供连接所需的props参数,这恰是审查连接状态的良机。
|
||||
|
||||
### 原生移动端端到端测试
|
||||
|
||||
Gutenberg的贡献者会发现,PR中包含了在Android和iOS上运行原生移动端E2E测试的持续集成流程。若需排查测试失败问题,请查阅我们的[持续集成中的原生移动端测试指南](/docs/contributors/code/react-native/integration-test-guide.md)。关于在本地运行这些测试的更多信息,可[在此处](/packages/react-native-editor/__device-tests__/README.md)获取。
|
||||
|
||||
### 原生移动端集成测试
|
||||
|
||||
我们正在持续推进为原生移动项目添加集成测试的工作,使用的是[`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/)库。编写集成测试的指南可[在此处](/docs/contributors/code/react-native/integration-test-guide.md)找到。
|
||||
|
||||
## 端到端测试
|
||||
|
||||
目前大多数现有的端到端测试使用[Puppeteer](https://github.com/puppeteer/puppeteer)作为无头Chromium驱动,在`packages/e2e-tests`中运行测试,并仍由[Jest](https://jestjs.io/)测试运行器执行。
|
||||
|
||||
我们正在推进一个[项目](https://github.com/WordPress/gutenberg/issues/38851),将这些测试从Puppeteer迁移到Playwright。**建议尽可能使用Playwright编写新的端到端测试**。以下部分主要适用于旧的Jest + Puppeteer框架。若使用Playwright编写测试,请参阅专用[指南](/docs/contributors/code/e2e/README.md)。
|
||||
|
||||
### 使用wp-env环境
|
||||
|
||||
如果使用内置的[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment),可通过以下命令在本地运行端到端测试:
|
||||
|
||||
```bash
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
或交互式运行:
|
||||
|
||||
```bash
|
||||
npm run test:e2e:watch
|
||||
```
|
||||
|
||||
有时在运行测试时观察浏览器行为会很有帮助。此时可使用:
|
||||
|
||||
```bash
|
||||
npm run test:e2e:watch -- --puppeteer-interactive
|
||||
```
|
||||
|
||||
可通过`--puppeteer-slowmo`控制执行速度:
|
||||
|
||||
```bash
|
||||
npm run test:e2e:watch -- --puppeteer-interactive --puppeteer-slowmo=200
|
||||
```
|
||||
|
||||
还可启用开发者工具,在浏览器中进行交互式调试:
|
||||
|
||||
```bash
|
||||
npm run test:e2e:watch -- --puppeteer-devtools
|
||||
```
|
||||
|
||||
### 使用其他环境
|
||||
|
||||
若使用`wp-env`之外的其他设置,需要先将端到端测试插件软链接到测试站点。从站点的插件目录运行:
|
||||
|
||||
```bash
|
||||
ln -s gutenberg/packages/e2e-tests/plugins/* .
|
||||
```
|
||||
|
||||
运行测试时需指定站点的基础URL、用户名和密码。例如,若测试站点为`http://wp.test`,则使用:
|
||||
|
||||
```bash
|
||||
WP_BASE_URL=http://wp.test npm run test:e2e -- --wordpress-username=admin --wordpress-password=password
|
||||
```
|
||||
|
||||
### 场景测试
|
||||
|
||||
若发现端到端测试在本地通过但在GitHub Actions中失败,可通过模拟低速CPU或网络来隔离CPU或网络相关的竞态条件:
|
||||
|
||||
```
|
||||
THROTTLE_CPU=4 npm run test:e2e
|
||||
```
|
||||
|
||||
`THROTTLE_CPU`为减速系数(此示例中为4倍减速)
|
||||
|
||||
参阅[Chrome文档:setCPUThrottlingRate](https://chromedevtools.github.io/devtools-protocol/tot/Emulation#method-setCPUThrottlingRate)
|
||||
|
||||
```
|
||||
SLOW_NETWORK=true npm run test:e2e
|
||||
```
|
||||
|
||||
`SLOW_NETWORK`模拟相当于Chrome开发者工具中"快速3G"的网络速度。
|
||||
|
||||
参阅[Chrome文档:emulateNetworkConditions](https://chromedevtools.github.io/devtools-protocol/tot/Network#method-emulateNetworkConditions)和[NetworkManager.js](https://github.com/ChromeDevTools/devtools-frontend/blob/80c102878fd97a7a696572054007d40560dcdd21/front_end/sdk/NetworkManager.js#L252-L274)
|
||||
|
||||
```
|
||||
OFFLINE=true npm run test:e2e
|
||||
```
|
||||
|
||||
`OFFLINE`模拟网络断开情况。
|
||||
|
||||
参阅[Chrome文档:emulateNetworkConditions](https://chromedevtools.github.io/devtools-protocol/tot/Network#method-emulateNetworkConditions)
|
||||
|
||||
### 核心区块测试
|
||||
|
||||
每个核心区块必须至少包含一套用于主要保存功能的测试固件文件,以及针对每个废弃功能的独立测试固件。这些测试固件用于验证区块的解析与序列化功能。更多详细信息及操作指南请参阅[集成测试固件说明文档](https://github.com/wordpress/gutenberg/blob/HEAD/test/integration/fixtures/blocks/README.md)。
|
||||
|
||||
### 不稳定性测试
|
||||
|
||||
当某个测试在未经代码修改的情况下,经过多次重试运行时出现时而过时而不通过的情况,该测试即被视为**不稳定性测试**。我们在持续集成环境中最多会自动重试失败测试**两次**,通过[`report-flaky-tests`](https://github.com/WordPress/gutenberg/tree/trunk/packages/report-flaky-tests) GitHub Action自动检测这类测试,并将其提交至GitHub问题区,标记为[`[Type] Flaky Test`](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22%5BType%5D+Flaky+Test%22)标签。请注意,连续失败三次的测试不会被判定为不稳定性测试,也不会被提交至问题区。
|
||||
|
||||
## PHP测试
|
||||
|
||||
PHP测试采用[PHPUnit](https://phpunit.de/)作为测试框架。若使用内置的[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment),可通过以下命令在本地运行PHP测试:
|
||||
|
||||
```bash
|
||||
npm run test:php
|
||||
```
|
||||
|
||||
若需在文件变更时自动重新运行测试(类似Jest功能),请执行:
|
||||
|
||||
```bash
|
||||
npm run test:php:watch
|
||||
```
|
||||
|
||||
_注意:phpunit命令需要`wp-env`处于运行状态且composer依赖已安装。若wp-env未运行,包脚本将自动启动该环境。_
|
||||
|
||||
在其他环境中,请运行`composer run test`和`composer run test:watch`命令。
|
||||
|
||||
PHP代码规范通过[PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)进行校验。建议通过[Composer](https://getcomposer.org/)安装PHP_CodeSniffer及[WordPress PHP编码标准规则集](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards#installation)。安装Composer后,在项目目录下运行`composer install`即可安装依赖。前述的`npm run test:php`命令将同时执行单元测试和代码规范检查。若需单独验证代码规范,可运行`npm run lint:php`。
|
||||
|
||||
若需仅运行单元测试(不包含规范检查),请使用`npm run test:unit:php`命令。
|
||||
|
||||
[快照测试]: https://jestjs.io/docs/en/snapshot-testing.html
|
||||
[更新快照]: https://jestjs.io/docs/en/snapshot-testing.html#updating-snapshots
|
||||
|
||||
## 性能测试
|
||||
|
||||
为确保编辑器在功能增补过程中始终保持高性能,我们持续监控拉取请求和版本发布对以下关键指标的影响:
|
||||
|
||||
- 编辑器加载时长
|
||||
- 输入时浏览器响应时长
|
||||
- 区块选中时长
|
||||
|
||||
性能测试通过端到端测试运行编辑器并采集这些指标。执行前请确保已配置好端到端测试环境。
|
||||
|
||||
配置端到端测试环境时,请先检出Gutenberg代码库并切换至待测试分支,随后运行以下命令完成环境准备:
|
||||
|
||||
```bash
|
||||
nvm use && npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
执行测试请运行以下命令:
|
||||
|
||||
```bash
|
||||
npm run test:performance
|
||||
```
|
||||
|
||||
此命令将输出当前分支/代码在运行环境中的测试结果。
|
||||
|
||||
此外,您还可以通过`./bin/plugin/cli.js perf [分支名]`命令对比不同分支(或标签/提交)间的指标差异,例如:
|
||||
|
||||
```bash
|
||||
./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0
|
||||
```
|
||||
|
||||
最后,您可通过`--tests-branch`参数指定要运行的性能测试文件所属分支。这在修改/扩展性能测试时特别有用:
|
||||
|
||||
```bash
|
||||
./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 --tests-branch add/perf-tests-coverage
|
||||
```
|
||||
|
||||
**注意** 此基准测试可能需要较长时间。运行期间请避免操作计算机或运行过多后台进程,以尽量减少可能影响跨分支测试结果的外部因素。
|
||||
|
||||
### 设置与清理方法
|
||||
|
||||
Jest API 包含一些实用的[设置与清理方法](https://jestjs.io/docs/en/setup-teardown.html),允许您在每项测试前后、所有测试前后或特定 `describe` 代码块内的测试前后执行任务。
|
||||
|
||||
这些方法支持异步代码,可实现常规行内代码无法完成的设置。与[独立测试用例](https://jestjs.io/docs/en/asynchronous.html#promises)类似,您可以返回 Promise 对象,Jest 将等待其状态落定:
|
||||
|
||||
```javascript
|
||||
// 为*所有*测试执行一次性设置
|
||||
beforeAll( () =>
|
||||
someAsyncAction().then( ( resp ) => {
|
||||
window.someGlobal = resp;
|
||||
} )
|
||||
);
|
||||
|
||||
// 为*所有*测试执行一次性清理
|
||||
afterAll( () => {
|
||||
window.someGlobal = null;
|
||||
} );
|
||||
```
|
||||
|
||||
`afterEach` 和 `afterAll` 提供了在测试后进行「清理」的完美(且推荐)方式,例如通过重置状态数据来实现。
|
||||
|
||||
请避免在断言之后放置清理代码,因为如果其中任何测试失败,清理操作将不会执行,并可能导致无关测试出现故障。
|
||||
|
||||
### 模拟依赖项
|
||||
|
||||
#### 依赖注入
|
||||
|
||||
将依赖项作为参数传递给函数通常能使代码更易于测试。在可能的情况下,请避免引用更高作用域中的依赖项。
|
||||
|
||||
**欠佳实践**
|
||||
|
||||
```javascript
|
||||
import VALID_VALUES_LIST from './constants';
|
||||
|
||||
function isValueValid( value ) {
|
||||
return VALID_VALUES_LIST.includes( value );
|
||||
}
|
||||
```
|
||||
|
||||
此时我们需要导入并使用 `VALID_VALUES_LIST` 中的值才能通过测试:
|
||||
|
||||
`expect( isValueValid( VALID_VALUES_LIST[ 0 ] ) ).toBe( true );`
|
||||
|
||||
上述断言同时测试了两个行为:1)函数能检测列表中的项目,2)函数能检测 `VALID_VALUES_LIST` 中的项目。
|
||||
|
||||
但如果我们不关心 `VALID_VALUES_LIST` 中存储的内容,或者该列表是通过 HTTP 请求获取的,而我们只想测试 `isValueValid` 能否检测列表中的项目呢?
|
||||
|
||||
**推荐实践**
|
||||
|
||||
```javascript
|
||||
function isValueValid( value, validValuesList = [] ) {
|
||||
return validValuesList.includes( value );
|
||||
}
|
||||
```
|
||||
|
||||
通过将列表作为参数传递,我们可以在测试中传入模拟的 `validValuesList` 值,同时还能额外测试更多场景:
|
||||
|
||||
`expect( isValueValid( 'hulk', [ 'batman', 'superman' ] ) ).toBe( false );`
|
||||
|
||||
`expect( isValueValid( 'hulk', null ) ).toBe( false );`
|
||||
|
||||
`expect( isValueValid( 'hulk', [] ) ).toBe( false );`
|
||||
|
||||
`expect( isValueValid( 'hulk', [ 'iron man', 'hulk' ] ) ).toBe( true );`
|
||||
|
||||
#### 导入的依赖项
|
||||
|
||||
当代码在多个位置使用来自外部和内部库的方法及属性时,通过参数传递会显得混乱且不切实际。对于这种情况,`jest.mock` 提供了一种简洁的存根化解决方案。
|
||||
|
||||
例如,假设我们有一个通过特性标志控制大量功能的 `config` 模块:
|
||||
|
||||
```javascript
|
||||
// bilbo.js
|
||||
import config from 'config';
|
||||
export const isBilboVisible = () =>
|
||||
config.isEnabled( 'the-ring' ) ? false : true;
|
||||
```
|
||||
|
||||
为了测试不同条件下的行为,我们存根化配置对象并使用 Jest 模拟函数来控制 `isEnabled` 的返回值:
|
||||
|
||||
```javascript
|
||||
// test/bilbo.js
|
||||
import { isEnabled } from 'config';
|
||||
import { isBilboVisible } from '../bilbo';
|
||||
|
||||
jest.mock( 'config', () => ( {
|
||||
// 比尔博默认可见
|
||||
isEnabled: jest.fn( () => false ),
|
||||
} ) );
|
||||
|
||||
describe( '比尔博模块', () => {
|
||||
test( '比尔博默认应可见', () => {
|
||||
expect( isBilboVisible() ).toBe( true );
|
||||
} );
|
||||
|
||||
test( '当启用 `the-ring` 配置特性标志时,比尔博应不可见', () => {
|
||||
isEnabled.mockImplementationOnce( ( name ) => name === 'the-ring' );
|
||||
expect( isBilboVisible() ).toBe( false );
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
### 测试全局对象
|
||||
|
||||
我们可以使用 [Jest 监控器](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname)来测试调用全局方法的代码:
|
||||
|
||||
```javascript
|
||||
import { myModuleFunctionThatOpensANewWindow } from '../my-module';
|
||||
|
||||
describe( '我的模块', () => {
|
||||
beforeAll( () => {
|
||||
jest.spyOn( global, 'open' ).mockImplementation( () => true );
|
||||
} );
|
||||
|
||||
test( '特定功能', () => {
|
||||
myModuleFunctionThatOpensANewWindow();
|
||||
expect( global.open ).toHaveBeenCalled();
|
||||
} );
|
||||
} );
|
||||
```
|
||||
|
||||
### 用户交互模拟
|
||||
|
||||
通过模拟用户交互来**从用户视角编写测试**是绝佳实践,能够有效避免测试实现细节。
|
||||
|
||||
使用 Testing Library 编写测试时,主要有两种模拟用户交互的方式:
|
||||
|
||||
1. [`fireEvent`](https://testing-library.com/docs/dom-testing-library/api-events/#fireevent) API:Testing Library 核心 API 中用于触发 DOM 事件的工具
|
||||
2. [`user-event`](https://testing-library.com/docs/user-event/intro/) 库:Testing Library 的配套库,通过模拟浏览器中真实交互时触发的事件来模拟用户操作
|
||||
|
||||
内置的 `fireEvent` 是用于派发 DOM 事件的工具。它会精确触发测试规范中描述的事件——即使这些事件在真实浏览器交互中从未被触发过。
|
||||
|
||||
而 `user-event` 库则提供了更高级的方法(如 `type`、`selectOptions`、`clear`、`doubleClick`...),这些方法会像真实用户与文档交互时那样派发事件,并处理所有 React 相关的特殊行为。
|
||||
|
||||
基于以上原因,**在为用户交互编写测试时,推荐使用 `user-event` 库**。
|
||||
|
||||
**不够理想**:使用 `fireEvent` 派发 DOM 事件
|
||||
|
||||
```javascript
|
||||
import { render, screen } from '@testing-library/react';
|
||||
|
||||
test( '输入新值时触发 onChange 事件', () => {
|
||||
const spyOnChange = jest.fn();
|
||||
|
||||
// 包含一个 input 和一个 select 的组件
|
||||
render( <MyComponent onChange={ spyOnChange } /> );
|
||||
|
||||
const input = screen.getByRole( 'textbox' );
|
||||
input.focus();
|
||||
// 没有点击事件,没有按键事件
|
||||
fireEvent.change( input, { target: { value: 62 } } );
|
||||
|
||||
// onChange 回调被调用一次,参数为 '62'
|
||||
expect( spyOnChange ).toHaveBeenCalledTimes( 1 );
|
||||
|
||||
const select = screen.getByRole( 'listbox' );
|
||||
select.focus();
|
||||
// 未派发指针事件
|
||||
fireEvent.change( select, { target: { value: 'optionValue' } } );
|
||||
|
||||
// ...
|
||||
```
|
||||
|
||||
**推荐做法**:使用 `user-event` 模拟用户事件
|
||||
|
||||
```javascript
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
test( '输入新值时触发 onChange 事件', async () => {
|
||||
const user = userEvent.setup();
|
||||
|
||||
const spyOnChange = jest.fn();
|
||||
|
||||
// 包含一个 input 和一个 select 的组件
|
||||
render( <MyComponent onChange={ spyOnChange } /> );
|
||||
|
||||
const input = screen.getByRole( 'textbox' );
|
||||
// 聚焦元素,选中并清空所有内容
|
||||
await user.clear( input );
|
||||
// 点击元素,逐个字符输入(生成 keydown、keypress 和 keyup 事件)
|
||||
await user.type( input, '62' );
|
||||
|
||||
// onChange 回调被调用 3 次,参数依次为:
|
||||
// - 1: clear ('')
|
||||
// - 2: '6'
|
||||
// - 3: '62'
|
||||
expect( spyOnChange ).toHaveBeenCalledTimes( 3 );
|
||||
|
||||
const select = screen.getByRole( 'listbox' );
|
||||
// 派发聚焦、指针、鼠标、点击和变更事件
|
||||
await user.selectOptions( select, [ 'optionValue' ] );
|
||||
|
||||
// ...
|
||||
} );
|
||||
```
|
||||
|
||||
### 区块界面集成测试
|
||||
|
||||
集成测试是指将不同部件作为整体进行测试的方法。在此场景下,我们需要测试的是特定区块或编辑器逻辑需要渲染的各个组件。最终这些测试与单元测试非常相似,因为它们都使用 Jest 库通过相同命令运行。主要区别在于集成测试中的区块运行在[`特殊的区块编辑器实例`](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/helpers/integration-test-editor.js#L60)中。
|
||||
|
||||
这种方法的优势在于,无需启动完整的端到端测试框架即可测试区块编辑器的大部分功能(如区块工具栏和检查器面板交互等)。这意味着测试运行速度更快、可靠性更高。建议尽可能使用集成测试覆盖区块的界面功能,而将对完整浏览器环境有需求的交互(例如文件上传、拖放操作等)留给端到端测试。
|
||||
|
||||
[`封面区块`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover/test/edit.js)就是运用此级别测试的典型范例,该测试覆盖了编辑器绝大部分的交互场景。
|
||||
|
||||
配置集成测试的 Jest 文件:
|
||||
|
||||
```js
|
||||
import { initializeEditor } from 'test/integration/helpers/integration-test-editor';
|
||||
|
||||
async function setup( attributes ) {
|
||||
const testBlock = { name: 'core/cover', attributes };
|
||||
return initializeEditor( testBlock );
|
||||
}
|
||||
```
|
||||
|
||||
`initializeEditor` 函数返回 `@testing-library/react` 的 `render` 方法输出结果。该函数也支持接收区块元数据对象数组,允许您设置包含多个区块的编辑器。
|
||||
|
||||
集成测试编辑器模块还导出了 `selectBlock` 方法,可通过区块包装器上的 aria-label(例如“区块: 封面”)来选择需要测试的区块。
|
||||
Reference in New Issue
Block a user