- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
导读
本文围绕 CKEditor 4 仓库中pastefromgdocs插件的手动测试用例tests/plugins/pastefromgdocs/manual/nestedlists.md展开,完整讲解"从 Google Docs 复制嵌套列表并粘贴到编辑器"这一场景的验证步骤、预期行为,以及其背后pastetools过滤管线如何把 Google Docs 的扁平列表 HTML 重建为正确的嵌套结构。读完本文,你将掌握该用例的复现方法、判定标准,并能够对照 filter/default.js 等源码理解列表层级重建的底层原理。
一、测试用例背景:4.13.0 新增的 Google Docs 粘贴能力
根据 CHANGES.md 的记录,CKEditor 4 在 4.13.0 版本中通过 issue #835 扩展了"从外部应用粘贴"的能力:
- 新增Paste from Google Docs(
pastefromgdocs)插件,支持从 Google Docs 粘贴富文本内容; - 新增Paste Tools(
pastetools)插件,提供统一的粘贴处理机制。
本仓库中的tests/plugins/pastefromgdocs/目录正是围绕该插件建立的测试集,其中manual/nestedlists.md是专门针对嵌套列表粘贴的手动测试用例,用于回归验证"列表层级在粘贴后是否保持一致"这一核心诉求。
二、测试用例文件速览
| 文件 | 作用 |
|---|---|
| manual/nestedlists.md | 手动测试步骤、预期/异常行为描述(本文主体) |
| manual/nestedlists.html | 手动测试页面:classic、divarea、inline 三种编辑器 |
| plugin.js | 插件主文件:注册粘贴处理 handler 与过滤器 |
| filter/default.js | Google Docs 专用 HTML 过滤规则(含列表重建) |
| filter/common.js | pastetools 公共过滤规则与样式/元素工具函数 |
| detectgdocs.js | 验证 Google Docs 内容识别逻辑的自动化测试 |
| generated/basic.js | 自动化回归套件(含 NestedOrderedLists / NestedUnorderedLists) |
| generated/_fixtures/NestedUnorderedLists/gdocs/ | 无序嵌套列表的输入/期望 HTML 夹具 |
| generated/_fixtures/NestedOrderedLists/gdocs/ | 有序嵌套列表的输入/期望 HTML 夹具 |
三、手动测试前置条件:Bender 标签与插件清单
该用例是 CKEditor 4 的Bender 手动测试,文件头部通过@bender-*元数据声明运行环境:
@bender-tags: feature, 4.13.0, 835, pastefromgdocs @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, undo, pastefromgdocs, sourcearea, elementspath, basicstyles, format, link, image2, autolink, colorbutton, stylescombo, font, justify, pagebreak, list, liststyle, tabletools, floatingspace各元数据含义:
@bender-tags:标记该用例归属的特性、版本(4.13.0)、关联 issue(835)与插件(pastefromgdocs);@bender-ui: collapsed:Bender 测试面板默认折叠,便于聚焦编辑器内容;@bender-ckeditor-plugins:测试页面加载的插件白名单,除pastefromgdocs本身外,还包含list、liststyle、undo、basicstyles、font、link等,用于保证粘贴后列表格式、样式与撤销操作都能正常展示和操作。
仓库根目录下的 bender.js、bender-runner.config.json、bender.ci.js 即为该测试体系的总配置,手动测试用例可通过 Bender 运行器打开nestedlists.html页面执行。
四、手动测试步骤
在 Bender 测试面板中打开 nestedlists.html 后,对页面上的每一个编辑器(Classic editor、Divarea editor、Inline editor)依次执行:
- 从手册中引用的 Google Docs 示例文档中复制一段列表片段(该示例文档链接位于 nestedlists.md 原始步骤中,需先在受支持的浏览器中打开并复制其内容)。
- 将复制的内容粘贴到编辑器内容区。
注意(原文档特别说明):虽然低于 IE 11 的旧版 IE 不被 Google Docs 支持,但你可以先在受支持的浏览器中打开示例文档、复制内容,再粘贴到编辑器中进行验证。
粘贴完成后,需要结合源码观察内容区 HTML:打开sourcearea插件提供的源码视图,可以直观对比粘贴前后的<ul>/<ol>/<li>层级结构。
五、预期结果与异常结果
预期结果
- 粘贴后的列表与源文档具有相同的嵌套层级,即 Google Docs 中的多级列表在 CKEditor 4 中仍然保持多级嵌套。
- 关键约定:如果被复制列表的第一个条目本身是嵌套项,它应当被提升为根层级的列表项(不再保持嵌套),其余元素则相对于第一个元素保持原有的嵌套关系。
这一点与过滤规则unwrapList的行为一一对应(详见下文源码分析),例如源文档结构为"嵌套列表项"开头时,过滤后首个<li>会被展开为根级条目。
异常结果(回归失败信号)
以下情况均视为测试失败,说明列表重建逻辑出现回归:
- 粘贴后的列表嵌套方式与源文档不一致(层级错乱、顺序颠倒);
- 列表开头出现了多个空的列表项或空列表(说明
fixList/unwrapList未正确折叠多余的列表容器)。
六、源码原理(一):如何识别 Google Docs 剪贴板内容
pastefromgdocs并不对所有粘贴内容生效,而是通过canHandle判定内容是否来自 Google Docs。plugin.js 中的判定逻辑如下:
canHandle: function( evt ) { var detectGDocsRegex = /id=(\"|\')?docs\-internal\-guid\-/; return detectGDocsRegex.test( evt.data.dataValue ); }Google Docs 在复制内容时会在内部元素上写入形如id="docs-internal-guid-xxxxxxxx-..."的唯一标识,该正则正是以此为指纹识别 Google Docs 内容。detectgdocs.js 的自动化测试验证了这一点:
test( '<span id="docs-internal-guid-whatever">Test</span>' ); test( '<whatever id=docs-internal-guid-' );两个样本(带引号、不带引号的 id 写法)都应被canHandle判定为true。
一旦命中,handle回调(plugin.js)会执行以下关键动作:
- 通过
CKEDITOR.plugins.pastetools.getClipboardData( data, 'text/html' )从剪贴板取 HTML; - 置
data.dontFilter = true,避免内容被再次应用普通粘贴过滤(防止与 Google Docs 过滤器冲突); - 调用
CKEDITOR.pasteFilters.gdocs( gDocsHtml, editor )执行过滤; - 若配置了
forcePasteAsPlainText === true,则强制按纯文本处理(issue #1013 的兼容逻辑)。
过滤器本身通过 pastetools/plugin.js 的createFilter创建:它把commonFilter.rules与gdocs.rules依次注册到CKEDITOR.htmlParser.filter,再对 HTML 片段应用过滤并序列化输出(见 filter/default.js)。
七、源码原理(二):列表层级如何被重建
1. 冗余信息的清理
Google Docs 复制的 HTML 中充满meta、注释、内联 GUID、kix-line-break类名等编辑器无关内容。filter/default.js 通过以下规则先行清理:
elementNames: /^meta/ → '':移除所有<meta>标签;comment: → false:删除注释节点;id:移除以docs-internal-guid-开头的内部标识;dir:值为ltr时移除(默认方向无意义);class:剔除kix-line-break类名;style:交由normalizedStyles归一化(删除background-color:transparent、mso-*、color:windowtext等冗余声明,见 common.js);b:Google Docs 常把<b>当作无语义包装器,直接replaceWithChildren展开(filter/default.js);p:位于<li>内部的段落直接展开为文本(filter/default.js)。
2. 列表重建的核心:fixList 与 unwrapList
Google Docs 输出的列表结构有一个显著特点:嵌套关系由"列表套列表"表达,即<ul><ul><li>…</li></ul></ul>,而非标准的<li><ul>…</ul></li>。这一点可以直接在自动化夹具中看到:NestedUnorderedLists/gdocs/chrome.html 中出现了连续嵌套的<ul style="margin-top:0;margin-bottom:0;">容器。
filter/default.js 针对ul、ol、li三个元素定义了重建规则:
'ul': function( element ) { Style.pushStylesLower( element ); return fixList( element ); }, 'ol': function( element ) { Style.pushStylesLower( element ); return fixList( element ); }, 'li': function( element ) { Style.pushStylesLower( element ); return unwrapList( element ); }fixList(filter/default.js):当<ul>/<ol>的父元素本身就是列表时,说明这是 Google Docs 用来表达缩进的"伪嵌套"容器,直接replaceWithChildren展开,消掉一层多余容器;unwrapList(filter/default.js):当<li>的唯一子元素恰好是一个列表时,把<li>展开为其子列表——这正是手册中"第一个条目为嵌套项时应提升为根层级列表项"约定的实现来源;pushStylesLower(common.js):把列表元素上的font-size、color等样式下推给子元素,同时保留list-style-type、width、height、border等对列表外观有意义的属性,从而保住list-style-type: circle/square/disc这类层级符号。
经过上述规则,类似下面的扁平输入:
<ul><ul><li>列表项A</li><ul><li>嵌套项B</li></ul></ul></ul>会被重建为标准的嵌套结构:
<ul><li>列表项A<ul><li>嵌套项B</li></ul></li></ul>这与 NestedUnorderedLists/gdocs/expected_chrome.html 中"<li>内嵌<ul>、list-style-type依次为 circle → square → disc"的期望输出完全吻合。
3. 有序列表的数字风格保留
对于有序列表,重建逻辑同样生效。NestedOrderedLists/gdocs/chrome.html 展示了多达六层的<ol>嵌套输入,而期望输出(如 expected_edge.html)则呈现为标准的逐层嵌套<ol>,且list-style-type按 lower-roman → decimal → lower-alpha 等层级符号保留;部分浏览器夹具的顶层<ol>还保留了start="2"起始序号属性,说明列表编号信息在重建过程中未被丢失。
八、自动化回归佐证:夹具与浏览器矩阵
手动测试之外,仓库还提供了自动化套件 generated/basic.js 作为长期回归防线:
- 测试项:
Basic、BasicCKE5、NestedOrderedLists、NestedUnorderedLists; - 浏览器矩阵:
chrome、firefox、safari、ie11、edge,每个浏览器都有独立的输入/期望 HTML 夹具; - 数据源:
wordVersions: ['gdocs'],即专门针对 Google Docs 生成的数据; - 过滤归一化:套件通过
customFilters移除多余的<br>与"bogus"空段落,保证不同浏览器产出的 HTML 差异不影响断言结果; - 环境约束:由于旧版 IE(<11)与移动端不支持 Google Docs 复制,套件在
CKEDITOR.env.ie && CKEDITOR.env.version < 11或移动端环境下整体跳过(ignoreAll),这与手动测试中"旧 IE 需借助受支持浏览器复制内容"的说明互为印证。
由此,manual/nestedlists.md描述的手动验证点(层级一致、首项提升、无空列表残留)全部被自动化夹具固化,任何过滤规则的改动都必须同时通过两套验证。
九、相关配置与边界说明
- 插件依赖:
pastefromgdocs依赖pastetools(见 plugin.js 的requires: 'pastetools'),而pastetools又依赖clipboard与ajax(pastetools/plugin.js)。引入该功能时需保证这些插件一并加载。 - handler 优先级:通过
editor.pasteTools.register()注册的 handler 默认priority = 10,多个 handler 按优先级排序、逐个执行(pastetools/plugin.js),为将来支持更多外部应用粘贴预留了扩展点。 - 强制纯文本:若编辑器配置
config.forcePasteAsPlainText = true,即使命中 Google Docs 内容也会退化为纯文本粘贴(plugin.js)。 - 适用范围:嵌套列表重建规则仅作用于
pastefromgdocs过滤管线;普通粘贴、粘贴自 Word(pastefromword)走各自独立的过滤器,行为互不干扰。
十、小结
tests/plugins/pastefromgdocs/manual/nestedlists.md虽是一份手动测试说明,但它精准刻画了"从 Google Docs 粘贴嵌套列表"这一场景的全部验收标准:层级一致、首项提升到根级、开头不得出现空列表。通过对照 filter/default.js 中的fixList/unwrapList/pushStylesLower实现、pastetools/plugin.js 的 handler 调度,以及generated/_fixtures下的多浏览器夹具,可以完整还原从"识别docs-internal-guid-指纹"到"重建标准嵌套列表"的整条技术链路。这套"手动用例 + 自动化夹具"的组合,正是 CKEditor 4 保证外部内容粘贴质量的关键工程实践。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
CKEditor 4 剪贴板粘贴全场景测试指南:paste.md 手动测试规格与源码级原理剖析
CKEditor 4 剪贴板粘贴全场景测试指南:paste.md 手动测试规格与源码级原理剖析 导读 本文以 CKEditor 4 仓库中的手动测试规格文档 t
前端UI组件CKEditor 4 Auto Embed 插件手动测试指南:粘贴即嵌入的完整验证清单与源码原理
CKEditor 4 Auto Embed 插件手动测试指南:粘贴即嵌入的完整验证清单与源码原理 Auto Embed( autoembed )是 CKEdit
前端UI组件CKEditor 4 Auto Embed 粘贴自动嵌入的通知机制:手动测试用例与源码级解析
CKEditor 4 Auto Embed 粘贴自动嵌入的通知机制:手动测试用例与源码级解析 本篇技术指南以 CKEditor 4 官方仓库中的手动测试文档 n
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考