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>
55 lines
3.3 KiB
JavaScript
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&__wk_embed=1"/)
|
|
assert.match(html, /title="Heatmap <Handy>"/)
|
|
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.' })
|
|
})
|