import { describe, expect, it, vi } from 'vitest'; import type { MenuItemConstructorOptions } from 'electron'; /* * `electron/menu.ts` bindet `Menu`, `app` und `shell` ein. Ausserhalb von * Electron gibt es die nicht; die Vorlage selbst braucht sie auch nicht - sie * ist reiner Inhalt. Der Ersatz erlaubt, sie zu pruefen, ohne ein Fenster zu * oeffnen. */ vi.mock('electron', () => ({ Menu: { buildFromTemplate: (vorlage: unknown) => vorlage }, app: { getVersion: () => '5.6.0' }, shell: { openExternal: () => undefined }, })); const { menuevorlage, schriftgroesseMenueId } = await import('../../electron/menu'); const { SCHRIFT_STANDARD, SCHRIFT_STUFEN } = await import('../../electron/schriftgroesse'); interface Aufzeichnung { gesetzt: number[]; schritte: number[]; } function vorlageFuer(aktuell: number): { menue: MenuItemConstructorOptions[]; auf: Aufzeichnung; } { const auf: Aufzeichnung = { gesetzt: [], schritte: [] }; const menue = menuevorlage( () => undefined, false, { aktuell: () => aktuell, setze: (prozent) => auf.gesetzt.push(prozent), schritt: (richtung) => auf.schritte.push(richtung), }, '5.6.0', () => undefined, () => undefined, ); return { menue, auf }; } function untermenue( eintraege: MenuItemConstructorOptions[] | undefined, beschriftung: string, ): MenuItemConstructorOptions[] { const eintrag = (eintraege ?? []).find((e) => e.label === beschriftung); expect(eintrag, `Menüeintrag "${beschriftung}" fehlt`).toBeDefined(); const inhalt = eintrag?.submenu; expect(Array.isArray(inhalt), `"${beschriftung}" hat kein Untermenü`).toBe(true); return inhalt as MenuItemConstructorOptions[]; } function eintrag( eintraege: MenuItemConstructorOptions[], beschriftung: string, ): MenuItemConstructorOptions { const gefunden = eintraege.find((e) => e.label === beschriftung); expect(gefunden, `Menüeintrag "${beschriftung}" fehlt`).toBeDefined(); return gefunden as MenuItemConstructorOptions; } /** * Befund L11 (EN 301 549 Kap. 11.7). * * Im Altbestand bestand "Ansicht" aus den drei Chromium-Rollen `resetZoom`, * `zoomIn`, `zoomOut`. Sie kannten keine benannten Stufen, zeigten die * eingestellte Groesse nicht an und sicherten nichts. */ describe('Ansichtsmenü: Schriftgröße', () => { it('bietet eine Auswahlliste "Schriftgröße" mit allen Stufen', () => { const { menue } = vorlageFuer(SCHRIFT_STANDARD); const stufen = untermenue(untermenue(menue, '&Ansicht'), 'Schriftgröße'); expect(stufen).toHaveLength(SCHRIFT_STUFEN.length); for (const stufe of stufen) { expect(stufe.type).toBe('radio'); expect(stufe.label).toMatch(/^\d+ %/); } }); it('markiert genau die eingestellte Stufe', () => { const { menue } = vorlageFuer(175); const stufen = untermenue(untermenue(menue, '&Ansicht'), 'Schriftgröße'); const markiert = stufen.filter((s) => s.checked === true); expect(markiert).toHaveLength(1); expect(markiert[0]?.id).toBe(schriftgroesseMenueId(175)); expect(markiert[0]?.label).toBe('175 %'); }); it('gibt jeder Stufe die Kennung, unter der der Hauptprozess sie nachführt', () => { const { menue } = vorlageFuer(SCHRIFT_STANDARD); const stufen = untermenue(untermenue(menue, '&Ansicht'), 'Schriftgröße'); expect(stufen.map((s) => s.id)).toEqual(SCHRIFT_STUFEN.map((p) => schriftgroesseMenueId(p))); }); it('meldet beim Anklicken genau diese Stufe', () => { const { menue, auf } = vorlageFuer(SCHRIFT_STANDARD); const stufen = untermenue(untermenue(menue, '&Ansicht'), 'Schriftgröße'); const zweihundert = stufen.find((s) => s.id === schriftgroesseMenueId(200)); zweihundert?.click?.(undefined as never, undefined, undefined as never); expect(auf.gesetzt).toEqual([200]); }); it('lässt "Vergrößern" und "Verkleinern" durch dieselbe Stufenliste laufen', () => { const { menue, auf } = vorlageFuer(150); const ansicht = untermenue(menue, '&Ansicht'); eintrag(ansicht, 'Vergrößern').click?.(undefined as never, undefined, undefined as never); eintrag(ansicht, 'Verkleinern').click?.(undefined as never, undefined, undefined as never); expect(auf.schritte).toEqual([1, -1]); expect(auf.gesetzt).toEqual([]); }); it('behält die gewohnten Tastenkürzel', () => { const ansicht = untermenue(vorlageFuer(SCHRIFT_STANDARD).menue, '&Ansicht'); expect(eintrag(ansicht, 'Vergrößern').accelerator).toBe('CmdOrCtrl+Plus'); expect(eintrag(ansicht, 'Verkleinern').accelerator).toBe('CmdOrCtrl+-'); expect(eintrag(ansicht, 'Standardgröße').accelerator).toBe('CmdOrCtrl+0'); }); it('behält auch Strg+= zum Vergrößern, verborgen im Menü', () => { /* * Die abgeloeste Chromium-Rolle `zoomIn` brachte BEIDE Kuerzel mit * (Befund B6). Auf einer Belegung, bei der "+" die Umschalttaste verlangt - * US, UK, viele Notebooktastaturen -, ist Strg+= der Weg, der mit einer * Hand geht; mit dem eigenen Eintrag entfiel er. Electron nimmt je Eintrag * genau einen Accelerator, also braucht es einen zweiten - und der gehoert * nicht ins aufgeklappte Menue, wo er ein zweites "Vergroessern" ohne * eigenen Zweck waere. */ const { menue, auf } = vorlageFuer(150); const ansicht = untermenue(menue, '&Ansicht'); const beide = ansicht.filter((e) => e.label === 'Vergrößern'); expect(beide.map((e) => e.accelerator)).toEqual(['CmdOrCtrl+Plus', 'CmdOrCtrl+=']); expect(beide[0]!.visible).toBeUndefined(); expect(beide[1]!.visible).toBe(false); // Und er fuehrt durch dieselbe Stufenliste, nicht an ihr vorbei. beide[1]!.click?.(undefined as never, undefined, undefined as never); expect(auf.schritte).toEqual([1]); }); it('setzt "Standardgröße" auf die Vorgabestufe zurück', () => { const { menue, auf } = vorlageFuer(200); const ansicht = untermenue(menue, '&Ansicht'); eintrag(ansicht, 'Standardgröße').click?.(undefined as never, undefined, undefined as never); expect(auf.gesetzt).toEqual([SCHRIFT_STANDARD]); }); it('überlässt die Vergrößerung keiner Chromium-Rolle mehr', () => { // Eine Rolle aendert den Vergroesserungsgrad an der eigenen Buchfuehrung // vorbei: Die Auswahlmarke zeigte danach auf eine Stufe, die nicht gilt, // und gesichert wuerde die Aenderung auch nicht. const ansicht = untermenue(vorlageFuer(SCHRIFT_STANDARD).menue, '&Ansicht'); const rollen = ansicht.map((e) => e.role); expect(rollen).not.toContain('zoomIn'); expect(rollen).not.toContain('zoomOut'); expect(rollen).not.toContain('resetZoom'); }); }); /** * Hinweis ohne Bezug auf ein Erfolgskriterium: "Rückgängig" bezeichnete im Menü * etwas anderes als in der Kopfzeile. */ describe('Bearbeitenmenü: Rückgängig', () => { it('benennt die Chromium-Rollen als Texteingabe', () => { const bearbeiten = untermenue(vorlageFuer(SCHRIFT_STANDARD).menue, '&Bearbeiten'); const beschriftungen = bearbeiten.map((e) => e.label); expect(beschriftungen).not.toContain('Rückgängig'); expect(beschriftungen).not.toContain('Wiederherstellen'); expect(bearbeiten.find((e) => e.role === 'undo')?.label).toBe('Texteingabe rückgängig'); expect(bearbeiten.find((e) => e.role === 'redo')?.label).toBe('Texteingabe wiederherstellen'); }); });