lsa-planer
LSA-Planer Professional – Planungssoftware für Lichtsignalanlagen nach RiLSA 2015 und § 45 StVO. EUPL-1.2.
/ tests ui zeichenflaecheTastatur.test.ts
| 1 | import { describe, expect, it } from 'vitest'; |
| 2 | import * as actions from '@/app/actions'; |
| 3 | import { ProjectStore } from '@/app/store'; |
| 4 | import type { Lageplan, Planlinie } from '@/domain/geometrie/lageplan'; |
| 5 | import type { Punkt } from '@/domain/geometrie/vermessung'; |
| 6 | import { ASSISTENT_VORGABE, projektAusAngaben } from '@/ui/assistent'; |
| 7 | import { lageplanView } from '@/ui/views/lageplanView'; |
| 8 | |
| 9 | /** |
| 10 | * Die Zeichenflaeche als BEDIENELEMENT - Erreichbarkeit, Name, Rolle, Belegung. |
| 11 | * |
| 12 | * WAS HIER ABGESICHERT WIRD UND WARUM ES NICHT SCHON DASTAND |
| 13 | * |
| 14 | * tests/ui/lageplanTastatur.test.ts prueft, was die einzelnen Tasten bewirken - |
| 15 | * aber es schickt die Tastendruecke unmittelbar an die Zeichenflaeche, ohne sie |
| 16 | * vorher zu fokussieren. Damit haengt der gesamte bisherige Nachweis an einer |
| 17 | * Voraussetzung, die nirgends geprueft ist: dass die Flaeche den Fokus |
| 18 | * ueberhaupt bekommen kann. Fiele das `tabindex="0"` aus lageplanView.ts |
| 19 | * heraus, bliebe der ganze Testbestand gruen, waehrend die Flaeche mit der |
| 20 | * Tastatur nicht mehr erreichbar waere - kein Tabulatorstopp, kein |
| 21 | * Tastenzeiger, keine Vermessung ohne Maus. |
| 22 | * |
| 23 | * Deshalb wird hier ausschliesslich ueber das FOKUSSIERTE Element getippt, so |
| 24 | * wie ein Browser die Taste zustellt. Ein Tastendruck, der die Flaeche nicht |
| 25 | * erreicht, darf nichts zeichnen - und genau das ist der Fall, sobald der |
| 26 | * Fokus woanders steht. |
| 27 | * |
| 28 | * DIE MASSE DER PRUEFFLAECHE |
| 29 | * |
| 30 | * In jsdom hat der Rahmen keine Breite; die Zeichenflaeche faellt auf ihre |
| 31 | * Mindestmasse von 320 x 320 Bildschirmpunkten zurueck, die Sicht bleibt die |
| 32 | * Startsicht ohne Zoom. Ein Bildschirmpunkt ist damit genau ein Bildpunkt, und |
| 33 | * der Tastenzeiger beginnt in der Mitte bei (160, 160). Eine Zeichenflaeche |
| 34 | * bringt jsdom nicht mit - die Ansicht meldet das fehlende getContext auf der |
| 35 | * Konsole und baut den Rest trotzdem auf; geprueft wird ohnehin nicht das Bild, |
| 36 | * sondern was im Projekt landet und was angesagt wird. |
| 37 | */ |
| 38 | |
| 39 | const FESTES_DATUM = '2026-01-01T00:00:00.000Z'; |
| 40 | |
| 41 | /** Mitte der Zeichenflaeche - dort beginnt der Tastaturzeiger. */ |
| 42 | const MITTE: Punkt = { x: 160, y: 160 }; |
| 43 | |
| 44 | /** |
| 45 | * Plan mit Massstab und einer waagerechten Haltlinie bei y = 200. |
| 46 | * |
| 47 | * 100 Bildpunkte auf 20 m ergeben 0,2 m je Bildpunkt. Der Massstab ist noetig, |
| 48 | * damit das Werkzeug "Fahrlinie" ueberhaupt freigegeben ist. |
| 49 | */ |
| 50 | function planMitHaltlinie(): Lageplan { |
| 51 | return { |
| 52 | arbeitsbereiche: [], |
| 53 | signalgeber: [], |
| 54 | bild: null, |
| 55 | kalibrierung: { |
| 56 | von: { x: 0, y: 0 }, |
| 57 | bis: { x: 100, y: 0 }, |
| 58 | laengeMeter: 20, |
| 59 | herkunft: 'gemessen', |
| 60 | }, |
| 61 | haltlinien: [ |
| 62 | { |
| 63 | id: 'hl1', |
| 64 | name: 'Nord', |
| 65 | art: 'zufahrt', |
| 66 | armId: null, |
| 67 | von: { x: 100, y: 200 }, |
| 68 | bis: { x: 220, y: 200 }, |
| 69 | }, |
| 70 | ], |
| 71 | linien: [], |
| 72 | }; |
| 73 | } |
| 74 | |
| 75 | interface Ansicht { |
| 76 | readonly store: ProjectStore; |
| 77 | readonly wurzel: HTMLElement; |
| 78 | readonly leinwand: HTMLCanvasElement; |
| 79 | readonly aufraeumen: () => void; |
| 80 | } |
| 81 | |
| 82 | function baueAnsicht(lageplan: Lageplan = planMitHaltlinie()): Ansicht { |
| 83 | const store = new ProjectStore(projektAusAngaben({ ...ASSISTENT_VORGABE }, FESTES_DATUM)); |
| 84 | store.update((p) => actions.setLageplan(p, lageplan), { label: 'Pruefplan' }); |
| 85 | |
| 86 | const wurzel = document.createElement('div'); |
| 87 | document.body.append(wurzel); |
| 88 | const schliessen = lageplanView.render(wurzel, { |
| 89 | store, |
| 90 | refresh: () => {}, |
| 91 | navigate: () => {}, |
| 92 | target: null, |
| 93 | }); |
| 94 | |
| 95 | const leinwand = wurzel.querySelector('canvas'); |
| 96 | if (leinwand === null) throw new Error('Die Ansicht hat keine Zeichenflaeche.'); |
| 97 | |
| 98 | return { |
| 99 | store, |
| 100 | wurzel, |
| 101 | leinwand, |
| 102 | aufraeumen: () => { |
| 103 | // Sonst bliebe der Fokus des einen Falls im naechsten stehen. |
| 104 | leinwand.blur(); |
| 105 | schliessen(); |
| 106 | wurzel.remove(); |
| 107 | }, |
| 108 | }; |
| 109 | } |
| 110 | |
| 111 | /** Waehlt ein Werkzeug ueber seine Schaltflaeche - so wie ein Anwender auch. */ |
| 112 | function waehleWerkzeug(ansicht: Ansicht, beschriftung: string): void { |
| 113 | const knopf = werkzeugKnopf(ansicht, beschriftung); |
| 114 | knopf.click(); |
| 115 | } |
| 116 | |
| 117 | function werkzeugKnopf(ansicht: Ansicht, beschriftung: string): HTMLButtonElement { |
| 118 | const knopf = [...ansicht.wurzel.querySelectorAll('button')].find( |
| 119 | (b) => (b.textContent ?? '').trim() === beschriftung, |
| 120 | ); |
| 121 | if (knopf === undefined) throw new Error(`Kein Werkzeug "${beschriftung}"`); |
| 122 | return knopf; |
| 123 | } |
| 124 | |
| 125 | interface Zusatztasten { |
| 126 | readonly shiftKey?: boolean; |
| 127 | readonly ctrlKey?: boolean; |
| 128 | } |
| 129 | |
| 130 | /** |
| 131 | * Tippt auf das Element, das GERADE DEN FOKUS HAT. |
| 132 | * |
| 133 | * Das ist der Kern dieser Datei: Ein Browser stellt einen Tastendruck dem |
| 134 | * fokussierten Element zu, nicht demjenigen, das der Test gern haette. Wer |
| 135 | * unmittelbar an die Zeichenflaeche sendet, prueft eine Zustellung, die es ohne |
| 136 | * Fokussierbarkeit gar nicht gaebe. |
| 137 | * |
| 138 | * Zurueck kommt, ob der letzte Anschlag abgefangen wurde - ohne |
| 139 | * preventDefault rollt die Seite unter der Zeichenflaeche weg. |
| 140 | */ |
| 141 | function tippe(key: string, zusatz: Zusatztasten = {}, male = 1): boolean { |
| 142 | let abgefangen = false; |
| 143 | for (let i = 0; i < male; i += 1) { |
| 144 | const ziel = (document.activeElement as HTMLElement | null) ?? document.body; |
| 145 | const ereignis = new KeyboardEvent('keydown', { |
| 146 | key, |
| 147 | bubbles: true, |
| 148 | cancelable: true, |
| 149 | ...zusatz, |
| 150 | }); |
| 151 | ziel.dispatchEvent(ereignis); |
| 152 | abgefangen = ereignis.defaultPrevented; |
| 153 | } |
| 154 | return abgefangen; |
| 155 | } |
| 156 | |
| 157 | /** Text der Live-Region unter der Zeichenflaeche. */ |
| 158 | function ansage(ansicht: Ansicht): string { |
| 159 | return ansicht.wurzel.querySelector('[role="status"]')?.textContent ?? ''; |
| 160 | } |
| 161 | |
| 162 | function linien(ansicht: Ansicht): readonly Planlinie[] { |
| 163 | return ansicht.store.getProject().lageplan.linien; |
| 164 | } |
| 165 | |
| 166 | describe('Die Zeichenfläche ist mit der Tastatur erreichbar', () => { |
| 167 | it('liegt in der Tabulatorfolge und nimmt den Fokus tatsächlich an', () => { |
| 168 | const ansicht = baueAnsicht(); |
| 169 | try { |
| 170 | // Beides zusammen: Das Attribut allein sagte nur, dass da etwas steht - |
| 171 | // die Zustellung des Fokus ist die Zusage. |
| 172 | expect(ansicht.leinwand.getAttribute('tabindex')).toBe('0'); |
| 173 | expect(ansicht.leinwand.tabIndex).toBe(0); |
| 174 | |
| 175 | expect(document.activeElement).not.toBe(ansicht.leinwand); |
| 176 | ansicht.leinwand.focus(); |
| 177 | expect(document.activeElement).toBe(ansicht.leinwand); |
| 178 | } finally { |
| 179 | ansicht.aufraeumen(); |
| 180 | } |
| 181 | }); |
| 182 | |
| 183 | it('trägt eine Rolle und einen zugänglichen Namen', () => { |
| 184 | const ansicht = baueAnsicht(); |
| 185 | try { |
| 186 | // "application" und nicht "img": Die Flaeche nimmt Tastendruecke entgegen. |
| 187 | // Von dieser Rolle haengt ab, dass eine Sprachausgabe die Pfeiltasten |
| 188 | // durchreicht, statt mit ihnen selbst durch den Text zu wandern. |
| 189 | expect(ansicht.leinwand.getAttribute('role')).toBe('application'); |
| 190 | |
| 191 | const namensId = ansicht.leinwand.getAttribute('aria-labelledby'); |
| 192 | expect(namensId).not.toBeNull(); |
| 193 | const name = document.getElementById(namensId!); |
| 194 | // Ein Verweis auf ein Element, das es nicht gibt, ergibt keinen Namen. |
| 195 | expect(name).not.toBeNull(); |
| 196 | expect(ansicht.wurzel.contains(name)).toBe(true); |
| 197 | expect((name!.textContent ?? '').trim()).not.toBe(''); |
| 198 | expect(name!.textContent).toContain('Zeichenfläche'); |
| 199 | } finally { |
| 200 | ansicht.aufraeumen(); |
| 201 | } |
| 202 | }); |
| 203 | |
| 204 | it('beschreibt beim Betreten, womit sich die Fläche bedienen lässt', () => { |
| 205 | const ansicht = baueAnsicht(); |
| 206 | try { |
| 207 | const hinweisId = ansicht.leinwand.getAttribute('aria-describedby'); |
| 208 | expect(hinweisId).not.toBeNull(); |
| 209 | const hinweis = document.getElementById(hinweisId!); |
| 210 | expect(hinweis).not.toBeNull(); |
| 211 | const text = hinweis!.textContent ?? ''; |
| 212 | expect(text).toContain('Pfeiltasten'); |
| 213 | expect(text).toContain('Leertaste'); |
| 214 | } finally { |
| 215 | ansicht.aufraeumen(); |
| 216 | } |
| 217 | }); |
| 218 | }); |
| 219 | |
| 220 | describe('Ein Tastendruck wirkt nur auf der fokussierten Zeichenfläche', () => { |
| 221 | /** Dieselbe Folge in beiden Fällen: zwei Punkte setzen und abschließen. */ |
| 222 | function zeichneMitTasten(): void { |
| 223 | tippe('ArrowDown', {}, 2); |
| 224 | tippe(' '); |
| 225 | tippe('ArrowUp', {}, 4); |
| 226 | tippe(' '); |
| 227 | tippe('Enter'); |
| 228 | } |
| 229 | |
| 230 | it('zeichnet nichts, solange der Seitenkörper den Fokus hat', () => { |
| 231 | const ansicht = baueAnsicht(); |
| 232 | try { |
| 233 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 234 | // Kein focus() auf die Flaeche: Der Fokus liegt auf dem Seitenkoerper, |
| 235 | // und dorthin stellt der Browser die Taste zu. |
| 236 | expect(document.activeElement).toBe(document.body); |
| 237 | |
| 238 | zeichneMitTasten(); |
| 239 | |
| 240 | expect(linien(ansicht)).toHaveLength(0); |
| 241 | // Auch der Zeiger hat sich nicht geruehrt - die Live-Region steht noch |
| 242 | // auf ihrem Ruhetext. |
| 243 | expect(ansage(ansicht)).toContain('noch nicht gesetzt'); |
| 244 | } finally { |
| 245 | ansicht.aufraeumen(); |
| 246 | } |
| 247 | }); |
| 248 | |
| 249 | it('zeichnet, sobald die Fläche den Fokus hat', () => { |
| 250 | const ansicht = baueAnsicht(); |
| 251 | try { |
| 252 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 253 | ansicht.leinwand.focus(); |
| 254 | |
| 255 | zeichneMitTasten(); |
| 256 | |
| 257 | expect(linien(ansicht)).toHaveLength(1); |
| 258 | expect(linien(ansicht)[0]?.punkte).toEqual([ |
| 259 | { x: 160, y: 170 }, |
| 260 | { x: 160, y: 150 }, |
| 261 | ]); |
| 262 | } finally { |
| 263 | ansicht.aufraeumen(); |
| 264 | } |
| 265 | }); |
| 266 | |
| 267 | it('hört wieder auf zu wirken, wenn die Fläche den Fokus abgibt', () => { |
| 268 | const ansicht = baueAnsicht(); |
| 269 | try { |
| 270 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 271 | ansicht.leinwand.focus(); |
| 272 | tippe(' '); |
| 273 | expect(ansage(ansicht)).toContain('Erster Punkt gesetzt'); |
| 274 | |
| 275 | ansicht.leinwand.blur(); |
| 276 | expect(document.activeElement).toBe(document.body); |
| 277 | tippe('ArrowRight', {}, 4); |
| 278 | tippe(' '); |
| 279 | tippe('Enter'); |
| 280 | |
| 281 | // Die Linie ist bei einem Punkt stehen geblieben und wurde nie |
| 282 | // abgeschlossen. |
| 283 | expect(linien(ansicht)).toHaveLength(0); |
| 284 | expect(ansage(ansicht)).toContain('Erster Punkt gesetzt'); |
| 285 | } finally { |
| 286 | ansicht.aufraeumen(); |
| 287 | } |
| 288 | }); |
| 289 | }); |
| 290 | |
| 291 | describe('Tastenbelegung der Zeichenfläche', () => { |
| 292 | /** |
| 293 | * Fuehrt die Tasten auf der fokussierten Flaeche aus, setzt dort einen Punkt |
| 294 | * und gibt dessen Lage IM BILD zurueck. |
| 295 | * |
| 296 | * Der zweite Punkt und der Abschluss dienen nur dazu, die Linie ins Projekt |
| 297 | * zu bringen - gefragt ist, wo der erste liegt. |
| 298 | */ |
| 299 | function ersterPunktNach(...tasten: readonly string[]): Punkt | undefined { |
| 300 | const ansicht = baueAnsicht(); |
| 301 | try { |
| 302 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 303 | ansicht.leinwand.focus(); |
| 304 | for (const taste of tasten) tippe(taste); |
| 305 | tippe(' '); |
| 306 | tippe('ArrowDown'); |
| 307 | tippe(' '); |
| 308 | tippe('Enter'); |
| 309 | return linien(ansicht)[0]?.punkte[0]; |
| 310 | } finally { |
| 311 | ansicht.aufraeumen(); |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | it('bewegt den Zeiger mit den vier Pfeiltasten in ihre Richtung', () => { |
| 316 | // Fuenf Bildpunkte je Anschlag, ausgehend von der Mitte. |
| 317 | expect(ersterPunktNach('ArrowLeft')).toEqual({ x: 155, y: 160 }); |
| 318 | expect(ersterPunktNach('ArrowRight')).toEqual({ x: 165, y: 160 }); |
| 319 | expect(ersterPunktNach('ArrowUp')).toEqual({ x: 160, y: 155 }); |
| 320 | expect(ersterPunktNach('ArrowDown')).toEqual({ x: 160, y: 165 }); |
| 321 | }); |
| 322 | |
| 323 | it('fängt Pfeiltasten und Leertaste ab, damit die Seite nicht wegrollt', () => { |
| 324 | const ansicht = baueAnsicht(); |
| 325 | try { |
| 326 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 327 | ansicht.leinwand.focus(); |
| 328 | expect(tippe('ArrowDown')).toBe(true); |
| 329 | expect(tippe(' ')).toBe(true); |
| 330 | } finally { |
| 331 | ansicht.aufraeumen(); |
| 332 | } |
| 333 | }); |
| 334 | |
| 335 | it('setzt mit der Leertaste einen Punkt und schließt mit der Eingabetaste ab', () => { |
| 336 | const ansicht = baueAnsicht(); |
| 337 | try { |
| 338 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 339 | ansicht.leinwand.focus(); |
| 340 | |
| 341 | tippe(' '); |
| 342 | expect(ansage(ansicht)).toContain('Erster Punkt gesetzt'); |
| 343 | // Ein Punkt ist noch keine Linie: Erst der Abschluss traegt sie ein. |
| 344 | expect(linien(ansicht)).toHaveLength(0); |
| 345 | |
| 346 | tippe('ArrowRight', {}, 4); |
| 347 | tippe(' '); |
| 348 | expect(ansage(ansicht)).toContain('Punkt 2 gesetzt'); |
| 349 | expect(linien(ansicht)).toHaveLength(0); |
| 350 | |
| 351 | tippe('Enter'); |
| 352 | expect(linien(ansicht)).toHaveLength(1); |
| 353 | expect(linien(ansicht)[0]?.punkte).toEqual([MITTE, { x: 180, y: 160 }]); |
| 354 | } finally { |
| 355 | ansicht.aufraeumen(); |
| 356 | } |
| 357 | }); |
| 358 | |
| 359 | it('bricht mit Esc ab und lässt nichts von der Zeichnung stehen', () => { |
| 360 | const ansicht = baueAnsicht(); |
| 361 | try { |
| 362 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 363 | ansicht.leinwand.focus(); |
| 364 | tippe(' '); |
| 365 | tippe('ArrowRight', {}, 4); |
| 366 | tippe(' '); |
| 367 | |
| 368 | tippe('Escape'); |
| 369 | expect(ansage(ansicht)).toContain('Zeichnen abgebrochen'); |
| 370 | |
| 371 | // Das Werkzeug steht wieder auf "Ansehen" - und zwar ablesbar, nicht nur |
| 372 | // an der Farbe des Knopfes. |
| 373 | expect(werkzeugKnopf(ansicht, 'Ansehen').getAttribute('aria-pressed')).toBe('true'); |
| 374 | expect(werkzeugKnopf(ansicht, 'Fahrlinie').getAttribute('aria-pressed')).toBe('false'); |
| 375 | |
| 376 | // Der Entwurf ist fort: Die Eingabetaste schliesst nichts mehr ab. |
| 377 | tippe('Enter'); |
| 378 | expect(linien(ansicht)).toHaveLength(0); |
| 379 | } finally { |
| 380 | ansicht.aufraeumen(); |
| 381 | } |
| 382 | }); |
| 383 | |
| 384 | it('nimmt mit der Rückschritttaste den letzten Punkt zurück', () => { |
| 385 | const ansicht = baueAnsicht(); |
| 386 | try { |
| 387 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 388 | ansicht.leinwand.focus(); |
| 389 | tippe(' '); |
| 390 | tippe('ArrowRight', {}, 4); |
| 391 | tippe(' '); |
| 392 | |
| 393 | tippe('Backspace'); |
| 394 | expect(ansage(ansicht)).toContain('Letzten Punkt zurückgenommen'); |
| 395 | expect(ansage(ansicht)).toContain('1 Punkt(e) verbleiben'); |
| 396 | |
| 397 | // Nachweis, dass wirklich nur noch ein Punkt steht: Der Abschluss |
| 398 | // verweigert sich, eine Fahrlinie braucht zwei. |
| 399 | tippe('Enter'); |
| 400 | expect(linien(ansicht)).toHaveLength(0); |
| 401 | } finally { |
| 402 | ansicht.aufraeumen(); |
| 403 | } |
| 404 | }); |
| 405 | |
| 406 | it('nimmt mit „z“ zurück, mit Strg+z dagegen nicht', () => { |
| 407 | // Strg+Z gehoert dem globalen Rueckgaengig. Wer beim Zeichnen aus |
| 408 | // Gewohnheit danach greift, darf nicht zugleich einen Punkt verlieren. |
| 409 | const ansicht = baueAnsicht(); |
| 410 | try { |
| 411 | waehleWerkzeug(ansicht, 'Fahrlinie'); |
| 412 | ansicht.leinwand.focus(); |
| 413 | tippe(' '); |
| 414 | tippe('ArrowRight', {}, 4); |
| 415 | tippe(' '); |
| 416 | |
| 417 | tippe('z', { ctrlKey: true }); |
| 418 | expect(ansage(ansicht)).toContain('Punkt 2 gesetzt'); |
| 419 | |
| 420 | tippe('z'); |
| 421 | expect(ansage(ansicht)).toContain('Letzten Punkt zurückgenommen'); |
| 422 | } finally { |
| 423 | ansicht.aufraeumen(); |
| 424 | } |
| 425 | }); |
| 426 | |
| 427 | it('vergrößert und verkleinert mit Plus und Minus und nennt die neue Schrittweite', () => { |
| 428 | const ansicht = baueAnsicht(); |
| 429 | try { |
| 430 | ansicht.leinwand.focus(); |
| 431 | |
| 432 | tippe('+'); |
| 433 | expect(ansage(ansicht)).toContain('Ansicht vergrößert'); |
| 434 | // Ein Schritt sind 5 Bildschirmpunkte; bei 1,25-fachem Zoom also 4 |
| 435 | // Bildpunkte und damit 0,8 m am Boden. |
| 436 | expect(ansage(ansicht)).toContain('0,8 m'); |
| 437 | |
| 438 | tippe('-'); |
| 439 | expect(ansage(ansicht)).toContain('Ansicht verkleinert'); |
| 440 | } finally { |
| 441 | ansicht.aufraeumen(); |
| 442 | } |
| 443 | }); |
| 444 | |
| 445 | it('lässt den Zeiger beim Vergrößern stehen, wo er stand', () => { |
| 446 | // Die Zusage der Tastenhilfe: „vergrößern / verkleinern – der Zeiger bleibt |
| 447 | // stehen". Wanderte er mit, finge das Anfahren des Ziels von vorn an. |
| 448 | expect(ersterPunktNach('+', '+')).toEqual(MITTE); |
| 449 | expect(ersterPunktNach('-')).toEqual(MITTE); |
| 450 | }); |
| 451 | |
| 452 | it('sagt bei „0“, dass ohne Bild nichts einzupassen ist', () => { |
| 453 | const ansicht = baueAnsicht(); |
| 454 | try { |
| 455 | ansicht.leinwand.focus(); |
| 456 | tippe('0'); |
| 457 | expect(ansage(ansicht)).toContain('Einpassen nicht möglich'); |
| 458 | } finally { |
| 459 | ansicht.aufraeumen(); |
| 460 | } |
| 461 | }); |
| 462 | |
| 463 | it('verschiebt die Sicht mit Bild auf, Bild ab, Pos 1 und Ende', () => { |
| 464 | // Ein Viertel der Zeichenflaeche je Anschlag - bei 320 Bildpunkten also 80. |
| 465 | // Der Zeiger behaelt seine Stelle auf dem Schirm und wandert im Bild mit; |
| 466 | // gemessen wird das an der Stelle, an der der Punkt dann landet. |
| 467 | expect(ersterPunktNach('ArrowDown', 'PageUp')).toEqual({ x: 160, y: 85 }); |
| 468 | expect(ersterPunktNach('ArrowDown', 'PageDown')).toEqual({ x: 160, y: 245 }); |
| 469 | expect(ersterPunktNach('ArrowDown', 'Home')).toEqual({ x: 80, y: 165 }); |
| 470 | expect(ersterPunktNach('ArrowDown', 'End')).toEqual({ x: 240, y: 165 }); |
| 471 | }); |
| 472 | }); |