@xiboplayer/core 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.
@@ -0,0 +1,516 @@
1
+ /**
2
+ * XMDS Client Tests
3
+ *
4
+ * Tests for XMDS campaign parsing
5
+ */
6
+
7
+ import { describe, it, expect } from 'vitest';
8
+ import { parseScheduleResponse } from '../../xmds/src/schedule-parser.js';
9
+
10
+ describe('Schedule Parsing', () => {
11
+ describe('Campaign Parsing', () => {
12
+ it('should parse schedule with campaigns', () => {
13
+ const xml = `
14
+ <schedule>
15
+ <default file="0"/>
16
+ <campaign id="5" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="15">
17
+ <layout file="100"/>
18
+ <layout file="101"/>
19
+ <layout file="102"/>
20
+ </campaign>
21
+ <layout file="200" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="20"/>
22
+ <campaign id="6" priority="8" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="25">
23
+ <layout file="300"/>
24
+ <layout file="301"/>
25
+ </campaign>
26
+ </schedule>
27
+ `;
28
+
29
+ const schedule = parseScheduleResponse(xml);
30
+
31
+ // Check default
32
+ expect(schedule.default).toBe('0');
33
+
34
+ // Check campaigns
35
+ expect(schedule.campaigns).toHaveLength(2);
36
+
37
+ const campaign1 = schedule.campaigns[0];
38
+ expect(campaign1.id).toBe('5');
39
+ expect(campaign1.priority).toBe(10);
40
+ expect(campaign1.layouts).toHaveLength(3);
41
+ expect(campaign1.layouts[0].file).toBe('100');
42
+ expect(campaign1.layouts[1].file).toBe('101');
43
+ expect(campaign1.layouts[2].file).toBe('102');
44
+ expect(campaign1.layouts[0].priority).toBe(10);
45
+ expect(campaign1.layouts[0].campaignId).toBe('5');
46
+
47
+ const campaign2 = schedule.campaigns[1];
48
+ expect(campaign2.id).toBe('6');
49
+ expect(campaign2.priority).toBe(8);
50
+ expect(campaign2.layouts).toHaveLength(2);
51
+
52
+ // Check standalone layouts
53
+ expect(schedule.layouts).toHaveLength(1);
54
+ expect(schedule.layouts[0].file).toBe('200');
55
+ expect(schedule.layouts[0].priority).toBe(5);
56
+ expect(schedule.layouts[0].campaignId).toBeNull();
57
+ });
58
+
59
+ it('should parse schedule with only standalone layouts (backward compatible)', () => {
60
+ const xml = `
61
+ <schedule>
62
+ <default file="0"/>
63
+ <layout file="100" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="10"/>
64
+ <layout file="101" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="11"/>
65
+ </schedule>
66
+ `;
67
+
68
+ const schedule = parseScheduleResponse(xml);
69
+
70
+ expect(schedule.default).toBe('0');
71
+ expect(schedule.campaigns).toHaveLength(0);
72
+ expect(schedule.layouts).toHaveLength(2);
73
+ expect(schedule.layouts[0].file).toBe('100');
74
+ expect(schedule.layouts[0].priority).toBe(10);
75
+ expect(schedule.layouts[1].file).toBe('101');
76
+ expect(schedule.layouts[1].priority).toBe(5);
77
+ });
78
+
79
+ it('should parse empty schedule', () => {
80
+ const xml = `
81
+ <schedule>
82
+ <default file="999"/>
83
+ </schedule>
84
+ `;
85
+
86
+ const schedule = parseScheduleResponse(xml);
87
+
88
+ expect(schedule.default).toBe('999');
89
+ expect(schedule.campaigns).toHaveLength(0);
90
+ expect(schedule.layouts).toHaveLength(0);
91
+ });
92
+ });
93
+
94
+ describe('Campaign Layout Timing Inheritance', () => {
95
+ it('should allow layouts to inherit timing from campaign', () => {
96
+ const xml = `
97
+ <schedule>
98
+ <default file="0"/>
99
+ <campaign id="1" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="5">
100
+ <layout file="100"/>
101
+ <layout file="101" fromdt="2026-01-30 12:00:00" todt="2026-01-30 18:00:00"/>
102
+ </campaign>
103
+ </schedule>
104
+ `;
105
+
106
+ const schedule = parseScheduleResponse(xml);
107
+
108
+ const campaign = schedule.campaigns[0];
109
+
110
+ // First layout inherits campaign timing
111
+ expect(campaign.layouts[0].fromdt).toBe('2026-01-30 00:00:00');
112
+ expect(campaign.layouts[0].todt).toBe('2026-01-31 23:59:59');
113
+
114
+ // Second layout has its own timing
115
+ expect(campaign.layouts[1].fromdt).toBe('2026-01-30 12:00:00');
116
+ expect(campaign.layouts[1].todt).toBe('2026-01-30 18:00:00');
117
+ });
118
+
119
+ it('should allow layouts to inherit priority from campaign', () => {
120
+ const xml = `
121
+ <schedule>
122
+ <default file="0"/>
123
+ <campaign id="1" priority="15" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59">
124
+ <layout file="100"/>
125
+ <layout file="101"/>
126
+ </campaign>
127
+ </schedule>
128
+ `;
129
+
130
+ const schedule = parseScheduleResponse(xml);
131
+
132
+ const campaign = schedule.campaigns[0];
133
+
134
+ expect(campaign.layouts[0].priority).toBe(15);
135
+ expect(campaign.layouts[1].priority).toBe(15);
136
+ });
137
+
138
+ it('should associate layouts with their campaign ID', () => {
139
+ const xml = `
140
+ <schedule>
141
+ <default file="0"/>
142
+ <campaign id="42" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59">
143
+ <layout file="100"/>
144
+ <layout file="101"/>
145
+ </campaign>
146
+ <layout file="200" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59"/>
147
+ </schedule>
148
+ `;
149
+
150
+ const schedule = parseScheduleResponse(xml);
151
+
152
+ // Campaign layouts should have campaignId
153
+ expect(schedule.campaigns[0].layouts[0].campaignId).toBe('42');
154
+ expect(schedule.campaigns[0].layouts[1].campaignId).toBe('42');
155
+
156
+ // Standalone layout should not have campaignId
157
+ expect(schedule.layouts[0].campaignId).toBeNull();
158
+ });
159
+ });
160
+
161
+ describe('Schedule ID Parsing', () => {
162
+ it('should parse scheduleid attribute', () => {
163
+ const xml = `
164
+ <schedule>
165
+ <default file="0"/>
166
+ <layout file="100" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="123"/>
167
+ <campaign id="1" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="456">
168
+ <layout file="200"/>
169
+ </campaign>
170
+ </schedule>
171
+ `;
172
+
173
+ const schedule = parseScheduleResponse(xml);
174
+
175
+ expect(schedule.layouts[0].scheduleid).toBe('123');
176
+ expect(schedule.campaigns[0].scheduleid).toBe('456');
177
+ });
178
+ });
179
+
180
+ describe('Action Parsing', () => {
181
+ it('should parse action elements from schedule', () => {
182
+ const xml = `
183
+ <schedule>
184
+ <default file="0"/>
185
+ <actions>
186
+ <action actionType="navLayout" triggerCode="tc1" layoutCode="42" fromdt="2026-01-01 00:00:00" todt="2030-12-31 23:59:59" priority="5" scheduleid="10"/>
187
+ </actions>
188
+ </schedule>
189
+ `;
190
+
191
+ const schedule = parseScheduleResponse(xml);
192
+
193
+ expect(schedule.actions).toHaveLength(1);
194
+ expect(schedule.actions[0].actionType).toBe('navLayout');
195
+ expect(schedule.actions[0].triggerCode).toBe('tc1');
196
+ expect(schedule.actions[0].layoutCode).toBe('42');
197
+ expect(schedule.actions[0].fromDt).toBe('2026-01-01 00:00:00');
198
+ expect(schedule.actions[0].toDt).toBe('2030-12-31 23:59:59');
199
+ expect(schedule.actions[0].priority).toBe(5);
200
+ expect(schedule.actions[0].scheduleId).toBe('10');
201
+ });
202
+
203
+ it('should parse multiple actions', () => {
204
+ const xml = `
205
+ <schedule>
206
+ <default file="0"/>
207
+ <actions>
208
+ <action actionType="navLayout" triggerCode="tc1" layoutCode="42" fromdt="2026-01-01 00:00:00" todt="2030-12-31 23:59:59" priority="1" scheduleid="1"/>
209
+ <action actionType="command" triggerCode="tc2" commandCode="restart" fromdt="2026-02-01 00:00:00" todt="2026-12-31 23:59:59" priority="10" scheduleid="2"/>
210
+ <action actionType="navigateToWidget" triggerCode="tc3" layoutCode="99" fromdt="2026-01-01 00:00:00" todt="2027-01-01 00:00:00" priority="3" scheduleid="3"/>
211
+ </actions>
212
+ </schedule>
213
+ `;
214
+
215
+ const schedule = parseScheduleResponse(xml);
216
+
217
+ expect(schedule.actions).toHaveLength(3);
218
+ expect(schedule.actions[0].triggerCode).toBe('tc1');
219
+ expect(schedule.actions[1].triggerCode).toBe('tc2');
220
+ expect(schedule.actions[1].commandCode).toBe('restart');
221
+ expect(schedule.actions[2].triggerCode).toBe('tc3');
222
+ });
223
+
224
+ it('should parse action with geoLocation attributes', () => {
225
+ const xml = `
226
+ <schedule>
227
+ <default file="0"/>
228
+ <actions>
229
+ <action actionType="navLayout" triggerCode="geo1" layoutCode="50" fromdt="2026-01-01 00:00:00" todt="2030-12-31 23:59:59" isGeoAware="1" geoLocation="41.3851,2.1734" scheduleid="5"/>
230
+ </actions>
231
+ </schedule>
232
+ `;
233
+
234
+ const schedule = parseScheduleResponse(xml);
235
+
236
+ expect(schedule.actions[0].isGeoAware).toBe(true);
237
+ expect(schedule.actions[0].geoLocation).toBe('41.3851,2.1734');
238
+ });
239
+
240
+ it('should initialize actions array as empty when no actions element', () => {
241
+ const xml = `
242
+ <schedule>
243
+ <default file="0"/>
244
+ <layout file="100" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1"/>
245
+ </schedule>
246
+ `;
247
+
248
+ const schedule = parseScheduleResponse(xml);
249
+
250
+ expect(schedule.actions).toEqual([]);
251
+ });
252
+ });
253
+
254
+ describe('Command Parsing', () => {
255
+ it('should parse command elements from schedule', () => {
256
+ const xml = `
257
+ <schedule>
258
+ <default file="0"/>
259
+ <command command="collectNow" date="2026-01-01"/>
260
+ </schedule>
261
+ `;
262
+
263
+ const schedule = parseScheduleResponse(xml);
264
+
265
+ expect(schedule.commands).toHaveLength(1);
266
+ expect(schedule.commands[0].code).toBe('collectNow');
267
+ expect(schedule.commands[0].date).toBe('2026-01-01');
268
+ });
269
+
270
+ it('should parse multiple commands', () => {
271
+ const xml = `
272
+ <schedule>
273
+ <default file="0"/>
274
+ <command command="collectNow" date="2026-02-11"/>
275
+ <command command="reboot" date="2026-02-12"/>
276
+ </schedule>
277
+ `;
278
+
279
+ const schedule = parseScheduleResponse(xml);
280
+
281
+ expect(schedule.commands).toHaveLength(2);
282
+ expect(schedule.commands[0].code).toBe('collectNow');
283
+ expect(schedule.commands[0].date).toBe('2026-02-11');
284
+ expect(schedule.commands[1].code).toBe('reboot');
285
+ expect(schedule.commands[1].date).toBe('2026-02-12');
286
+ });
287
+
288
+ it('should initialize commands array as empty when no command elements', () => {
289
+ const xml = `
290
+ <schedule>
291
+ <default file="0"/>
292
+ </schedule>
293
+ `;
294
+
295
+ const schedule = parseScheduleResponse(xml);
296
+
297
+ expect(schedule.commands).toEqual([]);
298
+ });
299
+ });
300
+
301
+ describe('Criteria Parsing', () => {
302
+ it('should parse criteria from standalone layout', () => {
303
+ const xml = `
304
+ <schedule>
305
+ <default file="0"/>
306
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1">
307
+ <criteria metric="dayOfWeek" condition="equals" type="string">Monday</criteria>
308
+ <criteria metric="temperature" condition="greaterThan" type="number">25</criteria>
309
+ </layout>
310
+ </schedule>
311
+ `;
312
+
313
+ const schedule = parseScheduleResponse(xml);
314
+
315
+ expect(schedule.layouts).toHaveLength(1);
316
+ expect(schedule.layouts[0].criteria).toHaveLength(2);
317
+ expect(schedule.layouts[0].criteria[0]).toEqual({
318
+ metric: 'dayOfWeek',
319
+ condition: 'equals',
320
+ type: 'string',
321
+ value: 'Monday'
322
+ });
323
+ expect(schedule.layouts[0].criteria[1]).toEqual({
324
+ metric: 'temperature',
325
+ condition: 'greaterThan',
326
+ type: 'number',
327
+ value: '25'
328
+ });
329
+ });
330
+
331
+ it('should parse criteria from campaign layout', () => {
332
+ const xml = `
333
+ <schedule>
334
+ <default file="0"/>
335
+ <campaign id="1" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="2">
336
+ <layout file="200">
337
+ <criteria metric="displayProperty" condition="contains" type="string">building-A</criteria>
338
+ </layout>
339
+ </campaign>
340
+ </schedule>
341
+ `;
342
+
343
+ const schedule = parseScheduleResponse(xml);
344
+
345
+ expect(schedule.campaigns[0].layouts[0].criteria).toHaveLength(1);
346
+ expect(schedule.campaigns[0].layouts[0].criteria[0].metric).toBe('displayProperty');
347
+ expect(schedule.campaigns[0].layouts[0].criteria[0].value).toBe('building-A');
348
+ });
349
+
350
+ it('should parse criteria from overlay', () => {
351
+ const xml = `
352
+ <schedule>
353
+ <default file="0"/>
354
+ <overlays>
355
+ <overlay file="300" duration="30" fromdt="2026-01-01 00:00:00" todt="2026-12-31 23:59:59" priority="5" scheduleid="3">
356
+ <criteria metric="timeOfDay" condition="between" type="string">09:00-17:00</criteria>
357
+ </overlay>
358
+ </overlays>
359
+ </schedule>
360
+ `;
361
+
362
+ const schedule = parseScheduleResponse(xml);
363
+
364
+ expect(schedule.overlays[0].criteria).toHaveLength(1);
365
+ expect(schedule.overlays[0].criteria[0].metric).toBe('timeOfDay');
366
+ expect(schedule.overlays[0].criteria[0].condition).toBe('between');
367
+ expect(schedule.overlays[0].criteria[0].value).toBe('09:00-17:00');
368
+ });
369
+
370
+ it('should return empty criteria array when no criteria elements', () => {
371
+ const xml = `
372
+ <schedule>
373
+ <default file="0"/>
374
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1"/>
375
+ </schedule>
376
+ `;
377
+
378
+ const schedule = parseScheduleResponse(xml);
379
+
380
+ expect(schedule.layouts[0].criteria).toEqual([]);
381
+ });
382
+
383
+ it('should parse geoLocation and isGeoAware from layouts', () => {
384
+ const xml = `
385
+ <schedule>
386
+ <default file="0"/>
387
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1" isGeoAware="1" geoLocation="41.3851,2.1734"/>
388
+ </schedule>
389
+ `;
390
+
391
+ const schedule = parseScheduleResponse(xml);
392
+
393
+ expect(schedule.layouts[0].isGeoAware).toBe(true);
394
+ expect(schedule.layouts[0].geoLocation).toBe('41.3851,2.1734');
395
+ });
396
+
397
+ it('should default isGeoAware to false and geoLocation to empty', () => {
398
+ const xml = `
399
+ <schedule>
400
+ <default file="0"/>
401
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1"/>
402
+ </schedule>
403
+ `;
404
+
405
+ const schedule = parseScheduleResponse(xml);
406
+
407
+ expect(schedule.layouts[0].isGeoAware).toBe(false);
408
+ expect(schedule.layouts[0].geoLocation).toBe('');
409
+ });
410
+ });
411
+
412
+ describe('Sync Event Parsing', () => {
413
+ it('should parse syncEvent from standalone layout', () => {
414
+ const xml = `
415
+ <schedule>
416
+ <default file="0"/>
417
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1" syncEvent="1"/>
418
+ <layout file="200" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="2" syncEvent="0"/>
419
+ </schedule>
420
+ `;
421
+
422
+ const schedule = parseScheduleResponse(xml);
423
+
424
+ expect(schedule.layouts[0].syncEvent).toBe(true);
425
+ expect(schedule.layouts[1].syncEvent).toBe(false);
426
+ });
427
+
428
+ it('should parse syncEvent from campaign layout', () => {
429
+ const xml = `
430
+ <schedule>
431
+ <default file="0"/>
432
+ <campaign id="1" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="5">
433
+ <layout file="100" syncEvent="1"/>
434
+ <layout file="101" syncEvent="0"/>
435
+ </campaign>
436
+ </schedule>
437
+ `;
438
+
439
+ const schedule = parseScheduleResponse(xml);
440
+
441
+ expect(schedule.campaigns[0].layouts[0].syncEvent).toBe(true);
442
+ expect(schedule.campaigns[0].layouts[1].syncEvent).toBe(false);
443
+ });
444
+
445
+ it('should parse syncEvent from overlay', () => {
446
+ const xml = `
447
+ <schedule>
448
+ <default file="0"/>
449
+ <overlays>
450
+ <overlay file="300" duration="30" fromdt="2026-01-01 00:00:00" todt="2026-12-31 23:59:59" priority="5" scheduleid="3" syncEvent="1"/>
451
+ </overlays>
452
+ </schedule>
453
+ `;
454
+
455
+ const schedule = parseScheduleResponse(xml);
456
+
457
+ expect(schedule.overlays[0].syncEvent).toBe(true);
458
+ });
459
+
460
+ it('should default syncEvent to false when not present', () => {
461
+ const xml = `
462
+ <schedule>
463
+ <default file="0"/>
464
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1"/>
465
+ </schedule>
466
+ `;
467
+
468
+ const schedule = parseScheduleResponse(xml);
469
+
470
+ expect(schedule.layouts[0].syncEvent).toBe(false);
471
+ });
472
+
473
+ it('should parse shareOfVoice from layouts', () => {
474
+ const xml = `
475
+ <schedule>
476
+ <default file="0"/>
477
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1" shareOfVoice="30"/>
478
+ <layout file="200" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="2"/>
479
+ </schedule>
480
+ `;
481
+
482
+ const schedule = parseScheduleResponse(xml);
483
+
484
+ expect(schedule.layouts[0].shareOfVoice).toBe(30);
485
+ expect(schedule.layouts[1].shareOfVoice).toBe(0);
486
+ });
487
+ });
488
+
489
+ describe('Actions and Commands Together', () => {
490
+ it('should parse both actions and commands in same schedule', () => {
491
+ const xml = `
492
+ <schedule>
493
+ <default file="0"/>
494
+ <layout file="100" priority="5" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="1"/>
495
+ <campaign id="1" priority="10" fromdt="2026-01-30 00:00:00" todt="2026-01-31 23:59:59" scheduleid="2">
496
+ <layout file="200"/>
497
+ </campaign>
498
+ <actions>
499
+ <action actionType="navLayout" triggerCode="tc1" layoutCode="42" fromdt="2026-01-01 00:00:00" todt="2030-12-31 23:59:59" priority="1" scheduleid="10"/>
500
+ </actions>
501
+ <command command="collectNow" date="2026-02-11"/>
502
+ </schedule>
503
+ `;
504
+
505
+ const schedule = parseScheduleResponse(xml);
506
+
507
+ expect(schedule.default).toBe('0');
508
+ expect(schedule.layouts).toHaveLength(1);
509
+ expect(schedule.campaigns).toHaveLength(1);
510
+ expect(schedule.actions).toHaveLength(1);
511
+ expect(schedule.actions[0].triggerCode).toBe('tc1');
512
+ expect(schedule.commands).toHaveLength(1);
513
+ expect(schedule.commands[0].code).toBe('collectNow');
514
+ });
515
+ });
516
+ });
package/vite.config.js ADDED
@@ -0,0 +1,51 @@
1
+ import { defineConfig } from 'vite';
2
+ import { copyFileSync, mkdirSync, existsSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+
9
+ export default defineConfig({
10
+ base: '/player/',
11
+ build: {
12
+ outDir: 'dist',
13
+ rollupOptions: {
14
+ input: {
15
+ main: './index.html',
16
+ setup: './setup.html'
17
+ },
18
+ output: {
19
+ manualChunks: {
20
+ 'pdfjs': ['pdfjs-dist']
21
+ }
22
+ }
23
+ }
24
+ },
25
+ server: {
26
+ port: 5173,
27
+ strictPort: false
28
+ },
29
+ plugins: [
30
+ {
31
+ name: 'copy-pdfjs-worker',
32
+ closeBundle() {
33
+ // Copy PDF.js worker to dist after build
34
+ try {
35
+ // Try local node_modules first, then parent (workspace root)
36
+ let workerSrc = join(__dirname, 'node_modules', 'pdfjs-dist', 'build', 'pdf.worker.min.mjs');
37
+ if (!existsSync(workerSrc)) {
38
+ workerSrc = join(__dirname, '..', '..', 'node_modules', 'pdfjs-dist', 'build', 'pdf.worker.min.mjs');
39
+ }
40
+
41
+ const workerDest = join(__dirname, 'dist', 'pdf.worker.min.mjs');
42
+ mkdirSync(dirname(workerDest), { recursive: true });
43
+ copyFileSync(workerSrc, workerDest);
44
+ console.log('✓ Copied PDF.js worker to dist/');
45
+ } catch (error) {
46
+ console.warn('⚠ Could not copy PDF.js worker:', error.message);
47
+ }
48
+ }
49
+ }
50
+ ]
51
+ });
@@ -0,0 +1,35 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ // Environment
6
+ environment: 'jsdom', // Browser-like environment
7
+
8
+ // Test files
9
+ include: ['src/**/*.test.js'],
10
+ exclude: ['node_modules', 'dist'],
11
+
12
+ // Coverage
13
+ coverage: {
14
+ provider: 'v8',
15
+ reporter: ['text', 'json', 'html'],
16
+ include: ['src/**/*.js'],
17
+ exclude: ['src/**/*.test.js', 'src/**/*.spec.js'],
18
+ thresholds: {
19
+ lines: 80,
20
+ functions: 80,
21
+ branches: 75,
22
+ statements: 80
23
+ }
24
+ },
25
+
26
+ // Globals
27
+ globals: true,
28
+
29
+ // Timeout
30
+ testTimeout: 10000,
31
+
32
+ // Reporters
33
+ reporters: ['verbose']
34
+ }
35
+ });