// Die Testknoepfe sind das einzige Werkzeug, mit dem sich das Overlay // ausserhalb eines Streams pruefen laesst. Geht hier etwas kaputt, merkt man // es ausgerechnet dann nicht, wenn man es am noetigsten braucht. import { test } from 'node:test'; import assert from 'node:assert/strict'; import { TEST_KNOEPFE, testEvent, type TestArt } from './test-events.ts'; test('zu jedem Knopf gibt es ein Ereignis', () => { // Ein Knopf ohne Ereignis tut beim Druecken nichts und sieht dabei aus wie // ein kaputtes Overlay. for (const k of TEST_KNOEPFE) { if (k.art === 'chat-loeschen') continue; // braucht eine Nachricht, siehe unten assert.ok(testEvent(k.art), `${k.art} liefert kein Ereignis`); } }); test('jeder Knopf kommt nur einmal vor', () => { const arten = TEST_KNOEPFE.map((k) => k.art); assert.equal(new Set(arten).size, arten.length); }); test('die Einblendungen tragen die Felder, die die Seite anzeigt', () => { const raid = testEvent('raid') as { kind: string; type: string; amount: number }; assert.equal(raid.kind, 'alert'); assert.equal(raid.type, 'raid'); assert.ok(raid.amount > 0, 'ohne Zuschauerzahl steht "mit undefined Zuschauern" im Bild'); const gift = testEvent('subgift') as { recipient?: string }; assert.ok(gift.recipient, 'ohne Beschenkten fehlt die zweite Zeile'); const resub = testEvent('resub') as { months?: number }; assert.ok(resub.months && resub.months > 0); }); test('"letzte löschen" braucht eine Nachricht und trifft genau die', () => { assert.equal(testEvent('chat-loeschen'), null, 'ohne Chat gibt es nichts zu loeschen'); assert.deepEqual(testEvent('chat-loeschen', 'abc'), { kind: 'clear', messageId: 'abc' }); }); test('"Chat leeren" loescht alles, nicht nur eine Nachricht', () => { assert.deepEqual(testEvent('chat-leeren'), { kind: 'clear' }); }); test('Chatnachrichten haben eine eigene ID', () => { // Zwei Nachrichten mit derselben ID wuerden sich beim Loeschen gegenseitig // mitnehmen. const a = testEvent('chat') as { id: string }; const b = testEvent('chat') as { id: string }; assert.notEqual(a.id, b.id); }); test('das Emote im Test ist im Text auch enthalten', () => { // Sonst wird nichts ersetzt und der Knopf prueft die Emote-Anzeige nicht. const ev = testEvent('chat-emote') as { text: string; emotes: Array<{ name: string }> }; assert.equal(ev.emotes.length, 1); assert.ok(ev.text.includes(ev.emotes[0]!.name)); }); test('gesperrt ist nur, was einen falschen Stand setzt - nicht der Weg zurueck', () => { // Eine Einblendung blitzt auf und ist weg. Ein erfundener Stream-Zustand // bleibt in der Statusleiste stehen, bis die naechste Abfrage ihn // geraderueckt - und das dauert bis zu einer Minute. // // "Zuruecksetzen" darf dabei nie gesperrt sein: sonst kommt man aus einem // erfundenen Zustand ohne Neustart nicht mehr heraus, und auf einem Hub ohne // Broadcaster-Token raeumt ueberhaupt niemand hinterher auf. const gesperrt = TEST_KNOEPFE.filter((k) => k.nurOffline).map((k) => k.art); assert.deepEqual(gesperrt, ['live-an']); const zurueck = TEST_KNOEPFE.find((k) => k.art === 'live-aus'); assert.equal(zurueck?.nurOffline, undefined, 'der Weg zurueck bleibt immer offen'); }); test('der Stand-Knopf setzt einen anzeigbaren Stream', () => { const an = testEvent('live-an') as { kind: string; live: boolean; title: string; startedAt: string | null }; assert.equal(an.kind, 'stream'); assert.equal(an.live, true); assert.ok(an.title.length > 0); assert.ok(an.startedAt && Date.parse(an.startedAt) < Date.now(), 'Laufzeit muss positiv sein'); const aus = testEvent('live-aus') as { live: boolean; startedAt: string | null }; assert.equal(aus.live, false); assert.equal(aus.startedAt, null); }); test('unbekannte Knoepfe liefern nichts, statt etwas zu erfinden', () => { assert.equal(testEvent('gibtsnicht' as TestArt), null); }); // ---------------------------------------------------------------- Kanalpunkte test('eine Belohnung wird nie als Abo eingeblendet', () => { // Genau das stand hier einmal: die Konfetti-Belohnung hat die Abo- // Einblendung wiederverwendet und "Neues Abo" ins Bild geschrieben. Eine // Falschmeldung im Stream, die in der Aufzeichnung bleibt. const e = testEvent('reward') as { kind: string; type: string; reward?: string }; assert.equal(e.kind, 'alert'); assert.equal(e.type, 'reward'); assert.notEqual(e.type, 'sub'); assert.ok(e.reward, 'ohne Titel weiss niemand, welche Belohnung gemeint ist'); }); test('Konfetti ist ein eigenes Ereignis, keine Nebenwirkung einer Einblendung', () => { // So muss die Effektseite nicht wissen, welche Belohnung was ausloest. const e = testEvent('reward-konfetti') as { kind: string; staerke: number }; assert.equal(e.kind, 'konfetti'); assert.ok(e.staerke > 0); }); test('der Vorlese-Knopf schickt Text, den die Seite auch sprechen kann', () => { const e = testEvent('tts') as { kind: string; text: string; name: string }; assert.equal(e.kind, 'tts'); assert.ok(e.text.length > 0); assert.ok(e.name.length > 0); });