lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui meldungsuhr.test.ts
| 1 | import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
| 2 | import { notify } from '@/ui/feedback'; |
| 3 | |
| 4 | /** |
| 5 | * Die Uhr einer Kurzmeldung und ihre zwei Anhaltegruende. |
| 6 | * |
| 7 | * Zeiger und Fokus halten dieselbe Uhr an, sind aber voneinander unabhaengig: |
| 8 | * Wer eine Meldung mit der Maus festhaelt und dann mit dem Tabulator zur |
| 9 | * Schliessen-Schaltflaeche geht, hat beide gleichzeitig. Wurde nur ein Merkmal |
| 10 | * gefuehrt, nahm der zuerst weichende Grund die Uhr wieder auf, und die Meldung |
| 11 | * verschwand unter der Schaltflaeche, die gerade angesteuert wurde. Deshalb |
| 12 | * pruefen die Faelle unten jede der vier Reihenfolgen einzeln. |
| 13 | */ |
| 14 | |
| 15 | const DAUER = 2500; |
| 16 | |
| 17 | function meldungsknoten(): HTMLElement | null { |
| 18 | return document.querySelector<HTMLElement>('.meldung'); |
| 19 | } |
| 20 | |
| 21 | function schliessKnopf(): HTMLElement { |
| 22 | const knopf = document.querySelector<HTMLElement>('.meldung-schliessen'); |
| 23 | if (knopf === null) throw new Error('Meldung ohne Schliessen-Schaltflaeche'); |
| 24 | return knopf; |
| 25 | } |
| 26 | |
| 27 | /** Zeigereintritt und -austritt steigen nicht auf, sie treffen den Kasten selbst. */ |
| 28 | function zeiger(art: 'mouseenter' | 'mouseleave'): void { |
| 29 | meldungsknoten()?.dispatchEvent(new MouseEvent(art, { bubbles: false })); |
| 30 | } |
| 31 | |
| 32 | /** |
| 33 | * Fokuswechsel an der Schaltflaeche im Inneren. |
| 34 | * |
| 35 | * Absichtlich von der Schaltflaeche aus und aufsteigend: Der Meldungskasten |
| 36 | * selbst nimmt keinen Fokus an, er hoert nur mit, was aus seinem Inneren |
| 37 | * heraufkommt. |
| 38 | */ |
| 39 | function fokus(art: 'focusin' | 'focusout'): void { |
| 40 | schliessKnopf().dispatchEvent(new FocusEvent(art, { bubbles: true })); |
| 41 | } |
| 42 | |
| 43 | describe('Uhr einer Kurzmeldung', () => { |
| 44 | beforeEach(() => { |
| 45 | vi.useFakeTimers(); |
| 46 | document.body.replaceChildren(); |
| 47 | }); |
| 48 | |
| 49 | afterEach(() => { |
| 50 | vi.useRealTimers(); |
| 51 | document.body.replaceChildren(); |
| 52 | }); |
| 53 | |
| 54 | it('entfernt die Meldung nach Ablauf der Zeit', () => { |
| 55 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 56 | expect(meldungsknoten()).not.toBeNull(); |
| 57 | |
| 58 | vi.advanceTimersByTime(DAUER); |
| 59 | expect(meldungsknoten()).toBeNull(); |
| 60 | }); |
| 61 | |
| 62 | it('haelt die Uhr an, solange der Zeiger auf der Meldung steht', () => { |
| 63 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 64 | |
| 65 | zeiger('mouseenter'); |
| 66 | vi.advanceTimersByTime(DAUER * 4); |
| 67 | expect(meldungsknoten()).not.toBeNull(); |
| 68 | }); |
| 69 | |
| 70 | it('behaelt die Meldung, wenn der Zeiger geht und der Fokus bleibt', () => { |
| 71 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 72 | |
| 73 | zeiger('mouseenter'); |
| 74 | fokus('focusin'); |
| 75 | zeiger('mouseleave'); |
| 76 | |
| 77 | vi.advanceTimersByTime(DAUER * 4); |
| 78 | expect(meldungsknoten()).not.toBeNull(); |
| 79 | }); |
| 80 | |
| 81 | it('behaelt die Meldung, wenn der Fokus geht und der Zeiger bleibt', () => { |
| 82 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 83 | |
| 84 | fokus('focusin'); |
| 85 | zeiger('mouseenter'); |
| 86 | fokus('focusout'); |
| 87 | |
| 88 | vi.advanceTimersByTime(DAUER * 4); |
| 89 | expect(meldungsknoten()).not.toBeNull(); |
| 90 | }); |
| 91 | |
| 92 | it('laesst die Uhr erst weiterlaufen, wenn Zeiger und Fokus weg sind', () => { |
| 93 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 94 | |
| 95 | zeiger('mouseenter'); |
| 96 | fokus('focusin'); |
| 97 | zeiger('mouseleave'); |
| 98 | fokus('focusout'); |
| 99 | |
| 100 | vi.advanceTimersByTime(DAUER); |
| 101 | expect(meldungsknoten()).toBeNull(); |
| 102 | }); |
| 103 | |
| 104 | it('beginnt die Zeit nach dem Verlassen von vorn', () => { |
| 105 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 106 | |
| 107 | vi.advanceTimersByTime(DAUER - 100); |
| 108 | zeiger('mouseenter'); |
| 109 | zeiger('mouseleave'); |
| 110 | |
| 111 | // Die verbleibenden 100 ms duerfen die Meldung nicht mehr entfernen - nach |
| 112 | // dem Verlassen steht die volle Lesezeit erneut zur Verfuegung. |
| 113 | vi.advanceTimersByTime(100); |
| 114 | expect(meldungsknoten()).not.toBeNull(); |
| 115 | |
| 116 | vi.advanceTimersByTime(DAUER); |
| 117 | expect(meldungsknoten()).toBeNull(); |
| 118 | }); |
| 119 | |
| 120 | it('startet nach dem Schliessen von Hand keine neue Uhr', () => { |
| 121 | notify('Plan gespeichert', 'erfolg', DAUER); |
| 122 | |
| 123 | const knoten = meldungsknoten(); |
| 124 | const knopf = schliessKnopf(); |
| 125 | zeiger('mouseenter'); |
| 126 | knopf.dispatchEvent(new MouseEvent('click', { bubbles: true })); |
| 127 | expect(meldungsknoten()).toBeNull(); |
| 128 | |
| 129 | // Zeiger- und Fokusaustritt treffen auch auf einer Flaeche ein, die es nicht |
| 130 | // mehr gibt - der Zeiger verlaesst ja den entfernten Knoten. Sie duerfen |
| 131 | // keinen Zeitgeber hinterlassen, der auf ihm weiterlaeuft. |
| 132 | knoten?.dispatchEvent(new MouseEvent('mouseleave', { bubbles: false })); |
| 133 | knopf.dispatchEvent(new FocusEvent('focusout', { bubbles: true })); |
| 134 | expect(vi.getTimerCount()).toBe(0); |
| 135 | }); |
| 136 | |
| 137 | it('laesst Warnungen ohne Uhr stehen', () => { |
| 138 | notify('Umlaufzeit gekappt', 'warnung', DAUER); |
| 139 | |
| 140 | vi.advanceTimersByTime(DAUER * 4); |
| 141 | expect(meldungsknoten()).not.toBeNull(); |
| 142 | }); |
| 143 | }); |