import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { notify } from '@/ui/feedback'; /** * Die Uhr einer Kurzmeldung und ihre zwei Anhaltegruende. * * Zeiger und Fokus halten dieselbe Uhr an, sind aber voneinander unabhaengig: * Wer eine Meldung mit der Maus festhaelt und dann mit dem Tabulator zur * Schliessen-Schaltflaeche geht, hat beide gleichzeitig. Wurde nur ein Merkmal * gefuehrt, nahm der zuerst weichende Grund die Uhr wieder auf, und die Meldung * verschwand unter der Schaltflaeche, die gerade angesteuert wurde. Deshalb * pruefen die Faelle unten jede der vier Reihenfolgen einzeln. */ const DAUER = 2500; function meldungsknoten(): HTMLElement | null { return document.querySelector('.meldung'); } function schliessKnopf(): HTMLElement { const knopf = document.querySelector('.meldung-schliessen'); if (knopf === null) throw new Error('Meldung ohne Schliessen-Schaltflaeche'); return knopf; } /** Zeigereintritt und -austritt steigen nicht auf, sie treffen den Kasten selbst. */ function zeiger(art: 'mouseenter' | 'mouseleave'): void { meldungsknoten()?.dispatchEvent(new MouseEvent(art, { bubbles: false })); } /** * Fokuswechsel an der Schaltflaeche im Inneren. * * Absichtlich von der Schaltflaeche aus und aufsteigend: Der Meldungskasten * selbst nimmt keinen Fokus an, er hoert nur mit, was aus seinem Inneren * heraufkommt. */ function fokus(art: 'focusin' | 'focusout'): void { schliessKnopf().dispatchEvent(new FocusEvent(art, { bubbles: true })); } describe('Uhr einer Kurzmeldung', () => { beforeEach(() => { vi.useFakeTimers(); document.body.replaceChildren(); }); afterEach(() => { vi.useRealTimers(); document.body.replaceChildren(); }); it('entfernt die Meldung nach Ablauf der Zeit', () => { notify('Plan gespeichert', 'erfolg', DAUER); expect(meldungsknoten()).not.toBeNull(); vi.advanceTimersByTime(DAUER); expect(meldungsknoten()).toBeNull(); }); it('haelt die Uhr an, solange der Zeiger auf der Meldung steht', () => { notify('Plan gespeichert', 'erfolg', DAUER); zeiger('mouseenter'); vi.advanceTimersByTime(DAUER * 4); expect(meldungsknoten()).not.toBeNull(); }); it('behaelt die Meldung, wenn der Zeiger geht und der Fokus bleibt', () => { notify('Plan gespeichert', 'erfolg', DAUER); zeiger('mouseenter'); fokus('focusin'); zeiger('mouseleave'); vi.advanceTimersByTime(DAUER * 4); expect(meldungsknoten()).not.toBeNull(); }); it('behaelt die Meldung, wenn der Fokus geht und der Zeiger bleibt', () => { notify('Plan gespeichert', 'erfolg', DAUER); fokus('focusin'); zeiger('mouseenter'); fokus('focusout'); vi.advanceTimersByTime(DAUER * 4); expect(meldungsknoten()).not.toBeNull(); }); it('laesst die Uhr erst weiterlaufen, wenn Zeiger und Fokus weg sind', () => { notify('Plan gespeichert', 'erfolg', DAUER); zeiger('mouseenter'); fokus('focusin'); zeiger('mouseleave'); fokus('focusout'); vi.advanceTimersByTime(DAUER); expect(meldungsknoten()).toBeNull(); }); it('beginnt die Zeit nach dem Verlassen von vorn', () => { notify('Plan gespeichert', 'erfolg', DAUER); vi.advanceTimersByTime(DAUER - 100); zeiger('mouseenter'); zeiger('mouseleave'); // Die verbleibenden 100 ms duerfen die Meldung nicht mehr entfernen - nach // dem Verlassen steht die volle Lesezeit erneut zur Verfuegung. vi.advanceTimersByTime(100); expect(meldungsknoten()).not.toBeNull(); vi.advanceTimersByTime(DAUER); expect(meldungsknoten()).toBeNull(); }); it('startet nach dem Schliessen von Hand keine neue Uhr', () => { notify('Plan gespeichert', 'erfolg', DAUER); const knoten = meldungsknoten(); const knopf = schliessKnopf(); zeiger('mouseenter'); knopf.dispatchEvent(new MouseEvent('click', { bubbles: true })); expect(meldungsknoten()).toBeNull(); // Zeiger- und Fokusaustritt treffen auch auf einer Flaeche ein, die es nicht // mehr gibt - der Zeiger verlaesst ja den entfernten Knoten. Sie duerfen // keinen Zeitgeber hinterlassen, der auf ihm weiterlaeuft. knoten?.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false })); knopf.dispatchEvent(new FocusEvent('focusout', { bubbles: true })); expect(vi.getTimerCount()).toBe(0); }); it('laesst Warnungen ohne Uhr stehen', () => { notify('Umlaufzeit gekappt', 'warnung', DAUER); vi.advanceTimersByTime(DAUER * 4); expect(meldungsknoten()).not.toBeNull(); }); });