@slidejs/runner 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +19 -0
- package/.turbo/turbo-test.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +283 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +1 -0
- package/package.json +34 -0
- package/src/__tests__/runner.test.ts +623 -0
- package/src/errors.ts +21 -0
- package/src/index.ts +16 -0
- package/src/runner.ts +377 -0
- package/src/types.ts +164 -0
- package/tsconfig.json +15 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vite.config.ts +25 -0
- package/vitest.config.ts +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slidejs/runner",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"require": "./dist/index.cjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "vite build",
|
|
17
|
+
"dev": "vite build --watch",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@slidejs/core": "workspace:*",
|
|
24
|
+
"@slidejs/context": "workspace:*"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^20.10.0",
|
|
28
|
+
"jsdom": "^24.0.0",
|
|
29
|
+
"typescript": "^5.3.3",
|
|
30
|
+
"vite": "^5.0.0",
|
|
31
|
+
"vite-plugin-dts": "^3.7.0",
|
|
32
|
+
"vitest": "^1.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @slidejs/runner - SlideRunner 单元测试
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
6
|
+
import { SlideRunner } from '../runner';
|
|
7
|
+
import { SlideRunnerError } from '../errors';
|
|
8
|
+
import type { SlideAdapter, SlideRunnerPlugin } from '../types';
|
|
9
|
+
import type { SlideDSL, SlideDefinition } from '@slidejs/core';
|
|
10
|
+
import type { SlideContext } from '@slidejs/context';
|
|
11
|
+
|
|
12
|
+
// 创建模拟适配器
|
|
13
|
+
function createMockAdapter(): SlideAdapter {
|
|
14
|
+
const eventHandlers = new Map<string, Set<(data: unknown) => void>>();
|
|
15
|
+
let currentIndex = 0;
|
|
16
|
+
let totalSlides = 0;
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
name: 'mock-adapter',
|
|
20
|
+
async initialize(container: HTMLElement) {
|
|
21
|
+
// 模拟初始化
|
|
22
|
+
},
|
|
23
|
+
async render(slides: SlideDefinition[]) {
|
|
24
|
+
totalSlides = slides.length;
|
|
25
|
+
},
|
|
26
|
+
async destroy() {
|
|
27
|
+
eventHandlers.clear();
|
|
28
|
+
},
|
|
29
|
+
navigateTo(index: number) {
|
|
30
|
+
const fromIndex = currentIndex;
|
|
31
|
+
currentIndex = index;
|
|
32
|
+
// 同步触发事件,模拟真实适配器行为
|
|
33
|
+
const handlers = eventHandlers.get('slideChanged');
|
|
34
|
+
if (handlers) {
|
|
35
|
+
handlers.forEach((handler) => handler({ index, from: fromIndex, to: index }));
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
getCurrentIndex() {
|
|
39
|
+
return currentIndex;
|
|
40
|
+
},
|
|
41
|
+
getTotalSlides() {
|
|
42
|
+
return totalSlides;
|
|
43
|
+
},
|
|
44
|
+
on(event: string, handler: (data: unknown) => void) {
|
|
45
|
+
if (!eventHandlers.has(event)) {
|
|
46
|
+
eventHandlers.set(event, new Set());
|
|
47
|
+
}
|
|
48
|
+
eventHandlers.get(event)!.add(handler);
|
|
49
|
+
},
|
|
50
|
+
off(event: string, handler: (data: unknown) => void) {
|
|
51
|
+
eventHandlers.get(event)?.delete(handler);
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 创建测试用的 SlideContext
|
|
57
|
+
function createTestContext(): SlideContext {
|
|
58
|
+
return {
|
|
59
|
+
sourceType: 'test',
|
|
60
|
+
sourceId: 'test-id',
|
|
61
|
+
metadata: {
|
|
62
|
+
title: 'Test Presentation',
|
|
63
|
+
},
|
|
64
|
+
items: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 创建测试用的 SlideDSL
|
|
69
|
+
function createTestDSL(): SlideDSL {
|
|
70
|
+
return {
|
|
71
|
+
version: '1.0.0',
|
|
72
|
+
sourceType: 'test',
|
|
73
|
+
sourceId: 'test-id',
|
|
74
|
+
rules: [
|
|
75
|
+
{
|
|
76
|
+
type: 'start',
|
|
77
|
+
name: 'intro',
|
|
78
|
+
generate: () => [
|
|
79
|
+
{
|
|
80
|
+
id: 'slide-1',
|
|
81
|
+
content: {
|
|
82
|
+
type: 'text',
|
|
83
|
+
lines: ['Welcome'],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
type: 'content',
|
|
90
|
+
name: 'content',
|
|
91
|
+
generate: () => [
|
|
92
|
+
{
|
|
93
|
+
id: 'slide-2',
|
|
94
|
+
content: {
|
|
95
|
+
type: 'text',
|
|
96
|
+
lines: ['Content'],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: 'end',
|
|
103
|
+
name: 'end',
|
|
104
|
+
generate: () => [
|
|
105
|
+
{
|
|
106
|
+
id: 'slide-3',
|
|
107
|
+
content: {
|
|
108
|
+
type: 'text',
|
|
109
|
+
lines: ['End'],
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
describe('SlideRunner', () => {
|
|
119
|
+
let container: HTMLElement;
|
|
120
|
+
let adapter: SlideAdapter;
|
|
121
|
+
let context: SlideContext;
|
|
122
|
+
let dsl: SlideDSL;
|
|
123
|
+
|
|
124
|
+
beforeEach(() => {
|
|
125
|
+
// 创建 DOM 容器
|
|
126
|
+
container = document.createElement('div');
|
|
127
|
+
document.body.appendChild(container);
|
|
128
|
+
|
|
129
|
+
// 创建模拟适配器
|
|
130
|
+
adapter = createMockAdapter();
|
|
131
|
+
context = createTestContext();
|
|
132
|
+
dsl = createTestDSL();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('构造函数', () => {
|
|
136
|
+
it('应该成功创建 SlideRunner 实例', () => {
|
|
137
|
+
const runner = new SlideRunner({
|
|
138
|
+
container,
|
|
139
|
+
adapter,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(runner).toBeInstanceOf(SlideRunner);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('应该接受字符串选择器作为容器', () => {
|
|
146
|
+
container.id = 'test-container';
|
|
147
|
+
const runner = new SlideRunner({
|
|
148
|
+
container: '#test-container',
|
|
149
|
+
adapter,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
expect(runner).toBeInstanceOf(SlideRunner);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('应该在没有适配器时抛出错误', () => {
|
|
156
|
+
expect(() => {
|
|
157
|
+
new SlideRunner({
|
|
158
|
+
container,
|
|
159
|
+
adapter: null as unknown as SlideAdapter,
|
|
160
|
+
});
|
|
161
|
+
}).toThrow(SlideRunnerError);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('应该在容器不存在时抛出错误', () => {
|
|
165
|
+
expect(() => {
|
|
166
|
+
new SlideRunner({
|
|
167
|
+
container: '#non-existent',
|
|
168
|
+
adapter,
|
|
169
|
+
});
|
|
170
|
+
}).toThrow(SlideRunnerError);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('应该接受插件数组', () => {
|
|
174
|
+
const plugin: SlideRunnerPlugin = {
|
|
175
|
+
name: 'test-plugin',
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const runner = new SlideRunner({
|
|
179
|
+
container,
|
|
180
|
+
adapter,
|
|
181
|
+
plugins: [plugin],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(runner).toBeInstanceOf(SlideRunner);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('run()', () => {
|
|
189
|
+
it('应该成功运行幻灯片演示', async () => {
|
|
190
|
+
const runner = new SlideRunner({
|
|
191
|
+
container,
|
|
192
|
+
adapter,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await runner.run(dsl, context);
|
|
196
|
+
|
|
197
|
+
expect(runner.getTotalSlides()).toBe(3);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('应该执行 beforeRender 插件钩子', async () => {
|
|
201
|
+
const beforeRender = vi.fn();
|
|
202
|
+
const plugin: SlideRunnerPlugin = {
|
|
203
|
+
name: 'test-plugin',
|
|
204
|
+
beforeRender,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const runner = new SlideRunner({
|
|
208
|
+
container,
|
|
209
|
+
adapter,
|
|
210
|
+
plugins: [plugin],
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
await runner.run(dsl, context);
|
|
214
|
+
|
|
215
|
+
expect(beforeRender).toHaveBeenCalledTimes(1);
|
|
216
|
+
expect(beforeRender).toHaveBeenCalledWith(expect.arrayContaining([
|
|
217
|
+
expect.objectContaining({ id: 'slide-1' }),
|
|
218
|
+
]));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('应该执行 afterRender 插件钩子', async () => {
|
|
222
|
+
const afterRender = vi.fn();
|
|
223
|
+
const plugin: SlideRunnerPlugin = {
|
|
224
|
+
name: 'test-plugin',
|
|
225
|
+
afterRender,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const runner = new SlideRunner({
|
|
229
|
+
container,
|
|
230
|
+
adapter,
|
|
231
|
+
plugins: [plugin],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await runner.run(dsl, context);
|
|
235
|
+
|
|
236
|
+
expect(afterRender).toHaveBeenCalledTimes(1);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('应该在适配器初始化失败时抛出错误', async () => {
|
|
240
|
+
const failingAdapter = createMockAdapter();
|
|
241
|
+
failingAdapter.initialize = vi.fn().mockRejectedValue(new Error('Init failed'));
|
|
242
|
+
|
|
243
|
+
const runner = new SlideRunner({
|
|
244
|
+
container,
|
|
245
|
+
adapter: failingAdapter,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await expect(runner.run(dsl, context)).rejects.toThrow(SlideRunnerError);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
describe('renderSlides()', () => {
|
|
253
|
+
it('应该直接渲染提供的幻灯片', async () => {
|
|
254
|
+
const slides: SlideDefinition[] = [
|
|
255
|
+
{
|
|
256
|
+
id: 'slide-1',
|
|
257
|
+
content: {
|
|
258
|
+
type: 'text',
|
|
259
|
+
lines: ['Slide 1'],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: 'slide-2',
|
|
264
|
+
content: {
|
|
265
|
+
type: 'text',
|
|
266
|
+
lines: ['Slide 2'],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
const runner = new SlideRunner({
|
|
272
|
+
container,
|
|
273
|
+
adapter,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
await runner.renderSlides(slides);
|
|
277
|
+
|
|
278
|
+
expect(runner.getTotalSlides()).toBe(2);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('应该执行 beforeRender 插件钩子', async () => {
|
|
282
|
+
const beforeRender = vi.fn();
|
|
283
|
+
const plugin: SlideRunnerPlugin = {
|
|
284
|
+
name: 'test-plugin',
|
|
285
|
+
beforeRender,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const slides: SlideDefinition[] = [
|
|
289
|
+
{
|
|
290
|
+
id: 'slide-1',
|
|
291
|
+
content: { type: 'text', lines: ['Test'] },
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
|
|
295
|
+
const runner = new SlideRunner({
|
|
296
|
+
container,
|
|
297
|
+
adapter,
|
|
298
|
+
plugins: [plugin],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
await runner.renderSlides(slides);
|
|
302
|
+
|
|
303
|
+
expect(beforeRender).toHaveBeenCalledTimes(1);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('play()', () => {
|
|
308
|
+
it('应该成功播放演示', async () => {
|
|
309
|
+
const runner = new SlideRunner({
|
|
310
|
+
container,
|
|
311
|
+
adapter,
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
await runner.run(dsl, context);
|
|
315
|
+
runner.play();
|
|
316
|
+
|
|
317
|
+
expect(runner.getCurrentIndex()).toBe(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('应该在未初始化时抛出错误', () => {
|
|
321
|
+
const runner = new SlideRunner({
|
|
322
|
+
container,
|
|
323
|
+
adapter,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
expect(() => runner.play()).toThrow(SlideRunnerError);
|
|
327
|
+
expect(() => runner.play()).toThrow('No slides to play');
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('应该在无幻灯片时抛出错误', async () => {
|
|
331
|
+
// 创建一个会生成空幻灯片的 DSL(但至少有一个 start 规则)
|
|
332
|
+
const emptyDSL: SlideDSL = {
|
|
333
|
+
version: '1.0.0',
|
|
334
|
+
sourceType: 'test',
|
|
335
|
+
sourceId: 'test-id',
|
|
336
|
+
rules: [
|
|
337
|
+
{
|
|
338
|
+
type: 'start',
|
|
339
|
+
name: 'empty',
|
|
340
|
+
generate: () => [], // 返回空数组
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
type: 'end',
|
|
344
|
+
name: 'empty-end',
|
|
345
|
+
generate: () => [],
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const runner = new SlideRunner({
|
|
351
|
+
container,
|
|
352
|
+
adapter,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
await runner.run(emptyDSL, context);
|
|
356
|
+
|
|
357
|
+
expect(() => runner.play()).toThrow(SlideRunnerError);
|
|
358
|
+
expect(() => runner.play()).toThrow('No slides');
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe('navigateTo()', () => {
|
|
363
|
+
it('应该成功导航到指定幻灯片', async () => {
|
|
364
|
+
const runner = new SlideRunner({
|
|
365
|
+
container,
|
|
366
|
+
adapter,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await runner.run(dsl, context);
|
|
370
|
+
runner.navigateTo(1);
|
|
371
|
+
|
|
372
|
+
// 等待异步钩子执行
|
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
374
|
+
|
|
375
|
+
expect(runner.getCurrentIndex()).toBe(1);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it('应该在索引超出范围时抛出错误', async () => {
|
|
379
|
+
const runner = new SlideRunner({
|
|
380
|
+
container,
|
|
381
|
+
adapter,
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await runner.run(dsl, context);
|
|
385
|
+
|
|
386
|
+
expect(() => runner.navigateTo(10)).toThrow(SlideRunnerError);
|
|
387
|
+
expect(() => runner.navigateTo(-1)).toThrow(SlideRunnerError);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('应该执行 beforeSlideChange 插件钩子', async () => {
|
|
391
|
+
const beforeSlideChange = vi.fn();
|
|
392
|
+
const plugin: SlideRunnerPlugin = {
|
|
393
|
+
name: 'test-plugin',
|
|
394
|
+
beforeSlideChange,
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const runner = new SlideRunner({
|
|
398
|
+
container,
|
|
399
|
+
adapter,
|
|
400
|
+
plugins: [plugin],
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
await runner.run(dsl, context);
|
|
404
|
+
runner.navigateTo(1);
|
|
405
|
+
|
|
406
|
+
// 等待异步钩子执行
|
|
407
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
408
|
+
|
|
409
|
+
expect(beforeSlideChange).toHaveBeenCalledWith(0, 1);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it('应该执行 afterSlideChange 插件钩子', async () => {
|
|
413
|
+
const afterSlideChange = vi.fn();
|
|
414
|
+
const plugin: SlideRunnerPlugin = {
|
|
415
|
+
name: 'test-plugin',
|
|
416
|
+
afterSlideChange,
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const runner = new SlideRunner({
|
|
420
|
+
container,
|
|
421
|
+
adapter,
|
|
422
|
+
plugins: [plugin],
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await runner.run(dsl, context);
|
|
426
|
+
runner.navigateTo(1);
|
|
427
|
+
|
|
428
|
+
// 等待异步钩子执行
|
|
429
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
430
|
+
|
|
431
|
+
expect(afterSlideChange).toHaveBeenCalledWith(0, 1);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('getCurrentIndex()', () => {
|
|
436
|
+
it('应该返回当前幻灯片索引', async () => {
|
|
437
|
+
const runner = new SlideRunner({
|
|
438
|
+
container,
|
|
439
|
+
adapter,
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await runner.run(dsl, context);
|
|
443
|
+
runner.navigateTo(2);
|
|
444
|
+
|
|
445
|
+
// 等待异步钩子执行
|
|
446
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
447
|
+
|
|
448
|
+
expect(runner.getCurrentIndex()).toBe(2);
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('getTotalSlides()', () => {
|
|
453
|
+
it('应该返回幻灯片总数', async () => {
|
|
454
|
+
const runner = new SlideRunner({
|
|
455
|
+
container,
|
|
456
|
+
adapter,
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await runner.run(dsl, context);
|
|
460
|
+
|
|
461
|
+
expect(runner.getTotalSlides()).toBe(3);
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
describe('updateSlide()', () => {
|
|
466
|
+
it('应该在适配器支持时更新幻灯片', async () => {
|
|
467
|
+
const adapterWithUpdate = createMockAdapter();
|
|
468
|
+
adapterWithUpdate.updateSlide = vi.fn().mockResolvedValue(undefined);
|
|
469
|
+
|
|
470
|
+
const runner = new SlideRunner({
|
|
471
|
+
container,
|
|
472
|
+
adapter: adapterWithUpdate,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
await runner.run(dsl, context);
|
|
476
|
+
|
|
477
|
+
const newSlide: SlideDefinition = {
|
|
478
|
+
id: 'slide-1-updated',
|
|
479
|
+
content: {
|
|
480
|
+
type: 'text',
|
|
481
|
+
lines: ['Updated'],
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
await runner.updateSlide(0, newSlide);
|
|
486
|
+
|
|
487
|
+
expect(adapterWithUpdate.updateSlide).toHaveBeenCalledWith(0, newSlide);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it('应该在适配器不支持时抛出错误', async () => {
|
|
491
|
+
const runner = new SlideRunner({
|
|
492
|
+
container,
|
|
493
|
+
adapter,
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
await runner.run(dsl, context);
|
|
497
|
+
|
|
498
|
+
const newSlide: SlideDefinition = {
|
|
499
|
+
id: 'slide-1-updated',
|
|
500
|
+
content: {
|
|
501
|
+
type: 'text',
|
|
502
|
+
lines: ['Updated'],
|
|
503
|
+
},
|
|
504
|
+
};
|
|
505
|
+
|
|
506
|
+
await expect(runner.updateSlide(0, newSlide)).rejects.toThrow(SlideRunnerError);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('应该在索引无效时抛出错误', async () => {
|
|
510
|
+
const adapterWithUpdate = createMockAdapter();
|
|
511
|
+
adapterWithUpdate.updateSlide = vi.fn().mockResolvedValue(undefined);
|
|
512
|
+
|
|
513
|
+
const runner = new SlideRunner({
|
|
514
|
+
container,
|
|
515
|
+
adapter: adapterWithUpdate,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
await runner.run(dsl, context);
|
|
519
|
+
|
|
520
|
+
const newSlide: SlideDefinition = {
|
|
521
|
+
id: 'slide-invalid',
|
|
522
|
+
content: {
|
|
523
|
+
type: 'text',
|
|
524
|
+
lines: ['Invalid'],
|
|
525
|
+
},
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
await expect(runner.updateSlide(10, newSlide)).rejects.toThrow(SlideRunnerError);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe('refresh()', () => {
|
|
533
|
+
it('应该重新渲染所有幻灯片', async () => {
|
|
534
|
+
const renderSpy = vi.spyOn(adapter, 'render');
|
|
535
|
+
|
|
536
|
+
const runner = new SlideRunner({
|
|
537
|
+
container,
|
|
538
|
+
adapter,
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
await runner.run(dsl, context);
|
|
542
|
+
await runner.refresh();
|
|
543
|
+
|
|
544
|
+
expect(renderSpy).toHaveBeenCalledTimes(2); // run() 一次,refresh() 一次
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe('destroy()', () => {
|
|
549
|
+
it('应该成功销毁 Runner', async () => {
|
|
550
|
+
const destroySpy = vi.spyOn(adapter, 'destroy');
|
|
551
|
+
|
|
552
|
+
const runner = new SlideRunner({
|
|
553
|
+
container,
|
|
554
|
+
adapter,
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
await runner.run(dsl, context);
|
|
558
|
+
await runner.destroy();
|
|
559
|
+
|
|
560
|
+
expect(destroySpy).toHaveBeenCalledTimes(1);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe('事件处理', () => {
|
|
565
|
+
it('应该注册事件监听器', () => {
|
|
566
|
+
const handler = vi.fn();
|
|
567
|
+
const runner = new SlideRunner({
|
|
568
|
+
container,
|
|
569
|
+
adapter,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
runner.on('slideChanged', handler);
|
|
573
|
+
|
|
574
|
+
// 触发适配器事件
|
|
575
|
+
adapter.navigateTo(1);
|
|
576
|
+
|
|
577
|
+
expect(handler).toHaveBeenCalled();
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('应该移除事件监听器', () => {
|
|
581
|
+
const handler = vi.fn();
|
|
582
|
+
const runner = new SlideRunner({
|
|
583
|
+
container,
|
|
584
|
+
adapter,
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
runner.on('slideChanged', handler);
|
|
588
|
+
runner.off('slideChanged', handler);
|
|
589
|
+
|
|
590
|
+
// 触发适配器事件
|
|
591
|
+
adapter.navigateTo(1);
|
|
592
|
+
|
|
593
|
+
expect(handler).not.toHaveBeenCalled();
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
describe('错误处理', () => {
|
|
598
|
+
it('应该正确处理插件钩子错误', async () => {
|
|
599
|
+
const plugin: SlideRunnerPlugin = {
|
|
600
|
+
name: 'error-plugin',
|
|
601
|
+
beforeRender: () => {
|
|
602
|
+
throw new Error('Plugin error');
|
|
603
|
+
},
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
607
|
+
|
|
608
|
+
const runner = new SlideRunner({
|
|
609
|
+
container,
|
|
610
|
+
adapter,
|
|
611
|
+
plugins: [plugin],
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
// 不应该抛出错误,应该继续执行
|
|
615
|
+
await expect(runner.run(dsl, context)).resolves.not.toThrow();
|
|
616
|
+
|
|
617
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
618
|
+
|
|
619
|
+
consoleErrorSpy.mockRestore();
|
|
620
|
+
});
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @slidejs/runner - 错误处理
|
|
3
|
+
*
|
|
4
|
+
* 定义 SlideRunner 相关的错误类
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SlideRunner 错误类
|
|
9
|
+
*/
|
|
10
|
+
export class SlideRunnerError extends Error {
|
|
11
|
+
/**
|
|
12
|
+
* @param message - 错误消息
|
|
13
|
+
* @param code - 错误代码(可选)
|
|
14
|
+
*/
|
|
15
|
+
constructor(message: string, public code?: string) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'SlideRunnerError';
|
|
18
|
+
// 保持正确的原型链
|
|
19
|
+
Object.setPrototypeOf(this, SlideRunnerError.prototype);
|
|
20
|
+
}
|
|
21
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @slidejs/runner - 入口文件
|
|
3
|
+
*
|
|
4
|
+
* 导出所有公共 API
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { SlideRunner } from './runner';
|
|
8
|
+
export { SlideRunnerError } from './errors';
|
|
9
|
+
export type {
|
|
10
|
+
SlideAdapter,
|
|
11
|
+
AdapterEvent,
|
|
12
|
+
EventHandler,
|
|
13
|
+
AdapterOptions,
|
|
14
|
+
SlideRunnerPlugin,
|
|
15
|
+
SlideRunnerConfig,
|
|
16
|
+
} from './types';
|