From f7bdd799548f43c73e64d24e0acb6348362ce418 Mon Sep 17 00:00:00 2001 From: xidedix Date: Thu, 22 Oct 2026 11:59:50 +0200 Subject: [PATCH] test(Tooltip, Popover): timer specs check the directive's own show and hide timers `vi.getTimerCount()` counted every pending fake timer, not only the directive's; one extra timer created during the test failed the show-timer spec on CI (its source was not measured). Show: the tooltip or popover element keeps `d-none` after destroy. Hide: destroy clears the 300 ms hide timer the directive scheduled. Dropping either `clearTimeout` in `destroyTooltipElement` fails its spec, also with a hide callback that tolerates a missing `tooltipRef`. `afterEach` restores spies before real timers, so a spy on a faked `setTimeout` is not put back after the clock is gone. --- .../src/lib/popover/popover.directive.spec.ts | 19 +++++++++++++------ .../src/lib/tooltip/tooltip.directive.spec.ts | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/projects/coreui-angular/src/lib/popover/popover.directive.spec.ts b/projects/coreui-angular/src/lib/popover/popover.directive.spec.ts index 8c0980d0..99f41c00 100644 --- a/projects/coreui-angular/src/lib/popover/popover.directive.spec.ts +++ b/projects/coreui-angular/src/lib/popover/popover.directive.spec.ts @@ -53,8 +53,8 @@ describe('PopoverDirective', () => { }); afterEach(() => { - vi.useRealTimers(); vi.restoreAllMocks(); + vi.useRealTimers(); }); it('should create an instance', () => { @@ -155,22 +155,29 @@ describe('PopoverDirective', () => { it('should not run the show timer when destroyed before the popover shows', async () => { visible.set(true); fixture.detectChanges(); + const popover = document.getElementById( + fixture.nativeElement.getAttribute('aria-describedby') ?? '' + ) as HTMLElement; await vi.advanceTimersByTimeAsync(50); fixture.destroy(); - await vi.advanceTimersByTimeAsync(0); - expect(vi.getTimerCount()).toBe(0); + await vi.advanceTimersByTimeAsync(150); + expect(popover.classList.contains('d-none')).toBe(true); }); - it('should leave no pending timers when destroyed while the popover hides', async () => { + it('should cancel the hide timer when destroyed while the popover hides', async () => { visible.set(true); fixture.detectChanges(); await vi.runAllTimersAsync(); + const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); + const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout'); visible.set(false); fixture.detectChanges(); await vi.advanceTimersByTimeAsync(50); + const hideTimer = + setTimeoutSpy.mock.results[setTimeoutSpy.mock.calls.findIndex(([, delay]) => delay === 300)]?.value; + expect(hideTimer).toBeDefined(); fixture.destroy(); - await vi.advanceTimersByTimeAsync(0); - expect(vi.getTimerCount()).toBe(0); + expect(clearTimeoutSpy).toHaveBeenCalledWith(hideTimer); }); it('should destroy Popper when the popover hides', async () => { diff --git a/projects/coreui-angular/src/lib/tooltip/tooltip.directive.spec.ts b/projects/coreui-angular/src/lib/tooltip/tooltip.directive.spec.ts index 84a60b90..913fa34b 100644 --- a/projects/coreui-angular/src/lib/tooltip/tooltip.directive.spec.ts +++ b/projects/coreui-angular/src/lib/tooltip/tooltip.directive.spec.ts @@ -54,8 +54,8 @@ describe('TooltipDirective', () => { }); afterEach(() => { - vi.useRealTimers(); vi.restoreAllMocks(); + vi.useRealTimers(); }); it('should create an instance', () => { @@ -156,22 +156,29 @@ describe('TooltipDirective', () => { it('should not run the show timer when destroyed before the tooltip shows', async () => { visible.set(true); fixture.detectChanges(); + const tooltip = document.getElementById( + fixture.nativeElement.getAttribute('aria-describedby') ?? '' + ) as HTMLElement; await vi.advanceTimersByTimeAsync(50); fixture.destroy(); - await vi.advanceTimersByTimeAsync(0); - expect(vi.getTimerCount()).toBe(0); + await vi.advanceTimersByTimeAsync(150); + expect(tooltip.classList.contains('d-none')).toBe(true); }); - it('should leave no pending timers when destroyed while the tooltip hides', async () => { + it('should cancel the hide timer when destroyed while the tooltip hides', async () => { visible.set(true); fixture.detectChanges(); await vi.runAllTimersAsync(); + const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout'); + const clearTimeoutSpy = vi.spyOn(globalThis, 'clearTimeout'); visible.set(false); fixture.detectChanges(); await vi.advanceTimersByTimeAsync(50); + const hideTimer = + setTimeoutSpy.mock.results[setTimeoutSpy.mock.calls.findIndex(([, delay]) => delay === 300)]?.value; + expect(hideTimer).toBeDefined(); fixture.destroy(); - await vi.advanceTimersByTimeAsync(0); - expect(vi.getTimerCount()).toBe(0); + expect(clearTimeoutSpy).toHaveBeenCalledWith(hideTimer); }); it('should destroy Popper when the tooltip hides', async () => {