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>
123 lines
6.5 KiB
JavaScript
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\(/)
|
|
})
|