- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
导读
本文以仓库中 imagebase 插件 Upload 功能的手动测试文档(tests/plugins/imagebase/features/manual/upload.md)为主线,系统讲解 CKEditor 4 中"将本地文件直接拖入编辑区、自动创建占位 Widget 并显示文件名"这一上传链路的测试场景与底层实现。读完本文,你将掌握该手动测试的步骤、预期结果、bender 测试元数据的含义,并能结合 imagebase 插件源码 理解文件类型匹配、FileLoader 上传、进度条渲染与 Cloud Services 鉴权等核心机制,从而具备自行复现、验证与扩展该上传功能的能力。
测试文档定位:一段面向人工验证的 Upload 功能用例
upload.md是 CKEditor 4 基于 Bender 测试框架的手动测试(manual test)用例,归属于 imagebase 插件的 features 目录。其完整内容围绕一个核心交互场景展开:
测试步骤:从文件系统拖拽任意文件到编辑区(editable)中。预期结果:创建一个占位 Widget(placeholder widget),其中包含文件名。注意事项:截至文档编写时,Cloud Services 只接受图片资源,对其他资源类型会返回
400状态码;Cloud Services 实际返回的 URL 可以在浏览器控制台日志中查看。
这段描述虽然简短,但它浓缩了三条关键技术事实:
- 触发方式是"拖放文件"(drag and drop),对应 imagebase 的
uploadfeature 在编辑器paste事件上的监听逻辑; - 产物形态是"包含文件名的 placeholder widget",即上传开始阶段先用本地 blob URL 渲染一个占位 Widget,而非直接插入最终图片;
- 后端约束是 Cloud Services 网关仅接受图片 MIME 类型,其余类型返回
400,这决定了该功能在默认配置下对非图片文件的失败路径。
文档头部还携带了 Bender 测试框架所需的元数据,是运行与筛选该测试的关键:
@bender-tags: 4.9.0, feature, 932 @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, imagebase, elementspath, placeholder, cloudservices, easyimage @bender-include: %BASE_PATH%/plugins/easyimage/_helpers/tools.js其中@bender-tags声明了该测试自 4.9.0 版本引入、属于 feature 类测试,关联内部 issue 932;@bender-ckeditor-plugins列出测试编辑器需要加载的插件集合;@bender-include引入测试辅助脚本(实际文件位于 tests/plugins/easyimage/_helpers/tools.js,其中定义了CLOUD_SERVICES_UPLOAD_GATEWAY与CLOUD_SERVICES_TOKEN_URL两个测试用的云端地址常量);@bender-ui: collapsed则控制测试页面在 Bender 结果面板中的展示方式。
手动测试页面的完整实现
与upload.md配套的页面源码是 tests/plugins/imagebase/features/manual/upload.html,它给出了该测试的可运行环境。核心代码可以拆成三层:
第一层:编辑器初始化与云端配置。页面创建一个经典编辑器,通过cloudServices_uploadUrl与cloudServices_tokenUrl接入 Cloud Services 网关,并加载 easyimage 的内容样式:
var editor = CKEDITOR.replace( 'classic', { cloudServices_uploadUrl: easyImageTools.CLOUD_SERVICES_UPLOAD_GATEWAY, cloudServices_tokenUrl: easyImageTools.CLOUD_SERVICES_TOKEN_URL, height: 500, contentsCss: '/apps/ckeditor/plugins/easyimage/styles/easyimage.css' } );第二层:为 placeholder Widget 挂载 upload feature。关键代码监听widgetDefinition事件,在 placeholder 的定义上调用CKEDITOR.plugins.imagebase.addFeature( this, 'upload', def ),并进一步定制三个上传相关的属性:
if ( def.name === 'placeholder' ) { var ret = CKEDITOR.plugins.imagebase.addFeature( this, 'upload', def ), i; for ( i in ret ) { def[ i ] = ret[ i ]; } // 使用 Cloud Services 专用的 Loader 类型。 def.loaderType = CKEDITOR.plugins.cloudservices.cloudServicesLoader; // 匹配任意文件类型(正则 /^.*$/)。 def.supportedTypes = /^.*$/; def.progressReporterType = CKEDITOR.plugins.imagebase.progressBar; }注意这里supportedTypes被设置为/^.*$/,这正是"拖入任意文件都会创建占位 Widget"的根源——与 easyimage 插件 默认只接受image/(jpeg|png|gif|bmp)的正则形成鲜明对比。
第三层:用上传事件驱动 Widget 内容。页面在instanceReady后监听instanceCreated,为每个 placeholder 实例绑定uploadStarted/uploadDone/uploadFailed三个一次性事件,实现"上传中显示 Uploading...、成功后显示文件名、失败后显示错误码"的完整状态机:
widget.once( 'uploadStarted', function() { widget.setData( 'name', 'Uploading...' ); } ); widget.once( 'uploadDone', function( uploadEvent ) { var loader = uploadEvent.data.loader; widget.setData( 'name', 'File "' + loader.file.name + '" uploaded!' ); console.log( 'File ' + loader.file.name + ' can be found at ' + loader.responseData.response[ 'default' ] ); } );console.log中打印的response[ 'default' ]就是upload.md末尾提示"可以在控制台日志中找到 Cloud Services 实际返回 URL"的来源——easyimage 的服务端响应以default字段给出默认尺寸图片地址(参见 plugins/easyimage/plugin.js 中的_parseSrcSet与uploadDone处理逻辑)。
页面开头还有两行环境守卫:移动端因缺少文件拖放支持直接bender.ignore()跳过,bender.tools.ignoreUnsupportedEnvironment( 'easyimage' )则在 unsupported 环境(如 IE10 及以下,见 easyimage 插件的isSupportedEnvironment)中忽略该测试。
底层原理:imagebase 的 upload feature 是如何工作的
占位 Widget 能"凭空"出现,靠的是 imagebase 插件内置的uploadfeature(定义见 plugins/imagebase/plugin.js 的getUploadFeature)。它是一个以 mixin 方式提供给 Widget 定义的私有 API,通过addFeature注入到目标 Widget 上。其核心链路如下。
1. setUp:拦截 paste 事件并过滤文件
feature 的setUp在编辑器上注册paste监听。当dataTransfer.isFileTransfer()为真且编辑器非只读时,遍历拖入的文件,用definition.supportedTypes正则逐一匹配 MIME 类型,收集所有命中文件后取消默认粘贴行为:
if ( editor.readOnly || !isFileTransfer ) { return; } for ( var i = 0; i < filesCount; i++ ) { curFile = dataTransfer.getFile( i ); if ( CKEDITOR.fileTools.isTypeSupported( curFile, definition.supportedTypes ) ) { matchedFiles.push( curFile ); } } if ( matchedFiles.length === 0 ) { return; } evt.cancel(); evt.stop();从 tests/plugins/imagebase/features/upload.js 的自动化用例可以验证该过滤行为:test dropping supported file type creates a widget断言命中 1 个 Widget,而test dropping unsupported file does not create a widget断言 0 个;test dropping file into a readonly does not create a widget则验证只读状态下不会创建 Widget。
2. 创建 FileLoader 并插入占位 Widget
对每个匹配文件,feature 依次调用两个私有方法:
_spawnLoader( editor, curFile, definition, curFile.name ):通过editor.uploadRepository.create()创建CKEDITOR.fileTools.fileLoader实例(loaderType 可定制,默认CKEDITOR.fileTools.fileLoader),并调用loader loadMethod发起加载与上传。loadMethod默认为loadAndUpload,其行为定义在 plugins/filetools/plugin.js 中:先load()用 FileReader 把文件读入内存,再链式触发upload()发送 XHR。_insertWidget( editor, definition, URL.createObjectURL( curFile ), true, { uploadId: loader.id } ):用URL.createObjectURL()生成的本地 blob URL 作为临时src,按 Widget 定义中的模板输出 HTML,包装为 Widget 并初始化,同时把uploadId写入 Widget 数据,建立 Widget 与 Loader 的关联。
多文件拖入时,代码还通过放大并折叠选区的方式调整光标位置,确保每个文件生成独立的 Widget 且最后一个保持选中状态。
3. init 与 _beginUpload:绑定事件与进度条
Widget 实例在ready后读取data.uploadId,在editor.uploadRepository.loaders中找回对应的 Loader,随即调用_beginUpload。该方法是整个上传状态机的枢纽:
widget.setData( 'uploadId', loader.id ); if ( widget.fire( 'uploadStarted', loader ) !== false && widget.progressReporterType ) { if ( !widget._isLoaderDone( loader ) ) { widget.wrapper.addClass( 'cke_widget_wrapper_uploading' ); var progress = new widget.progressReporterType(); widget.wrapper.append( progress.wrapper ); progress.bindLoader( loader ); } else { // ... } }它同时注册了loader.on( 'abort' | 'error' | 'uploaded' )三个监听,分别映射到failHandling与uploadComplete:
- 上传失败或中止时,清理
cke_widget_wrapper_uploading类,触发uploadFailed事件;若该事件未被cancel,默认行为是调用editor.widgets.del( widget )直接删除 Widget; - 上传成功时触发
uploadDone事件,交由业务代码(如 easyimage 或手动测试页面)把 blob URL 替换为服务端返回的最终地址。
这些事件在 upload.js 中有逐项断言:test events验证成功路径只触发uploadStarted与uploadDone;test upload error验证失败路径触发uploadFailed且 Widget 被移除;test upload error event is cancelable验证cancel后可保留 Widget(手动测试页面正是用这一机制把失败信息写进占位符文本)。
4. 进度条:progressReporterType 与 progressBar
_beginUpload中widget.progressReporterType默认指向CKEDITOR.plugins.imagebase.progressBar(见 plugins/imagebase/plugin.js 的ProgressBar)。它渲染一个.cke_loader > .cke_bar结构,updated( progress )按 0~1 的进度值换算百分比并写入width样式;bindLoader则把进度条与 Loader 的update事件挂钩,并自动在abort/error/uploaded时收尾。进度刷新频率受UPLOAD_PROGRESS_THROTTLING = 100(毫秒)节流控制。
值得注意,progressReporterType可以被设为false或null以完全禁用进度条,也可以在uploadStarted事件中evt.cancel()阻止默认进度条创建——自动化测试test preventing default progress reporter with event与test preventing default progress reporter with widgetDefinition.progressReporterType分别覆盖了这两种方式。若想自定义指示器形态,可以参考 tests/plugins/imagebase/features/_helpers/tools.js 中基于progressReporter扩展出的遮罩进度(getProgressOverlapType)与圆形进度(getProgressCircleType),其交互演示见 tests/plugins/imagebase/manual/progressbar.html。
Cloud Services 集成与 400 响应
upload.md特别强调"Cloud Services 目前只接受图片,其他资源类型返回 400",这与 cloudservices 插件的实现紧密相关。在 plugins/cloudservices/plugin.js 中:
cloudServicesLoader是fileLoader的子类,upload()未传 URL 时回退到editor.config.cloudServices_uploadUrl,缺失则抛出cloudservices-no-upload-url错误;- 插件在
beforeInit阶段通过cloudServices_tokenUrl拉取安全令牌,并每隔REFRESH_INTERVAL(默认 3600000ms,即 1 小时)自动刷新;未配置 token URL 会抛出cloudservices-no-token-url错误; - 发送请求时,插件把上传字段从
upload重命名为file,并以Authorization请求头携带令牌;响应则拦截fileUploadResponse事件,将 JSON 响应体原样解析为loader.responseData.response。
因此,当向该网关拖入非图片文件时,服务端返回400,Loader 触发error状态,Widget 走uploadFailed分支——手动测试页面会把它渲染为Uploading "xxx" failed (code 400) 😢,这正是upload.md中"返回 400 代码"在 UI 上的可见表现。
顺带一提,uploadsimple.html 是同一 feature 的精简演示:它只接受image/(jpeg|png),用div而非 placeholder 作为 Widget 模板,通过uploadDone/uploadFailed直接把文件名写入元素文本,可作为理解"自定义 Widget + upload feature"的最小示例。
自动化测试佐证:upload feature 的边界行为
除了手动测试,imagebase 还提供了覆盖度很高的自动化测试 tests/plugins/imagebase/features/upload.js,可用来交叉验证本文所述行为:
| 测试用例 | 验证点 |
|---|---|
test dropping supported file type creates a widget | 支持的 MIME 类型生成对应 Widget |
test dropping unsupported file does not create a widget | 不支持的 MIME 类型不生成 Widget |
test pasting supported file alongside HTML content does not create a widget | 文件与 HTML 混贴时不触发上传 |
test multiple files create multiple widgets | 多文件一次拖入生成多个 Widget |
test loader can be customized | loaderType可替换为自定义 Loader |
test events/test upload error | uploadStarted、uploadDone、uploadFailed触发路径 |
test default progress reporter/test progress reporter customization | 默认与自定义进度条 |
test widget gets class during the upload | 上传中cke_widget_wrapper_uploading类存在,完成后移除 |
test copy and paste file during upload | 上传中复制粘贴 Widget 会共享 Loader 并继续完成上传 |
这些用例同时印证了upload.md未直接写出但真实存在的边界:上传中的 Widget 被复制粘贴到同一编辑器甚至另一个编辑器时,新 Widget 会依据uploadId找到对应 Loader 并继续上传(参见test copy in-progress widget into a different editor)。
如何运行与验证
- 准备 Bender 环境:仓库根目录的 bender.js、bender.ci.js 与 bender-runner.config.json 提供了测试运行配置;手动测试通常在
bender server启动后于浏览器中访问对应页面运行。 - 加载目标用例:打开 upload.html,页面会自动创建带 imagebase、placeholder、cloudservices、easyimage 等插件的编辑器。
- 执行测试:从本地文件系统拖拽任意文件到编辑区;观察是否生成黄色背景的占位 Widget、其文本是否变为文件名;打开浏览器控制台查看 Cloud Services 返回的真实 URL。
- 验证失败路径:拖入非图片文件,预期 Widget 显示
400错误信息;拖入图片则上传成功后占位内容被替换为File "xxx" uploaded!。 - 对照自动化结果:运行 features/upload.js 可一键回归上述所有边界行为(成功、失败、多文件、只读、复制粘贴、进度条等),与手动测试互为补充。
需要注意,该手动测试依赖 Cloud Services 测试网关(URL 常量定义在 tests/plugins/easyimage/_helpers/tools.js),属于在线服务;若离线环境,可参考uploadsimple页面将loaderType替换为自定义 Loader 并指向本地上传接口,或参考 plugins/filetools/plugin.js 中fileUploadRequest/fileUploadResponse事件自行实现上传响应解析。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
CKEditor 4拖拽上传功能实现:图片、文件、视频一键上传终极指南
CKEditor 4拖拽上传功能实现:图片、文件、视频一键上传终极指南 CKEditor 4作为业界领先的富文本编辑器,其拖拽上传功能为用户提供了极致便捷的内容
前端UI组件CKEditor 4 Enhanced Image 图片拖拽边界测试用例解析:dndwithcaption 手动测试与 Widget 拖放机制
CKEditor 4 Enhanced Image 图片拖拽边界测试用例解析:dndwithcaption 手动测试与 Widget 拖放机制 导读 本文围绕仓
前端UI组件CKEditor 4 图片 Widget 标题(Caption)功能机制与手动测试指南
CKEditor 4 图片 Widget 标题(Caption)功能机制与手动测试指南 本篇指南聚焦于 CKEditor 4 中 imagebase 插件提供的
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考