diff --git a/docs/api/editor/codeBlockServiceMethods.md b/docs/api/editor/codeBlockServiceMethods.md index 928ce47d..26ef215c 100644 --- a/docs/api/editor/codeBlockServiceMethods.md +++ b/docs/api/editor/codeBlockServiceMethods.md @@ -288,15 +288,11 @@ 销毁 codeBlockService,重置状态并移除所有事件监听和插件 -## use - -使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展 - ## usePlugin - **详情:** -相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 + usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值 diff --git a/docs/api/editor/editorServiceMethods.md b/docs/api/editor/editorServiceMethods.md index 181d9045..2789fe89 100644 --- a/docs/api/editor/editorServiceMethods.md +++ b/docs/api/editor/editorServiceMethods.md @@ -712,45 +712,11 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调 移除所有事件监听,清空state,移除所有插件 -## use - -使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展 - -- **示例:** - -```js -import { editorService, getAddParent } from "@tmagic/editor"; -import { ElMessageBox } from "element-plus"; - -editorService.use({ - // 添加是否删除节点确认提示 - async remove(node, next) { - await ElMessageBox.confirm("是否删除", "提示", { - confirmButtonText: "确定", - cancelButtonText: "取消", - type: "warning", - }); - - next(); - }, - - add(node, next) { - // text组件只能添加到container中 - const parentNode = getAddParent(node); - if (node.type === "text" && parentNode?.type !== "container") { - return; - } - - next(); - }, -}); -``` - ## usePlugin - **详情:** -相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 + usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值 diff --git a/docs/api/editor/propsServiceMethods.md b/docs/api/editor/propsServiceMethods.md index 19302188..f25163cf 100644 --- a/docs/api/editor/propsServiceMethods.md +++ b/docs/api/editor/propsServiceMethods.md @@ -254,15 +254,11 @@ 销毁propsService -## use - -使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展 - ## usePlugin - **详情:** -相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 + usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值 diff --git a/docs/api/editor/storageServiceMethods.md b/docs/api/editor/storageServiceMethods.md index 2fd611f5..265584b1 100644 --- a/docs/api/editor/storageServiceMethods.md +++ b/docs/api/editor/storageServiceMethods.md @@ -231,28 +231,11 @@ import { storageService } from '@tmagic/editor'; storageService.destroy(); ``` -## use - -使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展 - -- **示例:** - -```js -import { storageService } from '@tmagic/editor'; - -storageService.use({ - getItem(key, options, next) { - console.log('获取存储项:', key); - return next(); - }, -}); -``` - ## usePlugin - **详情:** -相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 + usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值 diff --git a/docs/api/editor/uiServiceMethods.md b/docs/api/editor/uiServiceMethods.md index 2b16761e..3b3ca6bd 100644 --- a/docs/api/editor/uiServiceMethods.md +++ b/docs/api/editor/uiServiceMethods.md @@ -179,29 +179,11 @@ import { uiService } from '@tmagic/editor'; uiService.destroy(); ``` -## use - -使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展 - -- **示例:** - -```js -import { uiService } from '@tmagic/editor'; - -uiService.use({ - async zoom(value, next) { - console.log('缩放前:', uiService.get('zoom')); - await next(); - console.log('缩放后:', uiService.get('zoom')); - }, -}); -``` - ## usePlugin - **详情:** -相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 + usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展 每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值 diff --git a/packages/editor/src/services/BaseService.ts b/packages/editor/src/services/BaseService.ts index 81cee81e..b4217379 100644 --- a/packages/editor/src/services/BaseService.ts +++ b/packages/editor/src/services/BaseService.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-misused-promises */ /* * Tencent is pleased to support the open source community by making TMagicEditor available. * @@ -19,45 +18,32 @@ import { EventEmitter } from 'events'; -import { compose } from '@editor/utils/compose'; - const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpperCase()}${name.substring(1)}`; const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]'; -const doAction = ( - args: any[], - scope: any, - sourceMethod: any, - beforeMethodName: string, - afterMethodName: string, - fn: (args: any[], next?: Function | undefined) => void, -) => { - try { - let beforeArgs = args; +const doAction = (args: any[], scope: any, sourceMethod: any, beforeMethodName: string, afterMethodName: string) => { + let beforeArgs = args; - for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) { - beforeArgs = beforeMethod(...beforeArgs) || []; + for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) { + beforeArgs = beforeMethod(...beforeArgs) || []; - if (isError(beforeArgs)) throw beforeArgs; + if (isError(beforeArgs)) throw beforeArgs; - if (!Array.isArray(beforeArgs)) { - beforeArgs = [beforeArgs]; - } + if (!Array.isArray(beforeArgs)) { + beforeArgs = [beforeArgs]; } - - let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope)); - - for (const afterMethod of scope.pluginOptionsList[afterMethodName]) { - returnValue = afterMethod(returnValue, ...beforeArgs); - - if (isError(returnValue)) throw returnValue; - } - - return returnValue; - } catch (error) { - throw error; } + + let returnValue: any = sourceMethod.apply(scope, beforeArgs); + + for (const afterMethod of scope.pluginOptionsList[afterMethodName]) { + returnValue = afterMethod(returnValue, ...beforeArgs); + + if (isError(returnValue)) throw returnValue; + } + + return returnValue; }; const doAsyncAction = async ( @@ -66,40 +52,34 @@ const doAsyncAction = async ( sourceMethod: any, beforeMethodName: string, afterMethodName: string, - fn: (args: any[], next?: Function | undefined) => Promise | void, ) => { - try { - let beforeArgs = args; + let beforeArgs = args; - for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) { - beforeArgs = (await beforeMethod(...beforeArgs)) || []; + for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) { + beforeArgs = (await beforeMethod(...beforeArgs)) || []; - if (isError(beforeArgs)) throw beforeArgs; + if (isError(beforeArgs)) throw beforeArgs; - if (!Array.isArray(beforeArgs)) { - beforeArgs = [beforeArgs]; - } + if (!Array.isArray(beforeArgs)) { + beforeArgs = [beforeArgs]; } - - let returnValue: any = await fn(beforeArgs, sourceMethod.bind(scope)); - - for (const afterMethod of scope.pluginOptionsList[afterMethodName]) { - returnValue = await afterMethod(returnValue, ...beforeArgs); - - if (isError(returnValue)) throw returnValue; - } - - return returnValue; - } catch (error) { - throw error; } + + let returnValue: any = await sourceMethod.apply(scope, beforeArgs); + + for (const afterMethod of scope.pluginOptionsList[afterMethodName]) { + returnValue = await afterMethod(returnValue, ...beforeArgs); + + if (isError(returnValue)) throw returnValue; + } + + return returnValue; }; /** - * 提供两种方式对Class进行扩展 - * 方法1: + * 对Class进行扩展 * 给Class中的每个方法都添加before after两个钩子 - * 给Class添加一个usePlugin方法,use方法可以传入一个包含before或者after方法的对象 + * 给Class添加一个usePlugin方法,usePlugin方法可以传入一个包含before或者after方法的对象 * * 例如: * Class EditorService extends BaseService { @@ -124,27 +104,9 @@ const doAsyncAction = async ( * * 调用时的参数会透传到before方法的参数中, 然后before的return 会作为原方法的参数和after的参数,after第一个参数则是原方法的return值; * 如需终止后续方法调用可以return new Error(); - * - * 方法2: - * 给Class中的每个方法都添加中间件 - * 给Class添加一个use方法,use方法可以传入一个包含源对象方法名作为key值的对象 - * - * 例如: - * Class EditorService extends BaseService { - * constructor() { - * super([ { name: 'add', isAsync: true },]); - * } - * add(value) { return result; } - * }; - * - * const editorService = new EditorService(); - * editorService.use({ - * add(value, next) { console.log(value); next() }, - * }); */ -export default class extends EventEmitter { +class BaseService extends EventEmitter { private pluginOptionsList: Record = {}; - private middleware: Record = {}; private taskList: (() => Promise)[] = []; private doingTask = false; @@ -161,14 +123,12 @@ export default class extends EventEmitter { this.pluginOptionsList[beforeMethodName] = []; this.pluginOptionsList[afterMethodName] = []; - this.middleware[propertyName] = []; - const fn = compose(this.middleware[propertyName], isAsync); Object.defineProperty(scope, propertyName, { value: isAsync ? async (...args: any[]) => { if (!serialMethods.includes(propertyName)) { - return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); + return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName); } // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2; @@ -176,7 +136,7 @@ export default class extends EventEmitter { const promise = new Promise((resolve, reject) => { this.taskList.push(async () => { try { - const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn); + const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName); resolve(value); } catch (e) { reject(e); @@ -190,20 +150,11 @@ export default class extends EventEmitter { return promise; } - : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn), + : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName), }); }); } - /** - * @deprecated 请使用usePlugin代替 - */ - public use(options: Record) { - for (const [methodName, method] of Object.entries(options)) { - if (typeof method === 'function') this.middleware[methodName].push(method); - } - } - public usePlugin(options: Record) { for (const [methodName, method] of Object.entries(options)) { if (typeof method === 'function' && !this.pluginOptionsList[methodName].includes(method)) { @@ -224,10 +175,6 @@ export default class extends EventEmitter { for (const key of Object.keys(this.pluginOptionsList)) { this.pluginOptionsList[key] = []; } - - for (const key of Object.keys(this.middleware)) { - this.middleware[key] = []; - } } private async doTask() { @@ -240,3 +187,5 @@ export default class extends EventEmitter { this.doingTask = false; } } + +export default BaseService; diff --git a/packages/editor/src/services/editor.ts b/packages/editor/src/services/editor.ts index 738d0f35..f00ab362 100644 --- a/packages/editor/src/services/editor.ts +++ b/packages/editor/src/services/editor.ts @@ -66,25 +66,6 @@ import type { HistoryOpContext } from '@editor/utils/editor-history'; import { applyHistoryAddOp, applyHistoryRemoveOp, applyHistoryUpdateOp } from '@editor/utils/editor-history'; import { beforePaste, getAddParent } from '@editor/utils/operator'; -/** - * 经过 BaseService 的插件 / 中间件包装后,源方法的最后一个形参可能被注入为 dispatch 函数 - * 当 options 形参位置被注入为函数(或为 null)时,将其归一为空对象,避免后续逻辑误读 - */ -const safeOptions = (options: unknown): T => { - const empty = {}; - if (!options || typeof options === 'function') return empty as T; - return options as T; -}; - -/** - * 经过 BaseService 的插件 / 中间件包装后,源方法的形参可能被注入为 dispatch 函数 - * 当 parent 形参位置被注入为函数(或为空值)时,归一为 null,由调用方继续走默认 parent 逻辑 - */ -const safeParent = (parent: unknown): MContainer | null => { - if (!parent || typeof parent === 'function') return null; - return parent as MContainer; -}; - class Editor extends BaseService { public state: StoreState = reactive({ root: null, @@ -212,7 +193,7 @@ class Editor extends BaseService { * 只有容器拥有布局 */ public async getLayout(parent: MNode, node?: MNode | null): Promise { - if (node && typeof node !== 'function' && isFixed(node.style || {})) return Layout.FIXED; + if (node && isFixed(node.style || {})) return Layout.FIXED; if (parent.layout) { return parent.layout; @@ -393,11 +374,8 @@ class Editor extends BaseService { public async add( addNode: AddMNode | MNode[], parent?: MContainer | null, - options?: DslOpOptions, + { doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {}, ): Promise { - const safeParentNode = safeParent(parent); - const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options); - this.captureSelectionBeforeOp(); const stage = this.get('stage'); @@ -420,7 +398,7 @@ class Editor extends BaseService { if ((isPage(node) || isPageFragment(node)) && root) { return this.doAdd(node, root); } - const parentNode = safeParentNode ?? getAddParent(node); + const parentNode = parent ?? getAddParent(node); if (!parentNode) throw new Error('未找到父元素'); return this.doAdd(node, parentNode); }), @@ -476,9 +454,10 @@ class Editor extends BaseService { return Array.isArray(addNode) ? newNodes : newNodes[0]; } - public async doRemove(node: MNode, options?: DslOpOptions): Promise { - const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options); - + public async doRemove( + node: MNode, + { doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {}, + ): Promise { const root = this.get('root'); if (!root) throw new Error('root不能为空'); @@ -558,9 +537,10 @@ class Editor extends BaseService { * @param options.doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面) * @param options.doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面) */ - public async remove(nodeOrNodeList: MNode | MNode[], options?: DslOpOptions): Promise { - const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options); - + public async remove( + nodeOrNodeList: MNode | MNode[], + { doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {}, + ): Promise { this.captureSelectionBeforeOp(); const nodes = Array.isArray(nodeOrNodeList) ? nodeOrNodeList : [nodeOrNodeList]; @@ -711,9 +691,7 @@ class Editor extends BaseService { * @param options.doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致) * @returns void */ - public async sort(id1: Id, id2: Id, options?: DslOpOptions): Promise { - const { doNotSelect = false } = safeOptions(options); - + public async sort(id1: Id, id2: Id, { doNotSelect = false }: DslOpOptions = {}): Promise { this.captureSelectionBeforeOp(); const root = this.get('root'); @@ -784,10 +762,8 @@ class Editor extends BaseService { public async paste( position: PastePosition = {}, collectorOptions?: TargetOptions, - options?: DslOpOptions, + { doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {}, ): Promise { - const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options); - const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY); if (!Array.isArray(config)) return; @@ -840,9 +816,10 @@ class Editor extends BaseService { * @param options.doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致) * @returns 当前组件节点配置 */ - public async alignCenter(config: MNode | MNode[], options?: DslOpOptions): Promise { - const { doNotSelect = false } = safeOptions(options); - + public async alignCenter( + config: MNode | MNode[], + { doNotSelect = false }: DslOpOptions = {}, + ): Promise { const nodes = Array.isArray(config) ? config : [config]; const stage = this.get('stage'); @@ -922,9 +899,11 @@ class Editor extends BaseService { * @param options.doNotSelect 移动后是否不更新当前选中节点(默认 false) * @param options.doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换) */ - public async moveToContainer(config: MNode, targetId: Id, options?: DslOpOptions): Promise { - const { doNotSelect = false, doNotSwitchPage = false } = safeOptions(options); - + public async moveToContainer( + config: MNode, + targetId: Id, + { doNotSelect = false, doNotSwitchPage = false }: DslOpOptions = {}, + ): Promise { this.captureSelectionBeforeOp(); const root = this.get('root'); diff --git a/packages/editor/src/services/props.ts b/packages/editor/src/services/props.ts index 664640f0..b9e5f49e 100644 --- a/packages/editor/src/services/props.ts +++ b/packages/editor/src/services/props.ts @@ -97,9 +97,9 @@ class Props extends BaseService { return this.state.propsConfigMap; } - public async fillConfig(config: FormConfig, labelWidth?: string) { + public async fillConfig(config: FormConfig, labelWidth = '80px') { return fillConfig(config, { - labelWidth: typeof labelWidth !== 'function' ? labelWidth : '80px', + labelWidth, disabledDataSource: this.getDisabledDataSource(), disabledCodeBlock: this.getDisabledCodeBlock(), }); diff --git a/packages/editor/src/utils/compose.ts b/packages/editor/src/utils/compose.ts deleted file mode 100644 index 0748a6b1..00000000 --- a/packages/editor/src/utils/compose.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @param {Array} middleware - * @return {Function} - */ -export const compose = (middleware: Function[], isAsync: boolean) => { - if (!Array.isArray(middleware)) throw new TypeError('Middleware 必须是一个数组!'); - for (const fn of middleware) { - if (typeof fn !== 'function') throw new TypeError('Middleware 必须由函数组成!'); - } - - /** - * @param {Object} args - * @return {Promise} - * @api public - */ - return (args: any[], next?: Function) => { - // last called middleware # - let index = -1; - return dispatch(0); - function dispatch(i: number): Promise | void { - if (i <= index) { - const error = new Error('next() 被多次调用'); - if (isAsync) { - return Promise.reject(error); - } - throw error; - } - index = i; - let fn = middleware[i]; - if (i === middleware.length && next) fn = next; - if (!fn) { - if (isAsync) { - return Promise.resolve(); - } - return; - } - - if (isAsync) { - try { - return Promise.resolve(fn(...args, dispatch.bind(null, i + 1))); - } catch (err) { - return Promise.reject(err); - } - } - try { - return fn(...args, dispatch.bind(null, i + 1)); - } catch (err) { - throw err; - } - } - }; -}; diff --git a/packages/editor/tests/unit/services/BaseService.spec.ts b/packages/editor/tests/unit/services/BaseService.spec.ts index bc8691c2..0a855af5 100644 --- a/packages/editor/tests/unit/services/BaseService.spec.ts +++ b/packages/editor/tests/unit/services/BaseService.spec.ts @@ -43,20 +43,6 @@ describe('BaseService 同步方法 + plugin', () => { expect(result).toBe(30); }); - test('use 添加同步 middleware', () => { - const svc = new SyncService(); - const order: string[] = []; - svc.use({ - add(_value: number, next: Function) { - order.push('mw-before'); - next(); - order.push('mw-after'); - }, - }); - svc.add(1); - expect(order).toEqual(['mw-before', 'mw-after']); - }); - test('removePlugin 解除指定钩子', () => { const svc = new SyncService(); const before = vi.fn((v: number) => [v]); @@ -66,7 +52,7 @@ describe('BaseService 同步方法 + plugin', () => { expect(before).not.toHaveBeenCalled(); }); - test('removeAllPlugins 清空所有 plugin/middleware', () => { + test('removeAllPlugins 清空所有 plugin', () => { const svc = new SyncService(); const before = vi.fn((v: number) => [v]); svc.usePlugin({ beforeAdd: before }); diff --git a/packages/editor/tests/unit/services/editor.spec.ts b/packages/editor/tests/unit/services/editor.spec.ts index d62eb928..ff6cd62b 100644 --- a/packages/editor/tests/unit/services/editor.spec.ts +++ b/packages/editor/tests/unit/services/editor.spec.ts @@ -648,35 +648,6 @@ describe('moveLayer', () => { }); }); -describe('插件参数兜底', () => { - test('add 的 parent 形参传入函数时不抛错,仍走默认父节点逻辑', async () => { - editorService.set('root', cloneDeep(root)); - await editorService.select(NodeId.NODE_ID); - - // 模拟 BaseService 中间件机制在 parent 位置注入 dispatch 函数 - const dispatchFn = () => {}; - const newNode = await editorService.add({ type: 'text' }, dispatchFn as any); - - // 默认行为:被加到了当前选中节点的父节点 (PAGE) - const addedId = Array.isArray(newNode) ? newNode[0].id : newNode.id; - const parentInfo = editorService.getParentById(addedId); - expect(parentInfo?.id).toBe(NodeId.PAGE_ID); - }); - - test('add 的 options 形参传入函数时不抛错,doNotSelect 回落为默认值', async () => { - editorService.set('root', cloneDeep(root)); - await editorService.select(NodeId.NODE_ID); - - // 模拟 BaseService 中间件机制在 options 位置注入 dispatch 函数 - const dispatchFn = () => {}; - const newNode = await editorService.add({ type: 'text' }, null, dispatchFn as any); - - // 默认行为:当前选中节点变成了新增节点 - const addedId = Array.isArray(newNode) ? newNode[0].id : newNode.id; - expect(editorService.get('node')?.id).toBe(addedId); - }); -}); - describe('undo redo', () => { beforeAll(() => editorService.set('root', cloneDeep(root))); diff --git a/packages/editor/tests/unit/utils/compose.spec.ts b/packages/editor/tests/unit/utils/compose.spec.ts deleted file mode 100644 index d2dbfeab..00000000 --- a/packages/editor/tests/unit/utils/compose.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making TMagicEditor available. - * - * Copyright (C) 2025 Tencent. - */ -import { describe, expect, test } from 'vitest'; - -import { compose } from '@editor/utils/compose'; - -describe('compose 同步', () => { - test('依次调用 middleware 并通过 next 串行', () => { - const order: number[] = []; - const m1 = (next: Function) => { - order.push(1); - next(); - order.push(4); - }; - const m2 = (next: Function) => { - order.push(2); - next(); - order.push(3); - }; - compose([m1, m2], false)([]); - expect(order).toEqual([1, 2, 3, 4]); - }); - - test('next() 多次调用抛错', () => { - const m = (next: Function) => { - next(); - next(); - }; - expect(() => compose([m], false)([])).toThrow(/next\(\) 被多次调用/); - }); - - test('参数 fn 不是函数抛错', () => { - expect(() => compose([null as any], false)([])).toThrow(/Middleware 必须由函数组成/); - }); - - test('参数不是数组抛错', () => { - expect(() => compose('x' as any, false)([])).toThrow(/Middleware 必须是一个数组/); - }); - - test('支持 next 参数透传', () => { - const order: number[] = []; - const tail = () => order.push(99); - compose( - [ - (next: Function) => { - order.push(1); - next(); - }, - ], - false, - )([], tail); - expect(order).toEqual([1, 99]); - }); -}); - -describe('compose 异步', () => { - test('返回 Promise,按 next 顺序执行', async () => { - const order: number[] = []; - const m1 = async (next: Function) => { - order.push(1); - await next(); - order.push(4); - }; - const m2 = async (next: Function) => { - order.push(2); - await next(); - order.push(3); - }; - await compose([m1, m2], true)([]); - expect(order).toEqual([1, 2, 3, 4]); - }); - - test('next 多次调用 reject', async () => { - const m = async (next: Function) => { - await next(); - await next(); - }; - await expect(compose([m], true)([])).rejects.toBeInstanceOf(Error); - }); - - test('middleware 抛同步错时 reject', async () => { - const m = () => { - throw new Error('boom'); - }; - await expect(compose([m as any], true)([])).rejects.toThrow('boom'); - }); -});