Files
Webklar-Kundenbereich/test/knHoehe.test.js
T
ClaudeandClaude Opus 5.5 cca29a4aef Kundenansicht neu: vorübergehende Messung der iPhone-Höhe
Der Fix 0fa8fdb hat die zu hoch stehende Leiste am iPhone nicht
behoben. Damit wir nicht weiter raten: die Seite schickt am iPhone
Bildschirmwerte (Fenster, visualViewport, Bildschirm, Safe-Area,
vh-Einheiten, Lage der Leiste, Eingriffe von hoehe.js) an
/api/diag/hoehe, der Server schreibt sie als Zeile "[diag-hoehe]" ins
Log. Keine Inhalte, nur Pilot-Kunden (nicht die Mitarbeiter-Ansicht),
höchstens 80 Meldungen je Seite und 400 Zeilen je Kunde und Stunde.
Wird nach der Klärung wieder entfernt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 11:10:30 +00:00

123 lines
6.5 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { LUFT, MAX_VERSUCHE, PRUEF_MS, beobachteHoehe, haengt, hoeheZustand } from '../public/neu/hoehe.js'
import { lies } from './support/knHuelle.js'
test('hoeheZustand: größte Höhe je Breite, Drehen fängt neu an', () => {
let z = hoeheZustand(null, { hoehe: 932, breite: 430 })
z = hoeheZustand(z, { hoehe: 873, breite: 430 }); assert.deepEqual(z, { voll: 932, breite: 430 })
z = hoeheZustand(z, { hoehe: 430, breite: 932 }); assert.deepEqual(z, { voll: 430, breite: 932 })
})
test('haengt: nur bei gleicher Breite, ohne Textfeld und mehr als LUFT kleiner (iOS-Fall 932 → 873)', () => {
const z = { voll: 932, breite: 430 }
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: false }), true)
assert.equal(haengt(z, { hoehe: 873, breite: 430, textfeldAktiv: true }), false) // Tastatur offen
assert.equal(haengt(z, { hoehe: 932 - LUFT, breite: 430, textfeldAktiv: false }), false)
assert.equal(haengt(z, { hoehe: 400, breite: 932, textfeldAktiv: false }), false) // gedreht
assert.equal(haengt(null, { hoehe: 1, breite: 1 }), false)
})
/** Kleine Attrappe: Fenster, Dokument und eine Hülle, die mitschreibt, wann sie aus- und eingeblendet wird. */
function aufbau({ aktiv = () => true } = {}) {
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) { for (const fn of hoerer[typ] ?? []) fn({ type: typ }) },
anzahl: (typ) => (hoerer[typ] ?? []).length,
}, felder)
}
const spaeter = []
const protokoll = []
const win = sender({
innerHeight: 932, innerWidth: 430, scrollX: 0, scrollY: 0,
setTimeout: (fn, ms) => { spaeter.push({ fn, ms }); return spaeter.length },
clearTimeout: (id) => { if (spaeter[id - 1]) spaeter[id - 1].fn = () => {} },
scrollTo: (x, y) => { win.scrollX = x; win.scrollY = y; protokoll.push(['scrollTo', x, y]) },
})
const verlauf = { scrollTop: 1200, scrollLeft: 0 }
const haken = {}
const style = {}
let display = ''
Object.defineProperty(style, 'display', { get: () => display, set: (v) => { display = v; protokoll.push(['display', v]); if (v === 'none') { verlauf.scrollTop = 0; win.scrollY = 0; haken.beimAusblenden?.() } } })
const knopf = { focus: () => protokoll.push(['focus']) }
const element = {
style,
get offsetHeight() { protokoll.push(['umbruch']); return 100 },
querySelectorAll: () => [verlauf, { scrollTop: 0, scrollLeft: 0 }],
contains: (el) => el === knopf || el === verlauf,
}
const doc = sender({ activeElement: null, body: {}, visibilityState: 'visible' })
const istFeld = (el) => el?.feld === true
const h = beobachteHoehe({ win, doc, element, aktiv, istTextfeld: istFeld })
const laufe = () => { while (spaeter.length) spaeter.shift().fn() }
return { win, doc, element, verlauf, knopf, protokoll, spaeter, h, laufe, haken }
}
test('beobachteHoehe: Tastatur zu und Höhe hängt → Hülle einmal aus und ein, Scrollstände zurück; danach Ruhe', () => {
const t = aufbau()
t.win.scrollY = 40
t.doc.activeElement = { feld: true }
t.win.innerHeight = 873; t.win.senden('resize') // iOS: die Höhe fällt mit der Tastatur und bleibt unten
assert.equal(t.spaeter.length, 0) // solange das Feld den Fokus hat, wird nicht geprüft
t.doc.activeElement = t.doc.body; t.doc.senden('focusout')
assert.deepEqual(t.spaeter.map((s) => s.ms), PRUEF_MS)
t.spaeter.shift().fn()
assert.deepEqual(t.protokoll, [['display', 'none'], ['umbruch'], ['display', ''], ['umbruch'], ['scrollTo', 0, 40]])
assert.equal(t.verlauf.scrollTop, 1200)
t.win.innerHeight = 932 // WebKit hat neu gemessen
t.protokoll.length = 0; t.laufe()
assert.deepEqual(t.protokoll, [])
assert.deepEqual(t.h.stand(), { voll: 932, breite: 430, versuche: 1 })
})
test('beobachteHoehe: onHeile meldet vorher, nachher, voll und Versuch; ein Fehler im Beobachter stört nicht', () => {
const t = aufbau()
const meldungen = []
t.h.beende()
const h = beobachteHoehe({ win: t.win, doc: t.doc, element: t.element, istTextfeld: () => false, onHeile: (w) => { meldungen.push(w); throw new Error('kaputt') } })
t.win.innerHeight = 873
assert.equal(h.pruefe(), true)
assert.deepEqual(meldungen, [{ vorher: 873, nachher: 873, voll: 932, versuche: 1 }])
})
test('beobachteHoehe: hilft es nicht, höchstens MAX_VERSUCHE je Anlass; App wieder vorne ist ein neuer Anlass', () => {
const t = aufbau()
t.win.innerHeight = 873
t.doc.senden('focusout'); t.laufe()
t.win.senden('resize'); t.laufe()
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, MAX_VERSUCHE)
t.doc.visibilityState = 'hidden'; t.doc.senden('visibilitychange'); assert.equal(t.spaeter.length, 0)
t.doc.visibilityState = 'visible'; t.doc.senden('visibilitychange'); t.laufe()
assert.equal(t.protokoll.filter((p) => p[0] === 'display' && p[1] === 'none').length, 2 * MAX_VERSUCHE)
})
test('beobachteHoehe: Fokus auf einem Knopf in der Hülle bleibt, ohne Scrollen', () => {
const t = aufbau()
t.win.innerHeight = 873
t.doc.activeElement = t.knopf
t.haken.beimAusblenden = () => { t.doc.activeElement = t.doc.body } // beim Ausblenden verliert der Knopf den Fokus (wie im Browser)
assert.equal(t.h.pruefe(), true)
assert.deepEqual(t.protokoll.at(-1), ['focus'])
})
test('beobachteHoehe: nicht aktiv (kein iOS-App-Modus), keine Hülle oder Höhe passt → nichts passiert; beende räumt auf', () => {
const aus = aufbau({ aktiv: () => false })
aus.win.innerHeight = 873; assert.equal(aus.h.pruefe(), false); assert.deepEqual(aus.protokoll, [])
const ok = aufbau()
assert.equal(ok.h.pruefe(), false)
ok.win.innerHeight = 932 - LUFT; assert.equal(ok.h.pruefe(), false); assert.deepEqual(ok.protokoll, [])
ok.doc.senden('focusout'); ok.h.beende(); ok.win.innerHeight = 873; ok.laufe()
assert.deepEqual(ok.protokoll, [])
assert.deepEqual([ok.win.anzahl('resize'), ok.doc.anzahl('focusout'), ok.doc.anzahl('visibilitychange')], [0, 0, 0])
})
test('huelle.js: Höhen-Wächter nur in der iOS-App vom Home-Bildschirm am Handy, auf der Hülle', () => {
const js = lies('huelle.js')
assert.match(js, /import \{ beobachteHoehe \} from '\.\/hoehe\.js'/)
assert.match(js, /beobachteHoehe\(\{ win, doc, element: doc\.querySelector\('\.kn-huelle'\), aktiv: \(\) => istIos\(\) && standalone\(\) && istHandy\(\), istTextfeld, onHeile:/)
assert.match(js, /if \(istIos\(\) && istHandy\(\) && !viewAs\) diag = starteDiagHoehe\(/)
})