diff --git a/.changeset/calm-events-listen.md b/.changeset/calm-events-listen.md new file mode 100644 index 000000000..ae84855e8 --- /dev/null +++ b/.changeset/calm-events-listen.md @@ -0,0 +1,5 @@ +--- +'@plait/core': patch +--- + +add a framework-independent board event observer for plugin tests diff --git a/docs/guides/solutions/unit-test.md b/docs/guides/solutions/unit-test.md index d1d09cf3a..3f3f43e50 100644 --- a/docs/guides/solutions/unit-test.md +++ b/docs/guides/solutions/unit-test.md @@ -44,6 +44,29 @@ afterEach(() => { }); ``` +#### 验证插件是否向下游传递事件 + +当测试需要确认一个插件没有吞掉事件,而是把事件继续交给下游处理器时,可以使用 `createBoardEventObserver`: + +```ts +import { createBoardEventObserver, createPointerEvent, setupTestingBoard } from '@plait/core'; + +const observer = createBoardEventObserver(['pointerDown']); +const fixture = setupTestingBoard([observer.plugin, pluginUnderTest], []); +const event = createPointerEvent('pointerdown'); + +fixture.board.pointerDown(event); + +expect(observer.calls.pointerDown).toEqual([event]); + +observer.clear(); +fixture.destroy(); +``` + +插件顺序需要保持为 `[observer.plugin, pluginUnderTest]`。插件从左到右应用,因此待测插件会包裹观察器;只有待测插件确实调用了下游处理器,事件才会出现在 `observer.calls` 中。 + +`createBoardEventObserver` 只负责记录指定事件并继续调用原处理器,不依赖 Jasmine、Vitest 或 Jest。`clear()` 只清空调用记录,Board 相关测试资源仍应通过 `fixture.destroy()` 清理。 + 另外分类 3 在写测试的时候需要注意,如果一个测试用例里面包含超过一次的数据修改,那么在每一次的数据修改后都需要重新调用 fakeNodeWeakmap 构建基于最新数据引用的父子关系依赖: ``` @@ -73,4 +96,3 @@ it('should replace emoji success', () => { 不得不说写单元测试是一件非常难的事情,而把单元测试写的有条理则更难,因为它首先需要你的代码是有条理的(模块划分是否清楚、调用关系/目录关系是否统一、函数职责是否唯一/或者存在重复),如果代码结构、调用关系、职责划分等等不清晰,你的单元测试则会很混乱(无法按照一个统一的思路把所有的分支逻辑全部覆盖),或者出现重复功能的单元测试,必然会造成时间的浪费,也会消磨维护单元测试的信心。 - diff --git a/packages/core/src/testing/create-board-event-observer.ts b/packages/core/src/testing/create-board-event-observer.ts new file mode 100644 index 000000000..04f1cafae --- /dev/null +++ b/packages/core/src/testing/create-board-event-observer.ts @@ -0,0 +1,47 @@ +import { PlaitPlugin } from '../interfaces'; + +export type BoardEventName = 'pointerDown' | 'pointerMove' | 'pointerUp' | 'globalPointerUp' | 'touchStart'; + +export type BoardEventCalls = Partial>; + +/** + * Creates a plugin that records events delegated by plugins applied after it. + * + * Place the observer before the plugin under test in the plugin list so that it + * observes events delegated downstream: + * `[observer.plugin, pluginUnderTest]`. + */ +export const createBoardEventObserver = (eventNames: BoardEventName[]) => { + const calls: BoardEventCalls = {}; + + eventNames.forEach((eventName) => { + calls[eventName] = []; + }); + + const plugin: PlaitPlugin = (board) => { + eventNames.forEach((eventName) => { + const downstream = board[eventName] as (event: Event) => void; + + Object.assign(board, { + [eventName]: (event: Event) => { + calls[eventName]!.push(event); + downstream(event); + } + }); + }); + + return board; + }; + + const clear = () => { + eventNames.forEach((eventName) => { + calls[eventName]!.length = 0; + }); + }; + + return { + plugin, + calls, + clear + }; +}; diff --git a/packages/core/src/testing/index.ts b/packages/core/src/testing/index.ts index 412389f2f..f93fa4cb2 100644 --- a/packages/core/src/testing/index.ts +++ b/packages/core/src/testing/index.ts @@ -1,2 +1,3 @@ export * from './fixture'; export * from './mock-events'; +export * from './create-board-event-observer';