Öffnungszeiten: Fixrunde Website-Skript und Editor (JSON-LD, Kopf beim Scrollen, späte Platzhalter, Links, Bestätigung)
- JSON-LD wird ohne "<" geschrieben (<), ein </script> im Firmennamen bricht keinen Block mehr - verschiebe(): ist die Hinweis-Leiste weggescrollt, bekommen feste Elemente ihren eigenen Inline-top zurück (Köpfe, die per Klasse beim Scrollen wegfahren, funktionieren wieder) - Späte Platzhalter (Einzelseiten-Apps): entprellter MutationObserver auf body zeichnet neue oder wieder eingehängte Platzhalter nach - sichererLink lehnt Zugangsdaten in der Adresse ab; die Adresse der Schnittstelle bestimmt nur das eigene Script-Tag, kein Platzhalter mehr (der siteKey am Platzhalter bleibt laut Spec 4.2) - Editor: die Bestätigung klebt unter der Kopfleiste und bleibt nach dem Speichern eines unteren Abschnitts sichtbar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ecf67d6978
commit
aa9ee35ab6
6 files changed
+221
-12
No files matched your search
@@ -410,7 +410,7 @@ function leisten() {
|
||||
const tel = /\/\/ @telefon-anfang[^\n]*\n([\s\S]*?)\/\/ @telefon-ende/.exec(QUELLE)
|
||||
assert.ok(rein && tel, 'Marken fehlen')
|
||||
return vm.runInNewContext(
|
||||
`${tel[1]}; ${rein[1]}; ({ jsonLdEinbauen, kontaktKnoepfe, istEigeneAnrufLeiste, hinweisKnopf, HINWEIS_FARBEN })`,
|
||||
`${tel[1]}; ${rein[1]}; ({ jsonLdEinbauen, jsonLdText, kontaktKnoepfe, istEigeneAnrufLeiste, hinweisKnopf, HINWEIS_FARBEN })`,
|
||||
{},
|
||||
)
|
||||
}
|
||||
@@ -571,9 +571,15 @@ test('Kontakt-Knöpfe aus Teildaten', () => {
|
||||
test('Links aus der API werden erneut geprüft (https:, tel: nur über telefonLink)', () => {
|
||||
const { hinweisKnopf } = leisten()
|
||||
assert.equal(hinweisKnopf({ art: 'link', ziel: 'javascript:alert(1)', text: 'Los' }), null, 'javascript: ist kein Link')
|
||||
for (const bose of ['JaVaScRiPt:alert(1)', 'data:text/html,<b>x</b>', 'vbscript:x', 'http://x.example/', '//x.example/', '/aktion', 'x.example', ' https://x.example/', 'https://x.example/a b', 'https://', 'https:x', '', 'tel:+4963011234', 'https://x.example/\n', 5, null, {}]) {
|
||||
for (const bose of ['JaVaScRiPt:alert(1)', 'data:text/html,<b>x</b>', 'vbscript:x', 'http://x.example/', '//x.example/', '/aktion', 'x.example', ' https://x.example/', 'https://x.example/a b', 'https://', 'https:x', '', 'tel:+4963011234', 'https://x.example/\n', 5, null, {},
|
||||
// Zugangsdaten in der Adresse (wie der Server und linkOk des Portals): nie ein Link
|
||||
'https://user:pw@x.example/', 'https://user@x.example/', 'https://:pw@x.example/', 'https://x.example@evil.example/', 'https://evil.example\\@x.example/']) {
|
||||
assert.equal(hinweisKnopf({ art: 'link', ziel: bose, text: 'Los' }), null, JSON.stringify(bose))
|
||||
}
|
||||
// Pfad, Suche, Anker und Port bleiben erlaubt (ein @ dort ist keine Zugangsdaten)
|
||||
for (const gut of ['https://x.example', 'https://x.example:8443/a', 'https://x.example?x=1', 'https://x.example#a', 'https://x.example/mail@x.example', 'https://x.example/a?u=a@b#c@d']) {
|
||||
assert.equal(hinweisKnopf({ art: 'link', ziel: gut, text: 'Los' })?.href, gut, gut)
|
||||
}
|
||||
assert.deepEqual(norm(hinweisKnopf({ art: 'link', ziel: 'https://baeckerei.example/aktion?x=1#a', text: 'Zur Aktion' })), { art: 'link', href: 'https://baeckerei.example/aktion?x=1#a', text: 'Zur Aktion' })
|
||||
assert.deepEqual(norm(hinweisKnopf({ art: 'anruf', ziel: '06301 1234', text: 'Jetzt anrufen' })), { art: 'anruf', href: 'tel:063011234', text: 'Jetzt anrufen' })
|
||||
assert.deepEqual(norm(hinweisKnopf({ art: 'anruf', ziel: '+49 6301 1234', text: '' })), { art: 'anruf', href: 'tel:+4963011234', text: 'Anrufen' }, 'ohne Beschriftung eine Vorgabe')
|
||||
@@ -583,6 +589,44 @@ test('Links aus der API werden erneut geprüft (https:, tel: nur über telefonLi
|
||||
for (const leer of [null, undefined, {}, 'x', 5, []]) assert.equal(hinweisKnopf(leer), null, JSON.stringify(leer))
|
||||
})
|
||||
|
||||
test('Kontakt-Knöpfe: Route und WhatsApp mit Zugangsdaten in der Adresse fallen weg', () => {
|
||||
const { kontaktKnoepfe } = leisten()
|
||||
assert.deepEqual(norm(kontaktKnoepfe({ telefon: null, routeUrl: 'https://user:pw@www.google.com/maps', whatsappUrl: 'https://wa.me@evil.example/491711234567' })), [])
|
||||
})
|
||||
|
||||
test('JSON-LD-Text: kein "<" im geschriebenen Text (</script> beendet sonst ein serialisiertes Skript), Inhalt bleibt gleich lesbar', () => {
|
||||
const { jsonLdText } = leisten()
|
||||
const wert = { '@type': 'LocalBusiness', name: 'Bäckerei </script><img src=x onerror=alert(1)> & Co', address: 'a<b' }
|
||||
const text = jsonLdText(wert)
|
||||
assert.ok(!text.includes('<'), text)
|
||||
assert.ok(text.includes('\\u003c/script>'))
|
||||
assert.deepEqual(JSON.parse(text), wert, 'JSON.parse macht daraus wieder dasselbe')
|
||||
assert.equal(jsonLdText([]), '[]')
|
||||
// beide Stellen, die JSON-LD in die Seite schreiben, gehen über den Helfer
|
||||
assert.doesNotMatch(OHNE_KOMMENTARE, /textContent = JSON\.stringify\(/, 'JSON-LD nie ohne Escape in textContent')
|
||||
assert.equal([...OHNE_KOMMENTARE.matchAll(/textContent = jsonLdText\(/g)].length, 2, 'eigener und fremder Block')
|
||||
})
|
||||
|
||||
test('Quelltext: die Adresse der Schnittstelle bestimmt nur das eigene Script-Tag (data-api) oder dessen Herkunft, nie ein Platzhalter', () => {
|
||||
const api = /function bestimmeApi \(skript\) \{([\s\S]*?)\n \}\n/.exec(OHNE_KOMMENTARE)
|
||||
assert.ok(api, 'bestimmeApi fehlt')
|
||||
assert.match(api[1], /attr\(skript, 'data-api'\)/)
|
||||
assert.doesNotMatch(api[1], /platzhalter/, 'kein data-api an Platzhaltern: eine Seite könnte sonst die Anfragen umleiten')
|
||||
// der siteKey darf (Spec 4.2) auch am Platzhalter stehen
|
||||
const key = /function bestimmeSiteKey \(skript\) \{([\s\S]*?)\n \}\n/.exec(OHNE_KOMMENTARE)
|
||||
assert.ok(key && /attr\(skript, 'data-site'\)/.test(key[1]) && /attr\(platzhalter\[i\]\.root, 'data-webklar-zeiten'\)/.test(key[1]))
|
||||
})
|
||||
|
||||
test('Späte Platzhalter (Einzelseiten-Apps): ein MutationObserver auf body, entprellt über timer, rendert nur bei neuem oder wieder eingehängtem Platzhalter', () => {
|
||||
assert.match(OHNE_KOMMENTARE, /new MutationObserver\(/)
|
||||
assert.match(OHNE_KOMMENTARE, /observe\(document\.body, \{ childList: true, subtree: true \}\)/)
|
||||
const beo = /function beobachteSeite \(\) \{([\s\S]*?)\n \}\n/.exec(OHNE_KOMMENTARE)
|
||||
assert.ok(beo, 'beobachteSeite fehlt')
|
||||
assert.match(beo[1], /timer\(/, 'die Entprellung läuft über timer (die einzige setTimeout-Stelle)')
|
||||
assert.match(OHNE_KOMMENTARE, /var BEOBACHTER_MS = 200\b/)
|
||||
assert.match(OHNE_KOMMENTARE, /querySelector\(SELEKTOR \+ ':not\(\[data-wkz-init\]\)'\)/)
|
||||
})
|
||||
|
||||
test('istEigeneAnrufLeiste: festes oder klebendes Element unten mit tel:-Link; oben, ausgeblendet oder bildschirmhoch zählt nicht', () => {
|
||||
const { istEigeneAnrufLeiste } = leisten()
|
||||
const fenster = 844
|
||||
@@ -629,7 +673,7 @@ test('Leisten im Quelltext: Shadow DOM, Rolle und Name, Handy-Abfrage, z-index 4
|
||||
assert.doesNotMatch(OHNE_KOMMENTARE, /aria-live/)
|
||||
assert.ok(!OHNE_KOMMENTARE.includes('close') && !/Schlie(ss|ß)en/.test(OHNE_KOMMENTARE), 'keine Schließen-Schaltfläche')
|
||||
// JSON-LD wird über textContent geschrieben (nie innerHTML), der Selektor des Teams nur in try/catch
|
||||
assert.match(OHNE_KOMMENTARE, /\.textContent = JSON\.stringify\(/)
|
||||
assert.match(OHNE_KOMMENTARE, /\.textContent = jsonLdText\(/, "JSON-LD wird über jsonLdText (ohne <) geschrieben")
|
||||
assert.match(OHNE_KOMMENTARE, /try \{[^}]*querySelectorAll\(nav\)[^}]*\} catch/, 'navSelektor nur in try/catch')
|
||||
// Linkziele nie per String in HTML, sondern per setAttribute; jeder Link bekommt rel
|
||||
assert.ok(OHNE_KOMMENTARE.includes("'rel', 'noopener noreferrer'"))
|
||||
|
||||
Reference in new issue
Block a user