Files
Webklar-Kundenbereich/test/geraete.test.js
T
ClaudeandClaude Opus 5.5 0a113c2983 Heatmap in Geraeterahmen (Computer/Tablet + Handy) und Keks statt "Datenschutz-Einstellungen"
Heatmap im Portal (Funktionen -> Analyse): die echte Website laeuft jetzt in realistischen
Geraeterahmen, gross MacBook oder iPad (Umschalter oben), daneben das iPhone, jeweils in echter
Breite (1512 / 820 / 402 px), verkleinert und im Geraet scrollbar, mit den Klicks der
passenden Geraeteklasse als Overlay. MacBook und iPhone stammen aus den Rahmen der neuen
Website (device-mockups), das iPad ist im gleichen Stil nachgebaut (public/geraete.css/.js).

- heatmap-link: embed: true haengt __wk_embed=1 an; w.js reicht das als data-embed an h.js.
- h.js eingebettet (nur in einem Rahmen): keine Leiste, Links und Formulare gesperrt (kein
  Seitenwechsel, also keine Zaehlung und kein Banner), Scrollleisten aus, Klickzahl per
  postMessage nur an die Herkunft von h.js (project.webklar.com).
- iframe mit Sandbox allow-scripts allow-same-origin (keine Formulare, Pop-ups, keine
  Navigation des Portals); Laden erst, wenn die Heatmap in Sicht kommt. Laesst sich eine
  Website nicht einbetten, kommt nach 20 s ein Hinweis mit "In neuem Tab oeffnen" (alter Weg).

Banner-Skript: der Einstellungsknopf nach der Wahl ist jetzt ein angeknabberter Keks unten
rechts (Inline-SVG, 44 px). Er dreht sich langsam, haelt beim Darueberfahren/Fokus an und
zeigt dann "Datenschutz-Einstellungen" (auch sein Name fuer Screenreader, passend zum
Bannertext); bei reduzierter Bewegung dreht er sich nicht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 15:09:17 +00:00

55 lines
3.3 KiB
JavaScript

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { HEAT_DEVICES, deviceHtml, heatCaption, addressOf, deviceClock } from '../public/geraete.js'
import { vpClass } from '../analytics/client/core.js'
const SRC = 'https://baeckerei.de/preise?__wk_heat=abc.def&__wk_embed=1'
test('Geraete: Website-Breite liegt in der Geraeteklasse der Heatmap (m/t/d wie vpClass)', () => {
assert.deepEqual(Object.keys(HEAT_DEVICES), ['d', 't', 'm'])
for (const [vp, dev] of Object.entries(HEAT_DEVICES)) {
assert.equal(vpClass(dev.site.width), vp, vp)
assert.equal(dev.site.width, dev.canvas[0], 'volle Bildschirmbreite')
assert.equal(dev.site.top + dev.site.height, dev.canvas[1], 'Website reicht bis zum unteren Rand')
}
assert.deepEqual(Object.fromEntries(Object.entries(HEAT_DEVICES).map(([k, d]) => [k, d.label])), { d: 'Computer', t: 'Tablet', m: 'Handy' })
})
test('deviceHtml: Rahmen je Geraet, iframe in echter Breite mit enger Sandbox', () => {
const frames = { d: 'dm-macbook', t: 'dm-ipad', m: 'dm-iphone' }
for (const [vp, cls] of Object.entries(frames)) {
const html = deviceHtml(vp, { src: SRC, title: 'Heatmap <Handy>' })
const dev = HEAT_DEVICES[vp]
assert.match(html, new RegExp(`^<div class="${cls}"`), vp)
assert.match(html, new RegExp(`class="dm-canvas" data-design-width="${dev.canvas[0]}" data-design-height="${dev.canvas[1]}"`))
assert.match(html, new RegExp(`class="hs-site" style="top:${dev.site.top}px;width:${dev.site.width}px;height:${dev.site.height}px"`))
assert.match(html, /<iframe src="https:\/\/baeckerei\.de\/preise\?__wk_heat=abc\.def&amp;__wk_embed=1"/)
assert.match(html, /title="Heatmap &lt;Handy&gt;"/)
const sandbox = /sandbox="([^"]*)"/.exec(html)[1]
assert.equal(sandbox, 'allow-scripts allow-same-origin', 'keine Formulare, Pop-ups oder Navigation des Portals')
assert.match(html, /referrerpolicy="no-referrer"/)
assert.equal((html.match(/<iframe/g) || []).length, 1)
}
assert.match(deviceHtml('d', { src: SRC }), /<span>baeckerei\.de\/preise<\/span>/, 'Adresszeile ohne Parameter')
assert.match(deviceHtml('m', { src: SRC }), /data-statusbar="dark"/, 'dunkle Statusleiste auf weisser Website')
assert.equal(deviceHtml('x', { src: SRC }), '')
assert.doesNotMatch(deviceHtml('d', { src: '"><script>alert(1)</script>' }), /<script>/)
})
test('heatCaption', () => {
const fmt = (n) => new Intl.NumberFormat('de-DE').format(n)
assert.equal(heatCaption('loading'), 'Lädt …')
assert.equal(heatCaption('ready', 0), 'Noch keine Klicks auf dieser Seite (letzte 30 Tage)')
assert.equal(heatCaption('ready', 1), '1 Klick · letzte 30 Tage')
assert.equal(heatCaption('ready', 1234, fmt), '1.234 Klicks · letzte 30 Tage')
assert.equal(heatCaption('error'), 'Die Heatmap konnte nicht geladen werden.')
assert.equal(heatCaption('timeout'), 'Deine Website lässt sich hier nicht anzeigen.')
})
test('addressOf und deviceClock', () => {
assert.equal(addressOf('https://baeckerei.de/?__wk_heat=x'), 'baeckerei.de')
assert.equal(addressOf('https://cafe.project.webklar.com/karte/?a=1'), 'cafe.project.webklar.com/karte/')
assert.equal(addressOf('kaputt'), '')
assert.deepEqual(deviceClock(new Date('2026-10-02T10:00:00Z')), { mac: 'Fr. 2. Okt. 9:41', ipad: '9:41 Fr. 2. Okt.' })
})