| 291 | expect(registrationError?.errorMessage).toContain('is invalid'); | 291 | expect(registrationError?.errorMessage).toContain('is invalid'); |
| 292 | }); | 292 | }); |
| 293 | }); | 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 | }); |
| 294 | }); | 610 | }); |
| 295 | | 611 | |
| 296 | /** | 612 | /** |