CKEditor 5 测试辅助工具(Testing Helpers)完全指南:用 `_getModelData()` / `_setModelData()` 操控模型与视图
2026/9/15 12:03:42 网站建设 项目流程

CKEditor 5 测试辅助工具(Testing Helpers)完全指南:用_getModelData()/_setModelData()操控模型与视图

【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5

本文围绕 CKEditor 5 框架开发工具(development tools)中的 Testing Helpers 展开,系统讲解_getModelData()_setModelData()以及视图侧_getViewData()_setViewData()等开发辅助函数的字符串化与解析机制、完整选项参数与底层实现原理。读完本文,你将能够在自己的编辑器插件测试中,用一行字符串精确断言模型/视图的结构、选区与标记,大幅提升测试的可读性与编写效率。

提示:本指南的官方出处为 docs/framework/development-tools/testing-helpers.md,所有源码依据均来自 packages/ckeditor5-engine/src/dev-utils 及其对应的测试用例。

什么是 Testing Helpers

_getModelData()_setModelData()是 CKEditor 5 引擎在 model 开发工具模块 和 view 开发工具模块 中暴露的一组开发辅助函数。它们解决了一个非常实际的痛点:如何用一段可读的字符串来描述内存中的数据结构

在 CKEditor 5 中,编辑器内容同时存在于两个层次:

  • 模型(Model):编辑器的内部数据结构,例如<paragraph>$text节点;
  • 视图(View):由模型经 downcast 转换得到、面向编辑界面的树结构。

关于二者的架构关系,可参考 编辑引擎架构文档。

Testing Helpers 的核心能力是:

  1. 字符串化(Stringify):把模型/视图的结构、选区(selection)、范围(range)与位置(position)输出为一个 HTML 风格的字符串;
  2. 装载(Load):从字符串反解析,把内容写回模型/视图,同时还原选区。

由于断言结果是一段纯文本,测试的"期望值"与"实际值"可以直接对比,这在编写插件测试、调试转换流程时极其常用。

import { _getModelData } from 'ckeditor5'; ClassicEditor .create( { root: { initialData: '<p>Hello <b>world</b>!</p>' } } ) .then( editor => { console.log( _getModelData( editor.model ) ); // -> '<paragraph>[]Hello <$text bold="true">world</$text>!</paragraph>' } );

上面来自 原文档 的示例展示了_getModelData()的典型用法:它返回一段类似 XML 的字符串,其中<paragraph>表示模型段落元素,<$text bold="true">表示带属性(加粗)的文本节点,[]则标记出当前文档选区的折叠位置。

⚠️重要警告:这两组工具是为原型开发、调试与测试而设计的,请勿在生产级代码中使用它们。这一点在原文档以及 model.ts 源码注释 中都有明确强调。

模型字符串的语法规则

要熟练使用这些助手,必须先理解其输出/输入字符串的语法。以_getModelData()的输出为例(源码规范见 model.ts):

  • 普通文本:直接输出文本内容,如Hello
  • 块级元素:输出为 HTML 风格标签,如<paragraph>...</paragraph>
  • 带属性的文本节点:输出为<$text attribute="value">Text data</$text>形式。例如bold="true"表示文本带加粗属性,这是 model 工具独有的$text特殊标记;
  • 元素属性:直接作为标签属性输出,例如<paragraph alignment="center">
  • 选区:用方括号[]标记范围,[]表示折叠(collapsed)选区。

下表总结了模型字符串的核心符号:

符号含义示例
<paragraph>块级元素<paragraph>foo</paragraph>
<$text bold="true">带属性的文本节点<$text bold="true">world</$text>
[]折叠选区<paragraph>[]Hello</paragraph>
[]展开范围<paragraph>[Hello]</paragraph>

在字符串化时,属性值会经过类型还原:源码中的parseAttributeValue()(model.ts)会尝试用JSON.parse把字符串还原为原始类型,例如"true"变回布尔值true"1"变回数字1'{"x":1,"y":2}'变回对象;解析失败则保留为字符串。

_getModelData():把模型输出为字符串

_getModelData( model, options? )接收一个Model实例,返回当前文档的字符串化内容。其完整签名与选项定义见 model.ts 的 GetModelDataOptions:

选项类型默认值说明
withoutSelectionbooleanfalsetrue时,输出结果中不包含选区信息
rootNamestring'main'指定要从哪个根(root)字符串化内容;多根编辑器可传入其他根名
convertMarkersbooleanfalsetrue时,把标记(markers)也包含进输出字符串

convertMarkers的用法可以对照 model.js 测试:一个折叠标记foo会被输出为<foo:start></foo:start>,非折叠标记则输出<foo:start></foo:start>...<foo:end></foo:end>,且标记按名称排序以保证输出结果稳定。

底层实现:_stringifyModel

_getModelData()内部委托给_stringifyModel()(model.ts)。它的工作流程如下:

  1. 根据传入节点构造覆盖范围,并把ModelSelection/ModelPosition/ModelRange统一转换为 selection;
  2. 创建一个临时的ModelEditingViewViewRootEditableElement作为main根);
  3. 组装DowncastDispatcher及一组 converter:insertText()insertAttributesAndChildren()insertElement()、选区转换的convertRangeSelection()/convertCollapsedSelection()、标记转换的insertUIElement()
  4. 调用downcastDispatcher.convert()convertSelection()完成模型 → 视图的转换,再借助视图侧_stringifyView()把视图树输出为字符串;
  5. 移除临时<div>根标签,并把内部占位元素名model-text-with-attributes替换回$text

从源码可以看出,_getModelData()的输出不是简单的手写序列化,而是完整走了一遍 downcast 转换管线,因此它反映的是真实转换结果,这也是它在调试转换逻辑时特别有价值的原因。

_setModelData():从字符串装载模型

_setModelData( model, data, options? )把 HTML 风格的字符串解析并写入模型文档,同时重建选区。其选项定义见 model.ts 的 SetModelDataOptions:

选项类型默认值说明
rootNamestring'main'解析后的数据写入哪个根
selectionAttributesRecord<string, unknown>附加到选区上的属性集合
lastRangeBackwardbooleanfalsetrue时最后一个范围按 backward(反向)选区创建
batchTypeBatchType指定插入元素使用的批次类型;不传则走model.change(),传入则走model.enqueueChange()
inlineObjectElementsArray<string>需要按内联对象(inline object)处理的元素名列表

典型用法:

import { _setModelData } from 'ckeditor5'; _setModelData( editor.model, '<paragraph>Hello <$text bold="true">world</$text>!</paragraph>' ); // 写入模型,并把折叠选区放在段落开头 _setModelData( editor.model, '<paragraph>[]Hello</paragraph>' ); // 写入一个跨段落的展开选区 _setModelData( editor.model, '<paragraph>[Foo</paragraph><paragraph>Bar]</paragraph>' ); // 创建一个 backward 选区,并给选区附加属性 _setModelData( editor.model, '<paragraph>[Foo]</paragraph>', { lastRangeBackward: true, selectionAttributes: { foo: 'bar' } } );

使用前提:在_setModelData()解析元素之前,必须在模型的 schema 中注册这些元素,否则会抛出转换错误。这一点在源码注释(model.ts)与转换器实现中都有体现——convertToModelElement()会通过conversionApi.schema.checkChild()校验元素是否被允许(model.ts),不允许的位置会抛出Element 'x' was not allowed in given position.

底层实现:_parseModel

_setModelData()内部委托给_parseModel()(model.ts),流程如下:

  1. $text替换为合法的 XML 元素名model-text-with-attributes
  2. 调用视图侧的_parseView()解析出视图树与选区;
  3. 组装UpcastDispatcher,注册documentFragmentelement:model-text-with-attributeselementtext四类 converter;
  4. 通过upcastDispatcher.convert()完成视图 → 模型的 upcast 转换;
  5. Mapper把视图选区映射为模型选区,必要时附加selectionAttributes

测试用例(model.js)验证了各种边界情况:纯文本、带选区的文本、元素内嵌套选区、Unicode 文本、backward 选区、rootName指定的特殊根等。其中还验证了batchType选项的行为——传入batchType时调用model.enqueueChange(),不传时调用model.change()

自定义根注意_parseModel默认使用context: '$root'。若编辑器使用自定义根(通过RootConfig.modelElement配置),需要显式传入目标根元素或其模型元素名作为 context,否则转换结果可能不正确。相关细节可参阅 Schema 深度解析 中的自定义根章节。

视图侧工具:_getViewData()_setViewData()

除了模型工具,view.ts 还提供了视图层的对应助手_getViewData( view, options? )_setViewData( view, data, options? )。它们面向EditingView实例,用于检查视图树与视图选区的状态。

视图字符串化有几个独特选项(GetViewDataOptions):

选项说明
showType输出元素类型前缀,如<container:p><attribute:b><empty:img><ui:span>
showPriority输出属性元素的优先级,如<b view-priority="10">
renderUIElements输出ViewUIElement的内部 HTML 内容
renderRawElements输出ViewRawElement的内部 HTML 内容
domConverter传入真实ViewDomConverter可让转换走与编辑视图完全相同的过滤流程,否则使用简化 stub
skipListItemIdstrue(默认)时隐藏列表项随机生成的data-list-item-id属性,便于稳定断言

视图选区标记:[]{}

视图工具在选区标记上与模型工具略有不同,这是调试视图时最需要留意的细节:

  • 元素间范围[]标记,如<p>[<b>foobar</b>]</p>
  • 文本内部范围{}标记,如<p><b>f{ooba}r</b></p>
  • 通过sameSelectionCharacters: true可统一为[](模型工具内部正是使用该选项把两者统一,见 view.ts)。

_parseView()的解析器由RangeParser类实现(view.ts),它会从文本节点中提取括号标记并重建ViewRange,同时支持通过order数组重排多个范围的顺序、用lastRangeBackward标记最后一个范围为反向。如果遇到未闭合的]、嵌套的[、文本节点中间的元素级[]等情况,解析器会抛出明确的解析错误。

元素名的类型前缀(如container:p)在_convertElement()中(view.ts)被转换回ViewContainerElementViewAttributeElementViewEmptyElementViewUIElementViewRawElement等真实视图节点类型;view-priorityview-id属性则被解析为元素优先级与 id。

在测试中的典型应用模式

Testing Helpers 最常见的应用场景是编写插件/特性的单元测试,测试覆盖可参考 packages/ckeditor5-engine/tests/dev-utils 目录下的model.jsview.jsutils.jsoperationreplayer.js

一个典型的"写入—断言"闭环测试模式如下:

import { _setModelData, _getModelData } from 'ckeditor5'; import { Model } from 'ckeditor5/src/engine.js'; describe( '自定义插件的模型转换', () => { let model, document, root; beforeEach( () => { model = new Model(); document = model.document; root = document.createRoot(); // 在 schema 中注册测试用元素 model.schema.register( 'paragraph', { inheritAllFrom: '$block' } ); // 手动填充内容 _setModelData( model, '<paragraph>Foo!</paragraph>' ); } ); it( '应该正确插入文本与选区', () => { _setModelData( model, '[]<paragraph>Bar!</paragraph>' ); expect( _getModelData( model ) ).toBe( '[]<paragraph>Bar!</paragraph>' ); } ); } );

类似上面的测试结构在 model.js 测试文件 中可以看到完整范本:先注册 schema(abcparagraph等元素及其允许的属性),再通过_setModelData()铺设数据,最后用_getModelData()断言输出完全一致。

此外,这两个函数的可测试性也经过了专门设计:_getModelData暴露_stringify属性、_setModelData暴露_parse属性(用于测试中的 spy 监控),这在 model.ts 源码 和对应测试中都有体现。

更多开发调试辅助:utils 与 OperationReplayer

dev-utils目录下还包含其他开发调试工具:

  • utils.ts:提供convertMapToTags()(把 Map 转为key="value"标签格式)、convertMapToStringifiedObject()(转为 JSON 对象字符串)、dumpTrees()/initDocumentDumping()/logDocument()(按版本快照并回放文档树,用于调试),以及printTree()树打印。注意该文件顶部注释特别说明:这些函数仅限内部调试使用,依赖默认构建流程中不存在的特殊方法,因此没有配套测试;
  • operationreplayer.ts:提供OperationReplayer类,用于按版本回放模型操作,帮助定位协同编辑或复杂操作序列中的状态变化,其测试见 operationreplayer.js。

这两个工具与_getModelData()/_setModelData()共同组成了引擎的完整开发工具箱。

使用注意事项与最佳实践

结合 原文档、源码与测试,总结以下几点实践建议:

  1. 仅在开发/测试环境使用:这些函数刻意命名为_前缀并归入dev-utils,生产构建中不应出现;
  2. 先注册 schema 再装载数据_setModelData()依赖 schema 校验,未注册的元素会导致解析报错;
  3. 注意$text语法的唯一性:带属性的文本必须写成<$text attribute="value">,这是模型工具的特有表示法,与视图/HTML 的<b>标签不同;
  4. 多根编辑器指定rootName:需要操作非main根时,读写两侧都要显式传rootName(见 model.js 的特殊根测试);
  5. 稳定断言优先使用withoutSelection: true:当测试只关心内容结构、不关心选区时,关闭选区输出能让断言更聚焦;对列表内容,视图工具默认隐藏随机生成的listItemId,保证了断言的确定性;
  6. 调试转换流程时善用convertMarkers:标记(markers)常用于评论、高亮等特性,_getModelData( model, { convertMarkers: true } )能直观看到标记在模型中的位置。

小结

Testing Helpers 把 CKEditor 5 复杂的模型/视图结构"降维"成了可读、可比较的字符串,是插件开发者日常测试与调试的得力工具。本文详细拆解了模型侧与视图侧的四个核心函数、它们支持的选项参数、底层 downcast/upcast 实现链路,以及配套的调试工具,并给出了可直接套用的测试模式。掌握这些工具后,你可以为任何自定义插件写出精确、可维护、可读性极强的单元测试。更多框架开发工具请继续阅读 development-tools 目录 下的其他文档。

【免费下载链接】ckeditor5Powerful rich text editor framework with a modular architecture, modern integrations, and features like collaborative editing.项目地址: https://gitcode.com/GitHub_Trending/ck/ckeditor5

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

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

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

立即咨询