import { test } from 'node:test' import assert from 'node:assert/strict' import { installFakeDom } from './support/fakeDom.js' import { balkenMasse, overlayPasst, schriftStufe, sichtbarMachen, anzeigeText, baueReiter } from '../public/neu/reiter.js' test('Zahlenfunktionen: Balken, Overlay-Rückfall, Schriftstufe, Sichtbarmachen (C2)', () => { const basis = [{ links: 0, breite: 90 }, { links: 90, breite: 100 }, { links: 190, breite: 60 }, { links: 250, breite: 90 }] assert.deepEqual(balkenMasse({ basis, aktiv: 1, gesamtBreite: 340 }), { links: 90, breite: 100, overlayVersatz: -90, overlayBreite: 340 }) assert.deepEqual([overlayPasst([90, 100], [90, 100.4]), overlayPasst([90, 100], [90, 101])], [true, false]) assert.deepEqual([schriftStufe({ inhaltsBreite: 342, verfuegbar: 342 }), schriftStufe({ inhaltsBreite: 343, verfuegbar: 342 })], [15, 14]) const s = (scrollLinks, itemLinks) => sichtbarMachen({ scrollLinks, sichtbarBreite: 300, itemLinks, itemBreite: 80 }) assert.deepEqual([s(0, 420), s(200, 120), s(100, 150), s(50, 5)], [216, 104, 100, 0]) }) test('anzeigeText: Zahl, "!" und Deckel 99+ (Review Focus 3)', () => { assert.deepEqual([0, 7, 99, 100, 1500].map((zahl) => anzeigeText({ zahl })), ['', '7', '99', '99+', '99+']) assert.deepEqual([anzeigeText({ zahl: 3, ausruf: true }), anzeigeText({ zahl: 0, ausruf: true })], ['!', '!']) }) test('baueReiter: Rollen, Auswahl, Kurzlabel, Pfeiltasten, Anzeige mit Beschriftung', async () => { const fx = installFakeDom() try { const gew = [] const eintraege = [{ key: 'termine', label: 'Termine' }, { key: 'team', label: 'Team & Plätze', kurz: 'Team' }, { key: 'chat', label: 'Chat', einheit: 'ungelesen' }] const r = baueReiter({ eintraege, aktiv: 'termine', art: 'balken', ariaLabel: 'Bereiche', bei: (k) => gew.push(k), warte: (fn) => fn() }) const tabs = fx.alle(r, '[role=tab]') assert.deepEqual(tabs.map((t) => t.textContent), ['Termine', 'Team', 'Chat']); assert.equal(tabs[1].getAttribute('aria-label'), 'Team & Plätze') assert.deepEqual(tabs.map((t) => t.getAttribute('aria-selected')), ['true', 'false', 'false']); assert.equal(tabs[0].getAttribute('aria-current'), 'page') assert.equal(fx.alle(r, '.kn-overlay [role=tab]').length, 0) await fx.klick(tabs[1]); await fx.sende(tabs[1], 'keydown', { key: 'ArrowRight' }); assert.deepEqual(gew, ['team', 'chat']) r.setzeAktiv('chat'); assert.equal(tabs[2].getAttribute('aria-selected'), 'true') r.setzeAnzeige('chat', { zahl: 2 }) assert.deepEqual([fx.finde(tabs[2], '.kn-zahl').textContent, tabs[2].getAttribute('aria-label')], ['2', 'Chat, 2 ungelesen']) r.setzeAnzeige('chat', { zahl: 0 }); assert.deepEqual([fx.finde(tabs[2], '.kn-zahl'), tabs[2].hasAttribute('aria-label')], [null, false]) } finally { fx.restore() } }) test('baueReiter: Randfälle (ohne aktiven Eintrag, Home/End/Umbruch, Unterstrich-Art, Spiegelung im Overlay, Zahl kehrt vor dem Entfernen zurück)', async () => { assert.deepEqual(balkenMasse({ basis: [{ links: 0, breite: 90 }], aktiv: -1, gesamtBreite: 340 }), { links: 0, breite: 0, overlayVersatz: 0, overlayBreite: 340 }) assert.ok(Object.is(balkenMasse({ basis: [{ links: 0, breite: 90 }], aktiv: 0, gesamtBreite: 340 }).overlayVersatz, 0)) const fx = installFakeDom() try { const gew = []; const spaeter = [] const eintraege = [{ key: 'a', label: 'A' }, { key: 'b', label: 'B', einheit: 'offen', ausrufText: 'Zahlung fehlgeschlagen' }, { key: 'c', label: 'C' }] const r = baueReiter({ eintraege, aktiv: 'zzz', art: 'balken', bei: (k) => gew.push(k), warte: (fn) => spaeter.push(fn) }) const tabs = fx.alle(r, '[role=tab]'); const kopien = fx.alle(r, '.kn-overlay .kn-t') assert.deepEqual([tabs.map((t) => t.getAttribute('aria-selected')), tabs.map((t) => t.getAttribute('tabindex'))], [['false', 'false', 'false'], ['0', '-1', '-1']]) // ohne Treffer bleibt der erste Reiter per Tab erreichbar assert.deepEqual([r.getAttribute('data-art'), fx.finde(r, '.kn-overlay').parentNode.getAttribute('aria-hidden'), kopien.map((k) => k.textContent)], ['balken', 'true', ['A', 'B', 'C']]) await fx.sende(tabs[0], 'keydown', { key: 'ArrowLeft' }); await fx.sende(tabs[2], 'keydown', { key: 'ArrowRight' }); await fx.sende(tabs[1], 'keydown', { key: 'Home' }); await fx.sende(tabs[1], 'keydown', { key: 'End' }); await fx.sende(tabs[1], 'keydown', { key: 'x' }) assert.deepEqual(gew, ['c', 'a', 'a', 'c']) r.setzeAnzeige('b', { zahl: 150 }); assert.deepEqual([fx.finde(tabs[1], '.kn-zahl').textContent, fx.finde(kopien[1], '.kn-zahl').textContent, tabs[1].getAttribute('aria-label')], ['99+', '99+', 'B, 99+ offen']) r.setzeAnzeige('b', { zahl: 5, ausruf: true }); assert.deepEqual([fx.finde(tabs[1], '.kn-zahl').textContent, tabs[1].getAttribute('aria-label')], ['!', 'B, Zahlung fehlgeschlagen']) r.setzeAnzeige('b', { zahl: 0 }); assert.ok(fx.finde(tabs[1], '.kn-zahl').classList.contains('kn-zahl--weg')); assert.equal(spaeter.length, 2) // erst nach 260 ms weg r.setzeAnzeige('b', { zahl: 3 }); spaeter.splice(0).forEach((fn) => fn()) // der alte Timer darf die neue Zahl nicht löschen assert.deepEqual([fx.alle(tabs[1], '.kn-zahl').map((z) => z.textContent), fx.alle(tabs[1], '.kn-zahl--weg').length], [['3'], 0]) const u = baueReiter({ eintraege, aktiv: 'b', ariaLabel: 'Funktionen', bei: () => {} }) assert.deepEqual([u.className, u.getAttribute('role'), u.getAttribute('aria-label'), fx.alle(u, '.kn-reiter-t').length, fx.alle(u, '.kn-overlay').length], ['kn-reiter', 'tablist', 'Funktionen', 3, 0]) } finally { fx.restore() } }) test('messen: Gleiter, Overlay-Gegenbewegung, Schriftstufe, Rückfall und Sichtbarmachen mit nachgestellter Geometrie', () => { const fx = installFakeDom() try { const eintraege = ['a', 'b', 'c', 'd', 'e', 'f'].map((key) => ({ key, label: key.toUpperCase() })) const r = baueReiter({ eintraege, aktiv: 'a', art: 'balken', bei: () => {} }) fx.document.body.append(r) const basis = fx.finde(r, '.kn-balken-basis'); const gleiter = fx.finde(r, '.kn-gleiter'); const overlay = fx.finde(r, '.kn-overlay') const tabs = fx.alle(r, '[role=tab]'); const kopien = fx.alle(overlay, '.kn-t') for (const el of [gleiter, overlay]) el.style = {} gleiter.offsetWidth = 0 r.clientWidth = 300; r.scrollLeft = 0; basis.scrollWidth = 600 r.getBoundingClientRect = () => ({ left: 10, width: 300 }); basis.getBoundingClientRect = () => ({ left: 10 - r.scrollLeft, width: 600 }) tabs.forEach((t, i) => { t.getBoundingClientRect = () => ({ left: 10 - r.scrollLeft + i * 100, width: 100 }) }) kopien.forEach((k) => { k.getBoundingClientRect = () => ({ left: 0, width: 100 }) }) r.messen() assert.deepEqual([r.classList.contains('kn-schrift-klein'), r.classList.contains('kn-fb')], [true, false]) // 600 > 300: Schrift 14 px, alle Wörter gleich breit: Overlay bleibt assert.deepEqual([gleiter.style.width, gleiter.style.transform, overlay.style.width, overlay.style.transform], ['100px', 'translateX(0px)', '600px', 'translateX(0px)']) r.setzeAktiv('c') assert.deepEqual([gleiter.style.transform, overlay.style.transform, gleiter.style.transition], ['translateX(200px)', 'translateX(-200px)', '']) r.setzeAktiv('f'); assert.equal(r.scrollLeft, 316) // 500 + 100 + 16 - 300 r.setzeAktiv('a'); assert.equal(r.scrollLeft, 0) basis.scrollWidth = 300; kopien[2].getBoundingClientRect = () => ({ left: 0, width: 103 }); r.messen() assert.deepEqual([r.classList.contains('kn-schrift-klein'), r.classList.contains('kn-fb')], [false, true]) // passt, aber das Overlay weicht ab: Rückfall r.setzeAktiv('zzz'); assert.equal(gleiter.style.width, '0px') } finally { fx.restore() } }) test('messen: die erste Platzierung springt, erst die folgenden Wechsel federn', () => { const fx = installFakeDom() try { const r = baueReiter({ eintraege: ['a', 'b'].map((key) => ({ key, label: key })), aktiv: 'a', art: 'balken', bei: () => {} }) fx.document.body.append(r) const gleiter = fx.finde(r, '.kn-gleiter'); const overlay = fx.finde(r, '.kn-overlay'); const basis = fx.finde(r, '.kn-balken-basis'); const tabs = fx.alle(r, '[role=tab]') const verlauf = [] gleiter.style = new Proxy({}, { set(ziel, name, wert) { if (name === 'transition') verlauf.push(wert); ziel[name] = wert; return true } }); overlay.style = {}; gleiter.offsetWidth = 0 r.clientWidth = 200; r.scrollLeft = 0; basis.scrollWidth = 200 r.getBoundingClientRect = () => ({ left: 0, width: 200 }); basis.getBoundingClientRect = () => ({ left: 0, width: 200 }) tabs.forEach((t, i) => { t.getBoundingClientRect = () => ({ left: i * 100, width: 100 }) }) fx.alle(overlay, '.kn-t').forEach((k) => { k.getBoundingClientRect = () => ({ left: 0, width: 100 }) }) r.setzeAktiv('b'); assert.deepEqual(verlauf, ['none', '']) // erste Platzierung: ohne Übergang r.setzeAktiv('a'); assert.deepEqual(verlauf, ['none', '']) // danach mit Übergang (die Feder des CSS läuft) assert.equal(gleiter.style.transform, 'translateX(0px)') } finally { fx.restore() } })