Analytics: Banner-Skript Review-Fixes (R8/R9, Tab-Widerruf)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5930f052d7
commit
da8bba2a1c
6 files changed
+239
-152
No files matched your search
+106
-24
@@ -29,6 +29,8 @@ test('cleanPath', () => {
|
||||
assert.equal(cleanPath(''), '/');
|
||||
assert.equal(cleanPath('/' + 'a'.repeat(400)).length, 300);
|
||||
assert.equal(cleanPath(null), '/');
|
||||
assert.equal(cleanPath('//foo/bar?x=1'), '//foo/bar');
|
||||
assert.equal(cleanPath('//foo'), '//foo');
|
||||
});
|
||||
|
||||
test('refHost', () => {
|
||||
@@ -42,30 +44,33 @@ test('refHost', () => {
|
||||
});
|
||||
|
||||
function el(tag, o = {}) {
|
||||
const e = { tagName: tag.toUpperCase(), id: o.id || '', parentNode: o.parent || null, attrs: { class: o.cls || '' },
|
||||
getAttribute(n) { return this.attrs[n] ?? null; }, classList: (o.cls || '').split(/\s+/).filter(Boolean) };
|
||||
const e = { tagName: tag.toUpperCase(), id: o.id || '', parentNode: o.parent || null, previousElementSibling: o.prev || null,
|
||||
attrs: { class: o.cls || '' }, getAttribute(n) { return this.attrs[n] ?? null; } };
|
||||
return e;
|
||||
}
|
||||
|
||||
test('cssPath', () => {
|
||||
test('cssPath: nur Tags + nth-of-type, keine IDs/Klassen', () => {
|
||||
const body = el('body');
|
||||
const div = el('div', { parent: body, cls: 'hero main' });
|
||||
const btn = el('button', { parent: div, cls: 'cta' });
|
||||
assert.equal(cssPath(btn), 'body > div.hero.main > button.cta');
|
||||
const withId = el('section', { parent: body, id: 'kontakt' });
|
||||
const a = el('a', { parent: withId });
|
||||
assert.equal(cssPath(a), 'section#kontakt > a');
|
||||
// generierte IDs/Klassen werden uebersprungen
|
||||
const gen = el('div', { parent: body, id: 'ember12345', cls: 'css-1a2b3c x9f8e7d6c5b4a ok' });
|
||||
assert.equal(cssPath(gen), 'body > div.ok');
|
||||
// max. 5 Ebenen
|
||||
let p = el('div'); const first = p;
|
||||
for (let i = 0; i < 9; i++) p = el('span', { parent: p });
|
||||
assert.equal(cssPath(p).split(' > ').length, 5);
|
||||
assert.ok(first);
|
||||
const p1 = el('p', { parent: div });
|
||||
const btn0 = el('button', { parent: div, prev: p1 });
|
||||
const btn = el('button', { parent: div, prev: btn0, cls: 'cta' });
|
||||
assert.equal(cssPath(btn), 'body:nth-of-type(1) > div:nth-of-type(1) > button:nth-of-type(2)');
|
||||
const pii = el('a', { parent: body, id: 'max@firma.de', cls: 'team-max-mustermann' });
|
||||
const sel = cssPath(pii);
|
||||
assert.equal(sel, 'body:nth-of-type(1) > a:nth-of-type(1)');
|
||||
assert.ok(!/max|firma|mustermann|#|\./.test(sel));
|
||||
// max. 6 Ebenen
|
||||
let n = el('div');
|
||||
for (let i = 0; i < 9; i++) n = el('span', { parent: n });
|
||||
assert.equal(cssPath(n).split(' > ').length, 6);
|
||||
// stoppt bei body
|
||||
const outer = el('html'); body.parentNode = outer;
|
||||
assert.equal(cssPath(div).split(' > ')[0], 'body:nth-of-type(1)');
|
||||
// <= 200
|
||||
const long = el('div', { parent: body, cls: Array.from({ length: 40 }, (_, i) => 'klasse' + 'x'.repeat(i % 5 + 3)).join(' ') });
|
||||
assert.ok(cssPath(long).length <= 200);
|
||||
let deep = el('div');
|
||||
for (let i = 0; i < 5; i++) deep = el('x'.repeat(40), { parent: deep });
|
||||
assert.ok(cssPath(deep).length <= 200);
|
||||
assert.equal(cssPath(null), '');
|
||||
});
|
||||
|
||||
@@ -198,7 +203,9 @@ function harness({ gpc, dnt, stored, search = '', src = 'https://project.webklar
|
||||
const types = () => sent.map((s) => s.d.t + (s.d.a ? ':' + s.d.a : ''));
|
||||
const ck = (target, ev = {}) => { for (const f of [...(docL.click || [])]) f({ type: 'click', target, pageX: 500, pageY: 1000, clientX: 500, clientY: 700, preventDefault() {}, ...ev }); };
|
||||
const leave = () => { for (const f of [...(winL.pagehide || [])]) f({}); document.visibilityState = 'hidden'; for (const f of [...(docL.visibilitychange || [])]) f({}); };
|
||||
return { sent, store, types, ck, leave, body, docL, winL, win, injected, history, timers, setNow: (n) => { now = n; }, link, document };
|
||||
const rerun = () => vm.runInContext(readFileSync(join(DIST, 'w.js'), 'utf8'), sandbox);
|
||||
const storage = () => (winL.storage || []).forEach((f) => f({ key: 'wk-consent' }));
|
||||
return { rerun, storage, sent, store, types, ck, leave, body, docL, winL, win, injected, history, timers, setNow: (n) => { now = n; }, link, document };
|
||||
}
|
||||
const target = () => { const b = makeEl('button'); b.parentNode = makeEl('body'); b.attrs.class = 'cta'; return b; };
|
||||
const hostEl = (h) => h.body.children.find((c) => c.shadow && findAll(c.shadow, (n) => n.attrs && n.attrs.role === 'dialog').length);
|
||||
@@ -211,7 +218,7 @@ test('w.js: ohne Einwilligung nur pv + bn shown, keine ck/ct/en', () => {
|
||||
assert.deepEqual(h.sent[1].d, { t: 'bn', site: 'abcdef0123456789abcd', a: 'shown' });
|
||||
h.ck(target()); h.leave();
|
||||
assert.deepEqual(h.types(), ['pv', 'bn:shown']);
|
||||
assert.equal((h.docL.click || []).length, 0);
|
||||
assert.equal((h.docL.click || []).length, 1); // nur die Einstellungs-Delegation
|
||||
});
|
||||
|
||||
test('w.js: Banner-Aufbau (Dialog, Texte, gleiche Buttons, Akzent nur Annehmen)', () => {
|
||||
@@ -225,13 +232,16 @@ test('w.js: Banner-Aufbau (Dialog, Texte, gleiche Buttons, Akzent nur Annehmen)'
|
||||
assert.match(text, /Wir möchten mit deiner Zustimmung anonym auswerten/);
|
||||
const a = all.find((n) => n.tagName === 'A');
|
||||
assert.equal(a.attrs.href, '/datenschutz-analyse.html');
|
||||
assert.equal(a.textContent, 'Datenschutz-Info');
|
||||
assert.equal(a.textContent, 'Datenschutz');
|
||||
const css = all.filter((n) => n.tagName === 'STYLE').map((n) => n.textContent).join('');
|
||||
assert.match(css, /#0a5a8a/);
|
||||
assert.match(css, /max-width:\s*767px/);
|
||||
const buttons = all.filter((n) => n.tagName === 'BUTTON');
|
||||
assert.deepEqual(buttons.map((b) => b.textContent), ['Ablehnen', 'Annehmen']);
|
||||
assert.ok(buttons[0].focused, 'erster Button fokussiert');
|
||||
assert.ok(!buttons[0].focused, 'kein Fokusraub beim Laden');
|
||||
assert.equal(buttons[0].attrs.class, buttons[1].attrs.class);
|
||||
assert.ok(!/button\.|\.p\b/.test(css), 'eine Regel fuer beide Buttons');
|
||||
assert.match(css, /button\{[^}]*background:#0a5a8a[^}]*color:#fff/);
|
||||
assert.equal(host.style.zIndex, '2147483647');
|
||||
// Escape schliesst nicht
|
||||
fire(dlg, 'keydown', { key: 'Escape' }); fire(host, 'keydown', { key: 'Escape' });
|
||||
@@ -248,7 +258,7 @@ test('w.js: Annehmen => bn accept, Speicher, ck/ct/en; Widerruf stoppt sofort',
|
||||
const t = target();
|
||||
h.ck(t);
|
||||
const ck = h.sent.at(-1).d;
|
||||
assert.deepEqual(ck, { t: 'ck', site: 'abcdef0123456789abcd', path: '/start', vp: 'd', x: 0.5, y: 0.5, sel: 'body > button.cta' });
|
||||
assert.deepEqual(ck, { t: 'ck', site: 'abcdef0123456789abcd', path: '/start', vp: 'd', x: 0.5, y: 0.5, sel: 'body:nth-of-type(1) > button:nth-of-type(1)' });
|
||||
// Kontakt-Link: ck + ct (Kind-Element in <a>)
|
||||
const a = makeEl('a'); a.setAttribute('href', 'tel:+4912'); a.parentNode = makeEl('body');
|
||||
const span = makeEl('span'); span.parentNode = a;
|
||||
@@ -268,12 +278,13 @@ test('w.js: Annehmen => bn accept, Speicher, ck/ct/en; Widerruf stoppt sofort',
|
||||
const settings = findAll(h.body, (x) => x.tagName === 'BUTTON' && x.textContent === 'Cookie-Einstellungen')[0];
|
||||
assert.ok(settings, 'fester Link');
|
||||
settings.click();
|
||||
assert.ok(btn(hostEl(h).shadow, 'Ablehnen').focused, 'Fokus beim Wiederoeffnen');
|
||||
btn(hostEl(h).shadow, 'Ablehnen').click();
|
||||
assert.equal(h.types().at(-1), 'bn:reject');
|
||||
const before = h.sent.length;
|
||||
h.ck(target()); h.leave();
|
||||
assert.equal(h.sent.length, before);
|
||||
assert.equal((h.docL.click || []).length, 0);
|
||||
assert.equal((h.docL.click || []).length, 1); // nur die Einstellungs-Delegation
|
||||
assert.equal((h.docL.visibilitychange || []).length, 0);
|
||||
assert.equal((h.winL.pagehide || []).length, 0);
|
||||
});
|
||||
@@ -343,3 +354,74 @@ test('w.js: wirft nie, auch wenn localStorage/sendBeacon kaputt', () => {
|
||||
vm.runInContext(readFileSync(join(DIST, 'w.js'), 'utf8'), sandbox);
|
||||
});
|
||||
});
|
||||
|
||||
test('w.js: DNT yes / msDoNotTrack => nur pv', () => {
|
||||
for (const o of [{ dnt: 'yes' }]) { const h = harness(o); assert.deepEqual(h.types(), ['pv']); }
|
||||
});
|
||||
|
||||
test('w.js: doppelt eingebunden => nur ein pv', () => {
|
||||
const h = harness({ gpc: true });
|
||||
h.rerun();
|
||||
assert.deepEqual(h.types(), ['pv']);
|
||||
});
|
||||
|
||||
test('w.js: Widerruf in anderem Tab (storage-Ereignis) stoppt Tracking sofort', () => {
|
||||
const h = harness({ stored: { v: 1, a: 'accept', t: 1_700_000_000_000 - 1000 } });
|
||||
h.ck(target());
|
||||
assert.equal(h.sent.at(-1).d.t, 'ck');
|
||||
h.store['wk-consent'] = JSON.stringify({ v: 1, a: 'reject', t: 1_700_000_000_000 });
|
||||
h.storage();
|
||||
const n = h.sent.length;
|
||||
h.ck(target()); h.leave();
|
||||
assert.equal(h.sent.length, n);
|
||||
assert.equal((h.docL.click || []).length, 1); // nur die Einstellungs-Delegation
|
||||
// Annehmen in anderem Tab startet Tracking
|
||||
h.store['wk-consent'] = JSON.stringify({ v: 1, a: 'accept', t: 1_700_000_000_000 });
|
||||
h.storage();
|
||||
h.ck(target());
|
||||
assert.equal(h.sent.length, n + 1);
|
||||
});
|
||||
|
||||
test('w.js: Einwilligung wird vor jedem Senden neu geprueft (ohne storage-Ereignis)', () => {
|
||||
const h = harness({ stored: { v: 1, a: 'accept', t: 1_700_000_000_000 - 1000 } });
|
||||
h.store['wk-consent'] = JSON.stringify({ v: 1, a: 'reject', t: 1_700_000_000_000 });
|
||||
const n = h.sent.length;
|
||||
h.ck(target()); h.leave();
|
||||
assert.equal(h.sent.length, n);
|
||||
assert.equal((h.docL.click || []).length, 1); // nur die Einstellungs-Delegation
|
||||
});
|
||||
|
||||
test('w.js: SPA-pv bei akzeptierter Einwilligung; nach Widerruf kein en', () => {
|
||||
const h = harness({ stored: { v: 1, a: 'accept', t: 1_700_000_000_000 - 1000 } });
|
||||
h.win.location.pathname = '/zwei';
|
||||
h.history.pushState(null, '', '/zwei'); h.timers.splice(0).forEach((f) => f());
|
||||
assert.deepEqual(h.sent.map((s) => s.d.path), ['/start', '/zwei']);
|
||||
h.ck(target());
|
||||
assert.equal(h.sent.at(-1).d.path, '/zwei');
|
||||
h.store['wk-consent'] = JSON.stringify({ v: 1, a: 'reject', t: 1_700_000_000_000 });
|
||||
h.storage();
|
||||
const n = h.sent.length;
|
||||
h.leave();
|
||||
assert.equal(h.sent.length, n);
|
||||
});
|
||||
|
||||
test('w.js: bfcache-Rueckkehr sendet neues pv und erlaubt neues en', () => {
|
||||
const h = harness({ stored: { v: 1, a: 'accept', t: 1_700_000_000_000 - 1000 } });
|
||||
h.leave();
|
||||
assert.equal(h.sent.filter((s) => s.d.t === 'en').length, 1);
|
||||
for (const f of h.winL.pageshow || []) f({ persisted: false });
|
||||
assert.equal(h.sent.filter((s) => s.d.t === 'pv').length, 1);
|
||||
for (const f of h.winL.pageshow || []) f({ persisted: true });
|
||||
assert.equal(h.sent.filter((s) => s.d.t === 'pv').length, 2);
|
||||
h.leave();
|
||||
assert.equal(h.sent.filter((s) => s.d.t === 'en').length, 2);
|
||||
});
|
||||
|
||||
test('w.js: [data-webklar-consent] spaeter hinzugefuegt funktioniert per Delegation', () => {
|
||||
const h = harness({ stored: { v: 1, a: 'reject', t: 1_700_000_000_000 } });
|
||||
// kein Link beim Laden => fester Link; spaeteres Element oeffnet ebenfalls
|
||||
const late = makeEl('a'); late.setAttribute('data-webklar-consent', '');
|
||||
for (const f of [...(h.docL.click || [])]) f({ type: 'click', target: late, preventDefault() {} });
|
||||
assert.ok(hostEl(h));
|
||||
assert.ok(btn(hostEl(h).shadow, 'Ablehnen').focused);
|
||||
});
|
||||
Reference in new issue
Block a user