Blame Raw
· · · 719 lines (31.3 KB)
0 contributors
1import { test, expect } from '@playwright/test';
2import { testSetup } from './frontent-test-utils.js';
3
4test.describe('MacroRegistry', () => {
5 // Currently this test suits runs without ST context. Enable, if ever needed
6 test.beforeEach(testSetup.awaitST);
7
8 test.describe('register valid', () => {
9 test('should register a macro with valid options', async ({ page }) => {
10 const result = await page.evaluate(async () => {
11 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
12 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
13
14 MacroRegistry.unregisterMacro('test-valid');
15 MacroRegistry.registerMacro('test-valid', {
16 unnamedArgs: 2,
17 list: { min: 1, max: 3 },
18 strictArgs: false,
19 description: 'Test macro for validation.',
20 handler: ({ args }) => args.join(','),
21 });
22
23 const def = MacroRegistry.getMacro('test-valid');
24 return {
25 name: def?.name,
26 minArgs: def?.minArgs,
27 maxArgs: def?.maxArgs,
28 list: def?.list,
29 strictArgs: def?.strictArgs,
30 description: def?.description,
31 };
32 });
33
34 expect(result).toEqual({
35 name: 'test-valid',
36 minArgs: 2,
37 maxArgs: 2,
38 list: { min: 1, max: 3 },
39 strictArgs: false,
40 description: 'Test macro for validation.',
41 });
42 });
43 });
44
45 test.describe('register reject', () => {
46 test('should reject invalid macro name', async ({ page }) => {
47 const result = await registerMacroAndCaptureErrors(page, {
48 macroName: ' ',
49 options: {},
50 });
51
52 expect(result.registered).toBeNull();
53 expect(result.errors.length).toBeGreaterThan(0);
54
55 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
56 expect(registrationError).toBeTruthy();
57 expect(registrationError?.text).toContain('Failed to register macro ""');
58 expect(registrationError?.errorMessage).toContain('Must start with a letter, followed by alphanumeric characters or hyphens.');
59 });
60
61 test('should reject invalid options object', async ({ page }) => {
62 const result = await registerMacroAndCaptureErrors(page, {
63 macroName: 'invalid-options',
64 options: null,
65 });
66
67 expect(result.registered).toBeNull();
68 expect(result.errors.length).toBeGreaterThan(0);
69
70 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
71 expect(registrationError).toBeTruthy();
72 expect(registrationError?.text).toContain('Failed to register macro "invalid-options"');
73 expect(registrationError?.errorMessage).toContain('options must be a non-null object');
74 });
75
76 test('should reject invalid handler', async ({ page }) => {
77 const result = await registerMacroAndCaptureErrors(page, {
78 macroName: 'no-handler',
79 options: { handler: null },
80 });
81
82 expect(result.registered).toBeNull();
83 expect(result.errors.length).toBeGreaterThan(0);
84
85 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
86 expect(registrationError).toBeTruthy();
87 expect(registrationError?.text).toContain('Failed to register macro "no-handler"');
88 expect(registrationError?.errorMessage).toContain('options.handler must be a function');
89 });
90
91 test('should reject invalid unnamedArgs', async ({ page }) => {
92 const result = await registerMacroAndCaptureErrors(page, {
93 macroName: 'bad-required',
94 options: {
95 unnamedArgs: -1,
96 },
97 });
98
99 expect(result.registered).toBeNull();
100 expect(result.errors.length).toBeGreaterThan(0);
101
102 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
103 expect(registrationError).toBeTruthy();
104 expect(registrationError?.text).toContain('Failed to register macro "bad-required"');
105 expect(registrationError?.errorMessage).toContain('options.unnamedArgs must be a non-negative integer');
106 });
107
108 test('should reject invalid strictArgs', async ({ page }) => {
109 const result = await registerMacroAndCaptureErrors(page, {
110 macroName: 'bad-strict',
111 options: {
112 strictArgs: 'yes',
113 },
114 });
115
116 expect(result.registered).toBeNull();
117 expect(result.errors.length).toBeGreaterThan(0);
118
119 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
120 expect(registrationError).toBeTruthy();
121 expect(registrationError?.text).toContain('Failed to register macro "bad-strict"');
122 expect(registrationError?.errorMessage).toContain('options.strictArgs must be a boolean');
123 });
124
125 test('should reject invalid list configuration', async ({ page }) => {
126 const result = await registerMacroAndCaptureErrors(page, {
127 macroName: 'bad-list-type',
128 options: {
129 list: 'invalid',
130 },
131 });
132
133 expect(result.registered).toBeNull();
134 expect(result.errors.length).toBeGreaterThan(0);
135
136 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
137 expect(registrationError).toBeTruthy();
138 expect(registrationError?.text).toContain('Failed to register macro "bad-list-type"');
139 expect(registrationError?.errorMessage).toContain('options.list must be a boolean');
140 });
141
142 test('should reject invalid list.min', async ({ page }) => {
143 const result = await registerMacroAndCaptureErrors(page, {
144 macroName: 'bad-list-min',
145 options: {
146 list: { min: -1 },
147 },
148 });
149
150 expect(result.registered).toBeNull();
151 expect(result.errors.length).toBeGreaterThan(0);
152
153 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
154 expect(registrationError).toBeTruthy();
155 expect(registrationError?.text).toContain('Failed to register macro "bad-list-min"');
156 expect(registrationError?.errorMessage).toContain('options.list.min must be a non-negative integer');
157 });
158
159 test('should reject invalid list.max', async ({ page }) => {
160 const result = await registerMacroAndCaptureErrors(page, {
161 macroName: 'bad-list-max',
162 options: {
163 list: { min: 2, max: 1 },
164 },
165 });
166
167 expect(result.registered).toBeNull();
168 expect(result.errors.length).toBeGreaterThan(0);
169
170 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
171 expect(registrationError).toBeTruthy();
172 expect(registrationError?.text).toContain('Failed to register macro "bad-list-max"');
173 expect(registrationError?.errorMessage).toContain('options.list.max must be greater than or equal to options.list.min');
174 });
175
176 test('should reject invalid description', async ({ page }) => {
177 const result = await registerMacroAndCaptureErrors(page, {
178 macroName: 'bad-desc',
179 options: {
180 description: 123,
181 },
182 });
183
184 expect(result.registered).toBeNull();
185 expect(result.errors.length).toBeGreaterThan(0);
186
187 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
188 expect(registrationError).toBeTruthy();
189 expect(registrationError?.text).toContain('Failed to register macro "bad-desc"');
190 expect(registrationError?.errorMessage).toContain('options.description must be a string');
191 });
192 });
193
194 test.describe('identifier validation', () => {
195 test('should accept valid identifier with letters only', async ({ page }) => {
196 const result = await registerMacroAndCaptureErrors(page, {
197 macroName: 'validMacro',
198 options: {},
199 });
200 expect(result.registered).not.toBeNull();
201 expect(result.errors.length).toBe(0);
202 });
203
204 test('should accept valid identifier with hyphens', async ({ page }) => {
205 const result = await registerMacroAndCaptureErrors(page, {
206 macroName: 'my-macro-name',
207 options: {},
208 });
209 expect(result.registered).not.toBeNull();
210 expect(result.errors.length).toBe(0);
211 });
212
213 test('should accept valid identifier with underscores', async ({ page }) => {
214 const result = await registerMacroAndCaptureErrors(page, {
215 macroName: 'my_macro_name',
216 options: {},
217 });
218 expect(result.registered).not.toBeNull();
219 expect(result.errors.length).toBe(0);
220 });
221
222 test('should accept valid identifier with digits after first char', async ({ page }) => {
223 const result = await registerMacroAndCaptureErrors(page, {
224 macroName: 'macro123',
225 options: {},
226 });
227 expect(result.registered).not.toBeNull();
228 expect(result.errors.length).toBe(0);
229 });
230
231 test('should reject identifier starting with digit', async ({ page }) => {
232 const result = await registerMacroAndCaptureErrors(page, {
233 macroName: '123macro',
234 options: {},
235 });
236 expect(result.registered).toBeNull();
237 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
238 expect(registrationError?.errorMessage).toContain('is invalid');
239 });
240
241 test('should reject identifier starting with hyphen', async ({ page }) => {
242 const result = await registerMacroAndCaptureErrors(page, {
243 macroName: '-macro',
244 options: {},
245 });
246 expect(result.registered).toBeNull();
247 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
248 expect(registrationError?.errorMessage).toContain('is invalid');
249 });
250
251 test('should reject identifier with special characters', async ({ page }) => {
252 const result = await registerMacroAndCaptureErrors(page, {
253 macroName: 'macro@name',
254 options: {},
255 });
256 expect(result.registered).toBeNull();
257 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
258 expect(registrationError?.errorMessage).toContain('is invalid');
259 });
260
261 test('should reject identifier with spaces', async ({ page }) => {
262 const result = await registerMacroAndCaptureErrors(page, {
263 macroName: 'macro name',
264 options: {},
265 });
266 expect(result.registered).toBeNull();
267 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
268 expect(registrationError?.errorMessage).toContain('is invalid');
269 });
270
271 test('should accept valid alias identifier', async ({ page }) => {
272 const result = await registerMacroAndCaptureErrors(page, {
273 macroName: 'primaryMacro',
274 options: {
275 aliases: [{ alias: 'valid-alias_123' }],
276 },
277 });
278 expect(result.registered).not.toBeNull();
279 expect(result.errors.length).toBe(0);
280 });
281
282 test('should reject invalid alias identifier', async ({ page }) => {
283 const result = await registerMacroAndCaptureErrors(page, {
284 macroName: 'primaryMacro2',
285 options: {
286 aliases: [{ alias: '123-invalid' }],
287 },
288 });
289 expect(result.registered).toBeNull();
290 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
291 expect(registrationError?.errorMessage).toContain('is invalid');
292 });
293 });
294
295 test.describe('registerMacroAlias', () => {
296 test.describe('valid', () => {
297 test('should register an alias for an existing macro', async ({ page }) => {
298 const result = await page.evaluate(async () => {
299 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
300 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
301
302 // Clean up any existing registrations
303 MacroRegistry.unregisterMacro('alias-target');
304 MacroRegistry.unregisterMacro('my-alias');
305
306 // Register target macro
307 MacroRegistry.registerMacro('alias-target', {
308 description: 'Target macro for alias test',
309 handler: () => 'target-result',
310 });
311
312 // Register alias
313 const success = MacroRegistry.registerMacroAlias('alias-target', 'my-alias');
314
315 const aliasDef = MacroRegistry.getMacro('my-alias');
316 const targetDef = MacroRegistry.getMacro('alias-target');
317
318 return {
319 success,
320 aliasName: aliasDef?.name,
321 aliasOf: aliasDef?.aliasOf,
322 aliasVisible: aliasDef?.aliasVisible,
323 targetName: targetDef?.name,
324 sameHandler: aliasDef?.handler === targetDef?.handler,
325 };
326 });
327
328 expect(result.success).toBe(true);
329 expect(result.aliasName).toBe('my-alias');
330 expect(result.aliasOf).toBe('alias-target');
331 expect(result.aliasVisible).toBe(true);
332 expect(result.targetName).toBe('alias-target');
333 expect(result.sameHandler).toBe(true);
334 });
335
336 test('should register alias with visible=false option', async ({ page }) => {
337 const result = await page.evaluate(async () => {
338 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
339 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
340
341 MacroRegistry.unregisterMacro('alias-target-hidden');
342 MacroRegistry.unregisterMacro('hidden-alias');
343
344 MacroRegistry.registerMacro('alias-target-hidden', {
345 description: 'Target macro',
346 handler: () => 'result',
347 });
348
349 const success = MacroRegistry.registerMacroAlias('alias-target-hidden', 'hidden-alias', { visible: false });
350 const aliasDef = MacroRegistry.getMacro('hidden-alias');
351
352 return {
353 success,
354 aliasVisible: aliasDef?.aliasVisible,
355 };
356 });
357
358 expect(result.success).toBe(true);
359 expect(result.aliasVisible).toBe(false);
360 });
361
362 test('should resolve alias of alias to primary definition', async ({ page }) => {
363 const result = await page.evaluate(async () => {
364 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
365 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
366
367 MacroRegistry.unregisterMacro('primary-macro');
368 MacroRegistry.unregisterMacro('first-alias');
369 MacroRegistry.unregisterMacro('second-alias');
370
371 // Register primary macro
372 MacroRegistry.registerMacro('primary-macro', {
373 description: 'Primary macro',
374 handler: () => 'primary-result',
375 });
376
377 // Register first alias
378 MacroRegistry.registerMacroAlias('primary-macro', 'first-alias');
379
380 // Register alias of alias (should resolve to primary)
381 const success = MacroRegistry.registerMacroAlias('first-alias', 'second-alias');
382
383 const secondAliasDef = MacroRegistry.getMacro('second-alias');
384
385 return {
386 success,
387 aliasOf: secondAliasDef?.aliasOf,
388 };
389 });
390
391 expect(result.success).toBe(true);
392 // Should point to primary, not to the intermediate alias
393 expect(result.aliasOf).toBe('primary-macro');
394 });
395
396 test('should have independent source for alias', async ({ page }) => {
397 const result = await page.evaluate(async () => {
398 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
399 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
400
401 MacroRegistry.unregisterMacro('source-target');
402 MacroRegistry.unregisterMacro('source-alias');
403
404 MacroRegistry.registerMacro('source-target', {
405 description: 'Target',
406 handler: () => '',
407 });
408
409 MacroRegistry.registerMacroAlias('source-target', 'source-alias');
410
411 const targetDef = MacroRegistry.getMacro('source-target');
412 const aliasDef = MacroRegistry.getMacro('source-alias');
413
414 return {
415 // Both should have source objects
416 targetHasSource: !!targetDef?.source,
417 aliasHasSource: !!aliasDef?.source,
418 // The alias has its own source object (not shared reference)
419 sourcesAreDifferentObjects: targetDef?.source !== aliasDef?.source,
420 };
421 });
422
423 expect(result.targetHasSource).toBe(true);
424 expect(result.aliasHasSource).toBe(true);
425 expect(result.sourcesAreDifferentObjects).toBe(true);
426 });
427
428 test('should be case-insensitive for lookup', async ({ page }) => {
429 const result = await page.evaluate(async () => {
430 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
431 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
432
433 MacroRegistry.unregisterMacro('case-target');
434 MacroRegistry.unregisterMacro('CaseAlias');
435
436 MacroRegistry.registerMacro('case-target', {
437 handler: () => '',
438 });
439
440 MacroRegistry.registerMacroAlias('case-target', 'CaseAlias');
441
442 return {
443 foundLowercase: !!MacroRegistry.getMacro('casealias'),
444 foundUppercase: !!MacroRegistry.getMacro('CASEALIAS'),
445 foundMixed: !!MacroRegistry.getMacro('CaseAlias'),
446 };
447 });
448
449 expect(result.foundLowercase).toBe(true);
450 expect(result.foundUppercase).toBe(true);
451 expect(result.foundMixed).toBe(true);
452 });
453 });
454
455 test.describe('reject', () => {
456 test('should reject invalid alias name', async ({ page }) => {
457 const result = await registerAliasAndCaptureErrors(page, {
458 targetMacroName: 'random',
459 aliasName: '123-invalid',
460 });
461
462 expect(result.success).toBe(false);
463 expect(result.errors.length).toBeGreaterThan(0);
464
465 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
466 expect(registrationError).toBeTruthy();
467 expect(registrationError?.text).toContain('Failed to register alias "123-invalid"');
468 expect(registrationError?.errorMessage).toContain('is invalid');
469 });
470
471 test('should reject alias same as target name (case insensitive)', async ({ page }) => {
472 const result = await registerAliasAndCaptureErrors(page, {
473 targetMacroName: 'random',
474 aliasName: 'RANDOM',
475 });
476
477 expect(result.success).toBe(false);
478 expect(result.errors.length).toBeGreaterThan(0);
479
480 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
481 expect(registrationError).toBeTruthy();
482 expect(registrationError?.errorMessage).toContain('cannot be the same as the target macro name');
483 });
484
485 test('should reject alias for non-existent target macro', async ({ page }) => {
486 const result = await registerAliasAndCaptureErrors(page, {
487 targetMacroName: 'non-existent-macro-xyz',
488 aliasName: 'my-alias',
489 });
490
491 expect(result.success).toBe(false);
492 expect(result.errors.length).toBeGreaterThan(0);
493
494 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
495 expect(registrationError).toBeTruthy();
496 expect(registrationError?.errorMessage).toContain('is not registered');
497 });
498
499 test('should reject alias with special characters', async ({ page }) => {
500 const result = await registerAliasAndCaptureErrors(page, {
501 targetMacroName: 'random',
502 aliasName: 'alias@name',
503 });
504
505 expect(result.success).toBe(false);
506 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
507 expect(registrationError?.errorMessage).toContain('is invalid');
508 });
509
510 test('should reject alias starting with hyphen', async ({ page }) => {
511 const result = await registerAliasAndCaptureErrors(page, {
512 targetMacroName: 'random',
513 aliasName: '-alias',
514 });
515
516 expect(result.success).toBe(false);
517 const registrationError = result.errors.find(e => e.text.includes('[Macro] Registration Error:'));
518 expect(registrationError?.errorMessage).toContain('is invalid');
519 });
520 });
521
522 test.describe('warnings', () => {
523 test('should warn when alias overwrites existing macro', async ({ page }) => {
524 const result = await page.evaluate(async () => {
525 /** @type {string[]} */
526 const warnings = [];
527 const originalWarn = console.warn;
528
529 console.warn = (...args) => {
530 warnings.push(args.map(a => String(a)).join(' '));
531 };
532
533 try {
534 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
535 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
536
537 MacroRegistry.unregisterMacro('overwrite-target');
538 MacroRegistry.unregisterMacro('overwrite-existing');
539
540 // Register target macro
541 MacroRegistry.registerMacro('overwrite-target', {
542 handler: () => 'target',
543 });
544
545 // Register a macro that will be overwritten
546 MacroRegistry.registerMacro('overwrite-existing', {
547 handler: () => 'existing',
548 });
549
550 // Register alias that overwrites existing macro
551 const success = MacroRegistry.registerMacroAlias('overwrite-target', 'overwrite-existing');
552
553 return { success, warnings };
554 } finally {
555 console.warn = originalWarn;
556 }
557 });
558
559 expect(result.success).toBe(true);
560 const overwriteWarning = result.warnings.find(w =>
561 w.includes('overwrites an existing macro') && w.includes('overwrite-existing'),
562 );
563 expect(overwriteWarning).toBeTruthy();
564 });
565
566 test('should warn when alias overwrites another alias', async ({ page }) => {
567 const result = await page.evaluate(async () => {
568 /** @type {string[]} */
569 const warnings = [];
570 const originalWarn = console.warn;
571
572 console.warn = (...args) => {
573 warnings.push(args.map(a => String(a)).join(' '));
574 };
575
576 try {
577 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
578 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
579
580 MacroRegistry.unregisterMacro('alias-warn-target1');
581 MacroRegistry.unregisterMacro('alias-warn-target2');
582 MacroRegistry.unregisterMacro('shared-alias-name');
583
584 // Register two target macros
585 MacroRegistry.registerMacro('alias-warn-target1', { handler: () => '1' });
586 MacroRegistry.registerMacro('alias-warn-target2', { handler: () => '2' });
587
588 // Register first alias
589 MacroRegistry.registerMacroAlias('alias-warn-target1', 'shared-alias-name');
590
591 // Clear warnings from first registration
592 warnings.length = 0;
593
594 // Register second alias with same name (should warn)
595 MacroRegistry.registerMacroAlias('alias-warn-target2', 'shared-alias-name');
596
597 return { warnings };
598 } finally {
599 console.warn = originalWarn;
600 }
601 });
602
603 const overwriteWarning = result.warnings.find(w =>
604 w.includes('overwrites an existing macro') && w.includes('shared-alias-name'),
605 );
606 expect(overwriteWarning).toBeTruthy();
607 });
608 });
609 });
610});
611
612/**
613 * @typedef {Object} CapturedConsoleError
614 * @property {string} text
615 * @property {string|null} errorMessage
616 */
617
618/**
619 * @param {import('@playwright/test').Page} page
620 * @param {{ macroName: string, options: import('../../public/scripts/macros/engine/MacroRegistry.js').MacroDefinitionOptions|null }} params
621 * @returns {Promise<{ registered: unknown, errors: CapturedConsoleError[] }>}
622 */
623async function registerMacroAndCaptureErrors(page, { macroName, options }) {
624 const result = await page.evaluate(async ({ macroName, options }) => {
625 /** @type {CapturedConsoleError[]} */
626 const errors = [];
627 const originalError = console.error;
628
629 console.error = (...args) => {
630 const text = args
631 .map(a => (typeof a === 'string' ? a : (a instanceof Error ? `Error: ${a.message}` : '')))
632 .filter(Boolean)
633 .join(' ');
634
635 /** @type {string|null} */
636 let errorMessage = null;
637 for (const a of args) {
638 if (a instanceof Error) {
639 errorMessage ??= a.message;
640 continue;
641 }
642 if (a && typeof a === 'object' && 'error' in a && a.error instanceof Error) {
643 errorMessage ??= a.error.message;
644 }
645 }
646
647 errors.push({ text, errorMessage });
648 };
649
650 try {
651 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
652 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
653
654 /** @type {any} */
655 let resolvedOptions = options;
656 if (resolvedOptions && typeof resolvedOptions === 'object' && !('handler' in resolvedOptions)) {
657 resolvedOptions = {
658 ...resolvedOptions,
659 handler: () => '',
660 };
661 }
662
663 // Registering an invalid macro does not throw. It returns null and logs an error.
664 const registered = MacroRegistry.registerMacro(macroName, resolvedOptions);
665 return { registered, errors };
666 } finally {
667 console.error = originalError;
668 }
669 }, { macroName, options });
670
671 return result;
672}
673
674/**
675 * @param {import('@playwright/test').Page} page
676 * @param {{ targetMacroName: string, aliasName: string, options?: { visible?: boolean } }} params
677 * @returns {Promise<{ success: boolean, errors: CapturedConsoleError[] }>}
678 */
679async function registerAliasAndCaptureErrors(page, { targetMacroName, aliasName, options = {} }) {
680 const result = await page.evaluate(async ({ targetMacroName, aliasName, options }) => {
681 /** @type {CapturedConsoleError[]} */
682 const errors = [];
683 const originalError = console.error;
684
685 console.error = (...args) => {
686 const text = args
687 .map(a => (typeof a === 'string' ? a : (a instanceof Error ? `Error: ${a.message}` : '')))
688 .filter(Boolean)
689 .join(' ');
690
691 /** @type {string|null} */
692 let errorMessage = null;
693 for (const a of args) {
694 if (a instanceof Error) {
695 errorMessage ??= a.message;
696 continue;
697 }
698 if (a && typeof a === 'object' && 'error' in a && a.error instanceof Error) {
699 errorMessage ??= a.error.message;
700 }
701 }
702
703 errors.push({ text, errorMessage });
704 };
705
706 try {
707 /** @type {import('../../public/scripts/macros/engine/MacroRegistry.js')} */
708 const { MacroRegistry } = await import('./scripts/macros/engine/MacroRegistry.js');
709
710 // Registering an invalid alias does not throw. It returns false and logs an error.
711 const success = MacroRegistry.registerMacroAlias(targetMacroName, aliasName, options);
712 return { success, errors };
713 } finally {
714 console.error = originalError;
715 }
716 }, { targetMacroName, aliasName, options });
717
718 return result;
719}