Files
Webklar-Kundenbereich/test/knHuelleLeiste.test.js
T

83 lines
7.3 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { installFakeDom } from './support/fakeDom.js'
import { h } from '../public/neu/dom.js'
import { HANDY_ABFRAGE, baueNavigation, tastaturZustand, leisteSichtbar, beobachteTastatur } from '../public/neu/leiste.js'
import { lies, handyTeil, HANDY } from './support/knHuelle.js'
test('Leiste: vier Wörter, Auswahl, Zähler mit Beschriftung, "!" und 99+, Etage sperrt die Hauptzeile', async () => {
const fx = installFakeDom()
try {
const haupt = h('div'); const reel = h('div', null, haupt); const menue = h('nav'); const gew = []
const n = baueNavigation({ haupt, menue, onWahl: (k) => gew.push(k) }); const tabs = fx.alle(haupt, '[role=tab]')
assert.deepEqual(tabs.map((t) => t.textContent), ['Website', 'Funktionen', 'Chat', 'Verträge'])
n.setzeAktiv('chat'); assert.deepEqual(tabs.map((t) => t.getAttribute('aria-selected')), ['false', 'false', 'true', 'false']); assert.equal(fx.finde(menue, '[aria-current=page]').textContent, 'Chat')
await fx.klick(tabs[3]); assert.deepEqual(gew, ['vertraege'])
n.setzeZaehler({ chat: 2, vertraege: { zahl: 1, ausruf: false } })
assert.deepEqual([tabs[2].getAttribute('aria-label'), tabs[3].getAttribute('aria-label'), fx.finde(menue, '.kn-nav-zahl').textContent], ['Chat, 2 ungelesen', 'Verträge, 1 offen', '2'])
n.setzeZaehler({ chat: 150, vertraege: { zahl: 5, ausruf: true } })
assert.deepEqual([fx.finde(tabs[2], '.kn-zahl').textContent, fx.finde(tabs[3], '.kn-zahl').textContent, tabs[3].getAttribute('aria-label')], ['99+', '!', 'Verträge, Zahlung fehlgeschlagen'])
n.setzeZaehler({ chat: 0, vertraege: { zahl: 0, ausruf: false } }); assert.equal(fx.finde(menue, '.kn-nav-zahl'), null)
assert.deepEqual(fx.alle(menue, 'button').map((b) => b.textContent), ['Website', 'Funktionen', 'Chat', 'Verträge'])
n.setzeEtage(true); assert.deepEqual([reel.getAttribute('data-etage'), haupt.hasAttribute('inert')], ['offen', true])
n.setzeEtage(false); assert.equal(haupt.hasAttribute('inert'), false)
} finally { fx.restore() }
})
test('tastaturZustand: öffnen unter voll-150, Android-Geste ohne Blur, Drehen, nur Textfelder am Touch (Review Focus 4)', () => {
let z = { voll: 844, breite: 390, offen: false, tippt: false }
z = tastaturZustand(z, { art: 'vv', hoehe: 560, breite: 390, textfeldAktiv: true, grob: true }); assert.deepEqual([z.offen, z.tippt, leisteSichtbar(z)], [true, true, false])
z = tastaturZustand(z, { art: 'vv', hoehe: 700, breite: 390 }); assert.deepEqual([z.offen, z.tippt, leisteSichtbar(z)], [false, false, true]) // 700 >= 694, ohne Blur
z = tastaturZustand(z, { art: 'vv', hoehe: 390, breite: 844 }); assert.deepEqual([z.voll, z.breite, z.offen], [390, 844, false])
assert.deepEqual([true, false].map((grob) => tastaturZustand(z, { art: 'fokus', textfeld: true, grob }).tippt), [true, false])
assert.equal(tastaturZustand({ ...z, tippt: true }, { art: 'unfokus', textfeldAktiv: false }).tippt, false)
})
test('huelle.css: Desktop ist Grundregel, Handy überschreibt (Abfrage wörtlich, 44 px, Safe-Area, Reel, Gleiter, Menü, Bewegung)', () => {
assert.equal(`@media ${HANDY_ABFRAGE}`, HANDY)
const css = lies('huelle.css'); const handy = handyTeil(css)
assert.match(css, /\.kn-t\s*\{(?=[^}]*min-height:\s*44px)(?=[^}]*font-size:\s*15px)(?=[^}]*font-weight:\s*600)/s)
assert.match(css, /\.kn-schrift-klein \.kn-t\s*\{[^}]*font-size:\s*14px/s)
assert.match(css, /\.kn-menue\s*\{(?=[^}]*width:\s*var\(--kn-sidebar-w\))(?=[^}]*border-right:\s*1px solid var\(--kn-line\))/s)
assert.match(handy, /\.kn-menue\s*\{[^}]*display:\s*none/s); assert.match(handy, /\.kn-leiste\s*\{[^}]*padding-bottom:\s*env\(safe-area-inset-bottom\)/s)
assert.match(css, /\[data-etage="offen"\][^{]*\{[^}]*translateY\(-56px\)/s)
assert.match(css, /\.kn-gleiter\s*\{(?=[^}]*top:\s*4px)(?=[^}]*height:\s*36px)(?=[^}]*border-radius:\s*6px)(?=[^}]*background:\s*var\(--kn-accent\))/s)
assert.match(css, /\.kn-tastatur \.kn-leiste[^{]*\{[^}]*(translateY\(110%\)|display:\s*none)/s)
assert.match(css, /@media \(prefers-reduced-motion: reduce\)[\s\S]*150ms/)
})
test('Menü und Leiste: Klick im Menü wählt, Aktiv und Zähler laufen in beiden Navigationen, unbekannter Bereich wählt nichts', async () => {
const fx = installFakeDom()
try {
const haupt = h('div'); const menue = h('nav'); const gew = []
const n = baueNavigation({ haupt, menue, onWahl: (k) => gew.push(k) })
const knoepfe = fx.alle(menue, 'button')
await fx.klick(knoepfe[1]); assert.deepEqual(gew, ['funktionen'])
assert.match(fx.text(menue), /WEBklar/); assert.equal(fx.alle(menue, 'svg, img').length, 0) // Marke als Text, keine Icons
n.setzeAktiv('vertraege'); assert.deepEqual(knoepfe.map((b) => b.hasAttribute('aria-current')), [false, false, false, true])
n.setzeAktiv('gibtesnicht'); assert.deepEqual([fx.alle(menue, '[aria-current=page]').length, fx.alle(haupt, '[aria-selected=true]').length], [0, 0])
n.setzeZaehler({ chat: 3, vertraege: { zahl: 0, ausruf: true } })
assert.deepEqual([knoepfe[2].getAttribute('aria-label'), fx.finde(knoepfe[2], '.kn-nav-zahl').textContent, knoepfe[3].getAttribute('aria-label'), fx.finde(knoepfe[3], '.kn-nav-zahl').textContent], ['Chat, 3 ungelesen', '3', 'Verträge, Zahlung fehlgeschlagen', '!'])
n.setzeZaehler({ chat: 0, vertraege: { zahl: 2, ausruf: false } })
assert.deepEqual([knoepfe[2].hasAttribute('aria-label'), knoepfe[3].getAttribute('aria-label')], [false, 'Verträge, 2 offen'])
n.setzeZaehler({}); assert.equal(fx.finde(menue, '.kn-nav-zahl'), null) // ohne Angaben alles auf null
} finally { fx.restore() }
})
test('beobachteTastatur: Fokus, Tastatur auf und zu ohne Blur, Blur mit Verzögerung, Aufräumen', () => {
const sender = (felder = {}) => { const hoerer = {}; return Object.assign({ addEventListener(typ, fn) { (hoerer[typ] ??= []).push(fn) }, removeEventListener(typ, fn) { hoerer[typ] = (hoerer[typ] ?? []).filter((x) => x !== fn) }, senden(typ, ev = {}) { for (const fn of hoerer[typ] ?? []) fn({ type: typ, ...ev }) } }, felder) }
const vv = sender({ height: 800, width: 400 }); const doc = sender({ activeElement: null })
const spaeter = []; const meldungen = []; const feld = { matches: () => true }
const win = { visualViewport: vv, innerHeight: 800, innerWidth: 400, matchMedia: () => ({ matches: true }), setTimeout: (fn) => spaeter.push(fn) }
const t = beobachteTastatur({ win, doc, onChange: (z) => meldungen.push([z.offen, z.tippt]) })
doc.activeElement = feld; doc.senden('focusin', { target: feld })
vv.height = 480; vv.senden('resize')
assert.deepEqual([meldungen, t.stand().offen, leisteSichtbar(t.stand())], [[[false, true], [true, true]], true, false])
vv.height = 800; vv.senden('resize') // Android-Geste: Tastatur zu, das Feld behält den Fokus
assert.deepEqual([meldungen.at(-1), leisteSichtbar(t.stand())], [[false, false], true])
doc.senden('focusin', { target: feld }); assert.deepEqual(meldungen.at(-1), [false, true]) // erneut ins Feld tippen: Leiste weg
doc.activeElement = null; doc.senden('focusout'); assert.equal(spaeter.length, 1); spaeter.shift()()
assert.deepEqual(meldungen.at(-1), [false, false])
t.beende(); const n = meldungen.length; doc.senden('focusin', { target: feld }); vv.height = 400; vv.senden('resize'); assert.equal(meldungen.length, n)
})