// @vitest-environment node // Reiner Main-Prozess-Test; jsdom wird hier nicht gebraucht. import { EventEmitter } from 'node:events'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { KANAL_ENERGIESPAREN_ENDE } from '../src/shared/ipc'; /** * Die Uhr der Prüfungssimulation im Energiesparmodus. * * Bis 0.22.0 hing die gesamte Zeitrechnung an der Wanduhr: `endeMs` und * `verbrauchtMs` leiten sich aus `startMs` ab, und niemand fragte, ob der * Rechner zwischendurch schlief. Wer mitten in einem 120-Minuten-Bogen den * Laptop zuklappte, verbrauchte die Mittagspause als Bearbeitungszeit – der * Bogen war beim Aufwachen abgelaufen, und der Herzschlag schrieb die * aufgeblähte Dauer sogar in die Datenbank. * * Geprüft wird hier die Meldeseite: dass die Schlafdauer überhaupt und * richtig gemessen an die Fenster geht. Dass die Oberfläche daraufhin ihren * Zeitursprung verschiebt, prüft `renderer-pruefung.test.tsx`. */ /** Nachbildung von `powerMonitor` – ein Ereignissender genügt. */ const powerMonitor = new EventEmitter(); /** Was die Fenster zu sehen bekommen. */ let gesendet: { kanal: string; nutzlast: unknown }[] = []; /** Ob das Fenster sich für zerstört hält – der Randfall beim Beenden. */ let zerstoert = false; const fenster = { isDestroyed: (): boolean => zerstoert, webContents: { send: (kanal: string, nutzlast: unknown): void => { gesendet.push({ kanal, nutzlast }); }, }, }; vi.mock('electron', () => ({ powerMonitor, BrowserWindow: { getAllWindows: (): unknown[] => [fenster], }, })); const { energieUeberwachen, energieVergessen } = await import('../src/main/energie'); /** Führt einen Schlaf von `dauerMs` vor, ohne wirklich zu warten. */ function schlafen(dauerMs: number): void { const beginn = Date.UTC(2026, 7, 29, 10, 0, 0); const uhr = vi.spyOn(Date, 'now'); uhr.mockReturnValue(beginn); powerMonitor.emit('suspend'); uhr.mockReturnValue(beginn + dauerMs); powerMonitor.emit('resume'); uhr.mockRestore(); } beforeEach(() => { gesendet = []; zerstoert = false; energieUeberwachen(); }); afterEach(() => { energieVergessen(); vi.restoreAllMocks(); }); describe('Energiesparmodus melden', () => { it('meldet die Schlafdauer nach dem Aufwachen', () => { schlafen(30 * 60 * 1000); expect(gesendet).toEqual([{ kanal: KANAL_ENERGIESPAREN_ENDE, nutzlast: 30 * 60 * 1000 }]); }); it('schweigt bei einem Schlaf von Bruchteilen einer Sekunde', () => { /* Zwischen `suspend` und `resume` liegt selbst bei einem sofort abgebrochenen Standby ein Rest an Zeit. Den gutzuschreiben verschöbe den Zeitursprung ohne Anlass und ließe die Ansage grundlos erscheinen. */ schlafen(200); expect(gesendet).toEqual([]); }); it('meldet nichts bei einem Aufwachen ohne vorheriges Einschlafen', () => { /* `resume` kann ohne passendes `suspend` eintreffen – etwa wenn die Anwendung erst währenddessen gestartet wurde. Eine Gutschrift „seit Anbeginn“ wäre der schlimmste Fall: Sie schöbe den Zeitursprung um eine willkürliche Menge. */ powerMonitor.emit('resume'); expect(gesendet).toEqual([]); }); it('schreibt nicht in ein zerstörtes Fenster', () => { zerstoert = true; schlafen(30 * 60 * 1000); expect(gesendet).toEqual([]); }); it('meldet auch bei einem Uhrsprung zurück nichts Negatives', () => { /* Stellt jemand die Systemuhr während des Schlafs zurück, ergäbe die Differenz eine negative Dauer. Gutgeschrieben verkürzte sie die Restzeit – das Gegenteil der Absicht. */ const beginn = Date.UTC(2026, 7, 29, 10, 0, 0); const uhr = vi.spyOn(Date, 'now'); uhr.mockReturnValue(beginn); powerMonitor.emit('suspend'); uhr.mockReturnValue(beginn - 60 * 60 * 1000); powerMonitor.emit('resume'); uhr.mockRestore(); expect(gesendet).toEqual([]); }); it('meldet zwei Pausen einzeln', () => { schlafen(10 * 60 * 1000); schlafen(5 * 60 * 1000); expect(gesendet.map((eintrag) => eintrag.nutzlast)).toEqual([10 * 60 * 1000, 5 * 60 * 1000]); }); it('hängt die Zuhörer nur einmal, auch bei mehrfachem Anmelden', () => { /* Ein zweites Anmelden meldete jede Pause doppelt – und die Oberfläche schriebe den Zeitursprung zweimal vor. */ energieUeberwachen(); energieUeberwachen(); schlafen(30 * 60 * 1000); expect(gesendet).toHaveLength(1); }); it('schweigt nach dem Abmelden', () => { energieVergessen(); schlafen(30 * 60 * 1000); expect(gesendet).toEqual([]); }); it('lässt keine Zuhörer zurück', () => { energieVergessen(); expect(powerMonitor.listenerCount('suspend')).toBe(0); expect(powerMonitor.listenerCount('resume')).toBe(0); }); });