- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
本篇技术指南围绕 CKEditor 4 仓库中的手动测试文档 tests/plugins/toolbar/manual/hover/desktoprichcombo.md 展开,讲解桌面端工具栏 Rich Combo(下拉组合框,如样式、格式选择器)的悬停(hover)与按下(toggled on)视觉状态的验收标准。读者将掌握:如何读懂并复跑该 Bender.js 手动测试、Rich Combo 三态状态机(cke_combo_on/cke_combo_off/ disabled)在源码与皮肤 CSS 中的具体实现,以及该测试背后的 Bug #4611(移动端误触悬停样式)修复动机。
一、测试文档定位:Bug #4611 与手动回归测试
desktoprichcombo.md是 CKEditor 4 仓库中一套**手动测试(manual test)**的说明文档,文件头部三行元数据即指明了它的用途:
@bender-tags: 4.16.1, 4611, bug @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, stylescombo, format@bender-tags: 4.16.1, 4611, bug:该测试标记为4.16.1版本引入、与issue #4611相关、类型为bug 回归验证;@bender-ui: collapsed:手动测试页面以折叠 UI 呈现,便于直接观察工具栏;@bender-ckeditor-plugins:页面加载的插件集合为wysiwygarea、toolbar、stylescombo、format——前两者提供编辑区域与工具栏框架,后两者恰好是仓库中两个典型的 Rich Combo 使用者(样式组合框与格式组合框)。
Issue #4611 的修复记录可以在 CHANGES.md 中找到:
#4611: [Android, iOS] Fixed: Incorrect hover styles for buttons in the toolbar on mobile devices.
即:移动端设备由于不存在"悬停"概念,却错误地应用了悬停样式,导致按钮视觉状态混乱。本次桌面端测试正是为了保证修复没有破坏桌面端的正确行为——桌面端必须保留悬停样式,且点击后的按下状态必须正常。
二、测试步骤原文解读(Moono-Lisa 皮肤)
测试文档的正文部分(## Procedure (Moono-Lisa))要求测试者在Moono-Lisa皮肤下按以下三步执行:
- 检查 Rich Combo 是否有悬停样式(背景变深)
- 期望结果:存在悬停样式;
- 非期望结果:不存在悬停样式。
- 检查点击 Rich Combo 是否激活其按下(toggled on)状态(背景变浅)
- 期望结果:被点击的 Combo 呈现按下状态;
- 非期望结果:被点击的 Combo 没有按下状态。
- 对第二个编辑器重复以上流程。
第 3 步中的"第二个编辑器"指向测试页模板中的两个编辑实例:一个基于Iframe(wysiwygarea提供),另一个基于Div(通过divarea插件提供),详见下文第三节。
同一目录下的皮肤/设备变体矩阵
tests/plugins/toolbar/manual/hover/目录下还存在同一逻辑的多个变体,共同构成一套完整的回归矩阵:
| 设备 | 皮肤 | 文件 |
|---|---|---|
| Desktop | Moono-Lisa | desktoprichcombo.md(本文档) |
| Desktop | Moono | desktoprichcombomoono.md |
| Desktop | Kama | desktoprichcombokama.md |
| Android | 三种皮肤 | androidrichcombo*.md |
| iOS | 三种皮肤 | iosrichcombo*.md |
以 desktoprichcombomoono.md 为例,Moono 皮肤的测试额外增加了一步:
- Check if toggled on rich combos have hover styles (additional shadow, "deeper pressed in" combo).
即在 Moono 皮肤下,已处于按下状态的 Combo 仍要具备悬停样式(附加阴影,呈现"更深按下"效果)。这说明不同皮肤对状态组合的视觉定义并不完全相同,测试文档正是用来逐皮肤锁定这些细节。
三、测试页面模板:两个编辑器实例与皮肤动态注入
所有 hover 手动测试共用同一个页面模板 tests/plugins/toolbar/manual/hover/template.html。模板的核心逻辑如下:
- 页面包含两个
contenteditable容器:#iframe-editor(标题为 "Iframe-based editor")和#div-editor(标题为 "Div-based editor"); - 脚本通过当前页面 URL 中的路径名解析测试目标环境与皮肤:
var pathRegex = /(android|ios|desktop)(?:button|richcombo)(moono|kama)?/i, match = location.pathname.match( pathRegex ), expectedEnv = match[ 1 ], skin = match[ 2 ] ? match[ 2 ] : 'moono-lisa';即:文件名为desktoprichcombo.md时解析出expectedEnv = 'desktop'、skin = 'moono-lisa';若是desktoprichcombomoono.md则skin = 'moono',以此类推。皮肤名直接作为skin配置传入编辑器:
CKEDITOR.replace( 'iframe-editor', { skin: skin } ); CKEDITOR.replace( 'div-editor', { extraPlugins: 'divarea', skin: skin } );- 模板内置了
isIgnoredEnv()环境判定:通过navigator.userAgent判断当前是 Android、iOS 还是桌面环境。若与文件名期望的环境不匹配,则调用bender.ignore()跳过该测试。例如在 Android 真机上打开desktoprichcombo测试时会被自动忽略,从而保证每个测试只在正确的设备类别上执行。
四、源码级原理(一):Rich Combo 的 DOM 结构
Rich Combo 的渲染模板定义在 plugins/richcombo/plugin.js 中。其外层容器为cke_combo,可点击区域为a.cke_combo_button,内部依次是文本区cke_combo_text与下拉箭头区cke_combo_open/cke_combo_arrow:
+-- .cke_combo ----------------------------------------------------------------------+ | +-- .cke_combo_label --+ +-- .cke_combo_button ----------------------------------+ | | | | | +-- .cke_combo_text --+ +-- .cke_combo_open --------+ | | | | | | | | | +-- .cke_combo_arrow --+ | | | | | | | +---------------------+ +------------------------+ | | | | +----------------------+ +------------------------------------------------------+ | +-----------------------------------------------------------------------------------+上面的结构示意图来自 skins/moono-lisa/richcombo.css 文件头的注释。值得注意的是,cke_combo_label(标签)在视觉上是隐藏的,但保留用于无障碍语义(aria-labelledby引用它),见 plugins/richcombo/plugin.js。而悬停/按下样式的"舞台"全部集中在a.cke_combo_button上。
五、源码级原理(二):皮肤 CSS 中的三态视觉定义
测试文档期望的"悬停背景变深、点击后按下状态背景变浅",在皮肤样式中有精确对应的实现。skins/moono-lisa/richcombo.css 定义了 Combo 的两种状态类:
/* 按下态(on):浅色背景 #fff,深色边框 */ .cke_combo_on a.cke_combo_button, .cke_combo_off a.cke_combo_button:active { background: #fff; } /* 悬停/聚焦/激活态(off + hover):较深背景 #e5e5e5 */ .cke_combo_on a.cke_combo_button, .cke_combo_off a.cke_combo_button:hover, .cke_combo_off a.cke_combo_button:focus, .cke_combo_off a.cke_combo_button:active { background: #e5e5e5; border: 1px solid #bcbcbc; /* 通过负 margin 让边框覆盖相邻分隔线 */ padding: 0 0 0 1px; margin-left: -1px; }对照测试步骤可以一一对应:
- 步骤 1 的"悬停样式(背景变深)"即
a.cke_combo_button:hover时的background: #e5e5e5(相对默认背景更深的灰色); - 步骤 2 的"点击激活 toggled on 状态(背景变浅)"即
.cke_combo_on类生效时background: #fff(白色/更浅); - 两套规则中
border: 1px solid #bcbcbc与margin-left: -1px的组合,是为了让高亮边框恰好覆盖 Combo 之间的垂直分隔线,视觉上形成连续的按下效果。该文件还对 RTL 布局(.cke_rtl)和高对比度模式(.cke_hc,border: 3px solid #000)分别做了对称处理(见 richcombo.css)。
关键:hover: none媒体查询正是 #4611 的修复点
同一个文件中,紧接状态规则之后有一段带注释的媒体查询(richcombo.css):
/* Disable hover styles on mobile devices (#4611). */ @media screen and ( hover: none ) { .cke_combo_off a.cke_combo_button:hover { background: transparent; border-color: transparent; } /* We need to restore :active styles. */ .cke_combo_off a.cke_combo_button:active { background: #fff; border: 1px solid #bcbcbc; } }这正是 Bug #4611 的修复核心:在hover: none(触摸屏)环境下禁用:hover样式,同时保留:active点击反馈。桌面端测试文档要求"必须有悬停样式",是因为桌面设备hover: none不成立,悬停样式必须照常工作。
此外,皮肤脚本 skins/moono-lisa/skin.js 通过模板字符串将 Combo 悬停/按下颜色与主题色板绑定:
'{id} a.cke_combo_button:hover,' + '{id} a.cke_combo_button:focus,' + '{id} .cke_combo_on a.cke_combo_button [' + 'border-color:{toolbarElementsBorder};' + 'background-color:{darkBackground};' + '] '这说明悬停/按下样式不只是写死的颜色,而是随皮肤主题色(darkBackground、toolbarElementsBorder)联动,这也是测试需要按 Moono-Lisa、Moono、Kama 三套皮肤分别验证的原因。
六、源码级原理(三):Rich Combo 的 TRISTATE 状态机
CSS 类cke_combo_on/cke_combo_off由 Rich Combo 组件的setState()方法控制,实现在 plugins/richcombo/plugin.js:
setState: function( state ) { if ( this._.state == state ) return; var el = this.document.getById( 'cke_' + this.id ), linkEl = el.getElementsByTag( 'a' ).getItem( 0 ); el.setState( state, 'cke_combo' ); state == CKEDITOR.TRISTATE_DISABLED ? el.setAttribute( 'aria-disabled', true ) : el.removeAttribute( 'aria-disabled' ); if ( linkEl ) { linkEl.setAttribute( 'aria-expanded', state == CKEDITOR.TRISTATE_ON ); } this._.state = state; }这里的关键调用链是:
el.setState( state, 'cke_combo' )会依据CKEDITOR.TRISTATE_ON / OFF / DISABLED在容器上设置cke_combo_on/cke_combo_off/cke_combo_disabled类,从而触发第五节中的皮肤规则;- 同时同步维护无障碍属性:禁用时设置
aria-disabled="true",面板展开(TRISTATE_ON)时设置aria-expanded="true"。
按下(toggled on)状态在源码中的生命周期非常清晰:
- 用户点击
a.cke_combo_button,触发clickFn(plugin.js)进入instance.execute(); createPanel()创建浮层面板,面板onShow回调中执行me.setState( CKEDITOR.TRISTATE_ON )并置me._.on = 1(plugin.js)——此时 Combo 进入"按下态"(浅色背景),对应测试步骤 2;- 面板隐藏时(选择某项或按 Esc),
onHide回调将状态恢复为TRISTATE_OFF(plugin.js)——按下态解除; - 另外,
updateState()(plugin.js)监听activeFilterChange、mode、selectionChange、readOnly事件,负责把 Combo 切换到TRISTATE_DISABLED(如编辑器只读或当前模式不支持时),并优先保证 Combo 处于展开状态时不抢改状态。
值得注意的细节是execute()中的防重入逻辑(plugin.js):如果_.on为真(面板已打开),再次点击会直接panel.hide()收起面板,而不是重复展开。这与"点击一次变浅、再点一次恢复"的交互直觉一致。
七、测试中两个 Combo 的真实来源:stylescombo 与 format 插件
测试页加载了stylescombo与format两个插件,它们分别通过editor.ui.addRichCombo()注册了名为Styles与Format的 Rich Combo:
- plugins/stylescombo/plugin.js:
editor.ui.addRichCombo( 'Styles', { ... panel: { multiSelect: true, ... } } ),样式列表按块级/内联/对象类型分组; - plugins/format/plugin.js:
editor.ui.addRichCombo( 'Format', { ... panel: { multiSelect: false, ... } } ),格式列表(段落、标题 H1–H6、预格式化等)逐项添加。
两者在init回调中通过this.add( value, previewHtml, label )填充列表项(如 stylescombo/plugin.js),并在onClick中执行editor.applyStyle()/removeStyle()。因此测试者在页面上看到的"Rich Combo"就是这两个真实业务组件,验证的是用户实际会遇到的交互路径,而非抽象测试桩。
八、如何运行与验收这套手动测试
该测试是 Bender.js 手动测试体系的一部分,相关运行配置见仓库根目录 bender.js(其中manualBrowsers定义了接受手动测试的浏览器列表)。验收流程建议如下:
- 在桌面浏览器中打开由 Bender 服务器提供的手动测试页面(对应
desktoprichcombo条目); - 将鼠标移入第一个编辑器的 Styles / Format 组合框,确认背景变为较深的灰色(
#e5e5e5悬停样式); - 单击组合框展开下拉面板,确认按钮呈现较浅的按下态(
#fff背景 +#bcbcbc边框); - 关闭面板后,对第二个(Div 编辑器)重复第 2、3 步;
- 如需覆盖其他皮肤,切换至
desktoprichcombomoono(Moono,另需验证按下态上的附加悬停阴影)与desktoprichcombokama(Kama); - 若在 Android / iOS 真机上验证 #4611 修复本身,则使用对应的
androidrichcombo*.md/iosrichcombo*.md变体——模板中的isIgnoredEnv()会自动忽略与当前设备不匹配的测试。
判定标准即测试文档中的 Expected result:悬停样式存在、点击后按下态存在,且两个编辑器表现一致;任一缺失即为 Unexpected result,需要回归检查 skins/moono-lisa/richcombo.css 中的状态规则是否被hover: none或其他皮肤覆盖逻辑误伤。
九、小结
desktoprichcombo.md虽然只是一份寥寥数步的手动测试说明,但它精确刻画了 CKEditor 4 工具栏 Rich Combo 的交互状态契约:桌面端悬停必须呈现深色反馈、点击展开必须呈现浅色按下态,且 Iframe / Div 两种编辑模式行为一致。其背后是plugins/richcombo的 TRISTATE 状态机(cke_combo_on/cke_combo_off/ disabled 三态切换 + ARIA 同步)与皮肤 CSS 中:hover、:active、.cke_combo_on规则的协同,而 #4611 的修复则以@media screen and ( hover: none )精确隔离了移动端的伪悬停问题。读懂这份测试文档与对应源码,即可完整掌握该交互的验收口径与实现机理。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
TanStack Table Angular 列分组(Grouping)完整指南:从状态管理到分组行模型源码剖析
TanStack Table Angular 列分组(Grouping)完整指南:从状态管理到分组行模型源码剖析 导读 本文围绕 TanStack Table(
前端UI组件CKEditor 4 Android 端 Rich Combo 悬停样式修复:Issue 4611 的回归测试设计与移动端 hover 行为解析
CKEditor 4 Android 端 Rich Combo 悬停样式修复:Issue 4611 的回归测试设计与移动端 hover 行为解析 导读 本文聚焦
前端UI组件CKEditor 4 工具栏富组合框(Rich Combo)悬停样式回归测试指南:以 Kama 皮肤桌面端手动测试为例
CKEditor 4 工具栏富组合框(Rich Combo)悬停样式回归测试指南:以 Kama 皮肤桌面端手动测试为例 在 CKEditor 4 中,工具栏的「
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考