285 lines
21 KiB
JavaScript
285 lines
21 KiB
JavaScript
import { test } from 'node:test'
|
||
import assert from 'node:assert/strict'
|
||
import { delta, fmtDuration, fmtNum, dayLabel, periodLabel, compareFrom, contactRate, refName, weekdayStats, summarize, chartModel } from '../public/analyse-view.js'
|
||
|
||
// Testdaten: eine vollstaendige Statistik-Antwort mit 30 Tagen (Form wie analytics/src/stats.js)
|
||
const isoDay = (from, n) => new Date(Date.UTC(+from.slice(0, 4), +from.slice(5, 7) - 1, +from.slice(8, 10) + n)).toISOString().slice(0, 10)
|
||
const days = (from, n, pv) => Array.from({ length: n }, (_, i) => ({ day: isoDay(from, i), pv: typeof pv === 'function' ? pv(i, isoDay(from, i)) : pv }))
|
||
const t = { pv: 6930, visitors: 2410, contacts: 188, consentRate: 0.61, engaged: 1800, avgSec: 108, avgScroll: 64 }
|
||
const prev = {
|
||
from: '2026-08-06', to: '2026-09-04', complete: true,
|
||
totals: { pv: 6080, visitors: 2200, contacts: 143, consentRate: 0.6, engaged: 1500, avgSec: 114, avgScroll: 60 },
|
||
series: days('2026-08-06', 30, 203),
|
||
contacts: [{ key: 'tel', count: 80 }, { key: 'mail', count: 40 }, { key: 'wa', count: 23 }],
|
||
}
|
||
const stats = (over = {}) => ({
|
||
range: 30, from: '2026-09-05', to: '2026-10-04', firstDay: '2026-07-01',
|
||
totals: t,
|
||
series: days('2026-09-05', 30, 231),
|
||
pages: [{ key: '/', count: 3200 }, { key: '/preise', count: 1310 }, { key: '/kontakt', count: 900 }],
|
||
refs: [{ key: 'google.com', count: 880 }, { key: '', count: 700 }, { key: 'instagram.com', count: 400 }, { key: 'l.facebook.com', count: 290 }],
|
||
countries: [{ key: 'DE', count: 6000 }],
|
||
devices: [{ key: 'm', count: 4590 }, { key: 'd', count: 2200 }, { key: 't', count: 140 }],
|
||
contacts: [{ key: 'tel', count: 96 }, { key: 'mail', count: 52 }, { key: 'wa', count: 30 }, { key: 'out', count: 10 }],
|
||
banner: { shown: 3950, accept: 2410, reject: 1540 },
|
||
previous: prev,
|
||
...over,
|
||
})
|
||
|
||
test('delta: Prozent ab 100, sonst absolut, flat, Sekunden, null', () => {
|
||
assert.deepEqual(delta(6930, 6080), { dir: 'up', text: '▲ 14 %', aria: 'plus 14 Prozent gegenüber dem Zeitraum davor' })
|
||
assert.equal(delta(188, 143).text, '▲ 31 %') // beide ≥ 100 → Prozent
|
||
assert.equal(delta(45, 33).text, '▲ 12')
|
||
assert.equal(delta(30, 33).text, '▼ 3')
|
||
assert.deepEqual(delta(7, 7), { dir: 'flat', text: '± 0', aria: 'unverändert gegenüber dem Zeitraum davor' })
|
||
assert.equal(delta(1004, 1000).text, '± 0 %') // |0,4 %| < 1
|
||
assert.equal(delta(1004, 1000).dir, 'flat')
|
||
assert.equal(delta(108, 114, { kind: 'sec' }).text, '▼ 6 s')
|
||
assert.equal(delta(200, 135, { kind: 'sec' }).text, '▲ 1:05')
|
||
assert.equal(delta(null, 3), null)
|
||
assert.equal(delta(3, undefined), null)
|
||
assert.equal(delta('3', 4), null)
|
||
})
|
||
|
||
test('delta: Richtung und aria in Wörtern', () => {
|
||
assert.deepEqual(delta(5000, 6000), { dir: 'down', text: '▼ 17 %', aria: 'minus 17 Prozent gegenüber dem Zeitraum davor' })
|
||
assert.equal(delta(45, 33).aria, 'plus 12 gegenüber dem Zeitraum davor')
|
||
assert.equal(delta(30, 33).aria, 'minus 3 gegenüber dem Zeitraum davor')
|
||
assert.equal(delta(108, 114, { kind: 'sec' }).aria, 'minus 6 Sekunden gegenüber dem Zeitraum davor')
|
||
assert.equal(delta(200, 135, { kind: 'sec' }).aria, 'plus 1 Minute 5 Sekunden gegenüber dem Zeitraum davor')
|
||
assert.equal(delta(240, 120, { kind: 'sec' }).aria, 'plus 2 Minuten gegenüber dem Zeitraum davor')
|
||
})
|
||
|
||
test('fmtDuration, dayLabel, periodLabel, fmtNum', () => {
|
||
assert.equal(fmtDuration(108), '1:48'); assert.equal(fmtDuration(7), '0:07'); assert.equal(fmtDuration(null), '–')
|
||
assert.equal(fmtDuration(NaN), '–'); assert.equal(fmtDuration(-3), '–'); assert.equal(fmtDuration(undefined), '–')
|
||
assert.equal(dayLabel('2026-09-19'), 'Sa., 19.09.')
|
||
assert.equal(dayLabel('2026-09-20'), 'So., 20.09.')
|
||
assert.equal(periodLabel('2026-09-05', '2026-10-04'), '5. Sep. – 4. Okt.')
|
||
assert.equal(periodLabel('2026-03-01', '2026-06-30'), '1. März – 30. Juni')
|
||
assert.equal(periodLabel('', ''), '')
|
||
assert.equal(periodLabel('2026-09-05', ''), '')
|
||
assert.equal(fmtNum(6930), '6.930')
|
||
assert.equal(fmtNum(0), '0')
|
||
assert.equal(fmtNum(1234567), '1.234.567')
|
||
})
|
||
|
||
test('compareFrom: ok, Datum bei neuer Website, nichts ohne previous', () => {
|
||
assert.deepEqual(compareFrom(stats()), { ok: true, from: '' })
|
||
assert.deepEqual(compareFrom(stats({ firstDay: '2026-09-20', previous: { ...prev, complete: false } })), { ok: false, from: '18.11.' }) // range 30: +59 Tage
|
||
assert.deepEqual(compareFrom(stats({ range: 7, firstDay: '2026-09-28', previous: { ...prev, complete: false } })), { ok: false, from: '11.10.' }) // range 7: +13 Tage
|
||
assert.deepEqual(compareFrom(stats({ firstDay: '', previous: { ...prev, complete: false } })), { ok: false, from: '' })
|
||
assert.deepEqual(compareFrom({ totals: { pv: 50 }, series: [] }), { ok: false, from: '' })
|
||
assert.equal(compareFrom(stats({ previous: { ...prev, totals: { ...prev.totals, pv: 19 } } })).ok, false)
|
||
assert.equal(compareFrom(stats({ previous: { ...prev, totals: { ...prev.totals, pv: 20 } } })).ok, true)
|
||
})
|
||
|
||
test('contactRate und refName', () => {
|
||
assert.equal(contactRate(188, 2410), 7.8); assert.equal(contactRate(5, 29), null)
|
||
assert.equal(contactRate(0, 30), 0); assert.equal(contactRate(null, 100), null)
|
||
assert.equal(refName('www.google.de'), 'Google'); assert.equal(refName('l.facebook.com'), 'Facebook'); assert.equal(refName(''), 'Direkt oder unbekannt'); assert.equal(refName('blog.example'), 'blog.example')
|
||
assert.equal(refName('google.com'), 'Google'); assert.equal(refName('google.co.uk'), 'Google')
|
||
assert.equal(refName('www.bing.com'), 'Bing'); assert.equal(refName('l.instagram.com'), 'Instagram')
|
||
assert.equal(refName('m.facebook.com'), 'Facebook'); assert.equal(refName('facebook.com'), 'Facebook')
|
||
assert.equal(refName('duckduckgo.com'), 'DuckDuckGo'); assert.equal(refName('www.ecosia.org'), 'Ecosia')
|
||
assert.equal(refName('notgoogle.com'), 'notgoogle.com'); assert.equal(refName('google.evil.de'), 'google.evil.de')
|
||
assert.equal(refName('Google.DE'), 'Google')
|
||
})
|
||
|
||
test('weekdayStats: stärkster Wochentag, Spitzentag, < 7 Tage null', () => {
|
||
// 05.09.2026 ist ein Samstag: 5 Samstage mit 20, alle anderen 10 → Schnitt 11,67; Samstag 20 ≥ 1,2 × 11,67, aber < 1,8 × 11,67 (kein Spitzentag)
|
||
const sat = (i, day) => (new Date(day + 'T00:00:00Z').getUTCDay() === 6 ? 20 : 10)
|
||
const w = weekdayStats(days('2026-09-05', 30, sat), '')
|
||
assert.equal(w.best.weekday, 6); assert.equal(w.best.name, 'Samstag'); assert.equal(w.best.avg, 20)
|
||
assert.ok(Math.abs(w.avg - 350 / 30) < 1e-9)
|
||
assert.equal(w.peak, null)
|
||
// ein Tag mit dem 3-Fachen des Schnitts → Spitzentag
|
||
const spike = (i, day) => (day === '2026-09-19' ? 60 : 10)
|
||
const p = weekdayStats(days('2026-09-05', 30, spike), '')
|
||
assert.deepEqual(p.peak, { day: '2026-09-19', pv: 60 })
|
||
assert.equal(p.best.name, 'Samstag')
|
||
// gleichmäßig: weder stärkster Tag noch Spitzentag
|
||
assert.deepEqual(weekdayStats(days('2026-09-05', 30, 10), ''), { best: null, avg: 10, peak: null })
|
||
// weniger als 7 Tage → null; firstDay schneidet Tage davor ab
|
||
assert.equal(weekdayStats(days('2026-09-05', 6, 10), ''), null)
|
||
assert.notEqual(weekdayStats(days('2026-09-05', 30, 10), '2026-09-28'), null) // 28.09.–04.10. = genau 7 Tage
|
||
})
|
||
|
||
test('weekdayStats: firstDay zählt nur Tage ab firstDay', () => {
|
||
assert.equal(weekdayStats(days('2026-09-05', 30, 10), '2026-09-29'), null) // nur 6 Tage (29.09.–04.10.)
|
||
const w = weekdayStats(days('2026-09-05', 30, (i, day) => (day < '2026-09-20' ? 1000 : 10)), '2026-09-20')
|
||
assert.equal(w.avg, 10) // die Tage davor (1000) bleiben außen vor
|
||
assert.equal(weekdayStats(null, ''), null)
|
||
})
|
||
|
||
test('summarize: Wachstum mit Prozent', () => {
|
||
const s = summarize(stats())
|
||
assert.equal(s.lead, 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen – <b>14 % mehr</b> als in den 30 Tagen davor.')
|
||
assert.equal(s.items[0].icon, 'contact')
|
||
assert.equal(s.items[0].html, '188-mal hat dich jemand direkt kontaktiert, meist per Telefon (96). Das sind 45 mehr als zuvor.')
|
||
assert.ok(s.items.length <= 4)
|
||
})
|
||
|
||
test('summarize: Rückgang, etwa gleich, kleine Zahlen absolut', () => {
|
||
const lead = (totalsPv, prevPv) => summarize(stats({ totals: { ...t, pv: totalsPv }, previous: { ...prev, totals: { ...prev.totals, pv: prevPv } } })).lead
|
||
assert.equal(lead(6930, 7615), 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen – <b>9 % weniger</b> als in den 30 Tagen davor.')
|
||
assert.equal(lead(6930, 6800), 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen – etwa so oft wie in den 30 Tagen davor.')
|
||
assert.equal(lead(80, 68), 'Deine Website wurde in den letzten 30 Tagen <b>80-mal</b> aufgerufen – <b>12 Aufrufe mehr</b> als in den 30 Tagen davor.')
|
||
assert.equal(lead(60, 75), 'Deine Website wurde in den letzten 30 Tagen <b>60-mal</b> aufgerufen – <b>15 Aufrufe weniger</b> als in den 30 Tagen davor.')
|
||
assert.equal(lead(84, 83), 'Deine Website wurde in den letzten 30 Tagen <b>84-mal</b> aufgerufen – etwa so oft wie in den 30 Tagen davor.') // unter 5 % gilt auch bei kleinen Zahlen
|
||
assert.match(lead(32, 31), /aufgerufen – etwa so oft wie in den 30 Tagen davor\.$/)
|
||
assert.equal(lead(40, 30), 'Deine Website wurde in den letzten 30 Tagen <b>40-mal</b> aufgerufen – <b>10 Aufrufe mehr</b> als in den 30 Tagen davor.')
|
||
assert.equal(lead(31, 20), 'Deine Website wurde in den letzten 30 Tagen <b>31-mal</b> aufgerufen – <b>11 Aufrufe mehr</b> als in den 30 Tagen davor.')
|
||
assert.equal(lead(80, 80), 'Deine Website wurde in den letzten 30 Tagen <b>80-mal</b> aufgerufen – etwa so oft wie in den 30 Tagen davor.')
|
||
assert.equal(lead(7200, 6857), 'Deine Website wurde in den letzten 30 Tagen <b>7.200-mal</b> aufgerufen – <b>5 % mehr</b> als in den 30 Tagen davor.') // genau 5 % gilt noch als Veränderung
|
||
})
|
||
|
||
test('summarize: ohne Vergleich und neu gestartet', () => {
|
||
// previous fehlt: '…in den letzten 30 Tagen <b>X-mal</b> aufgerufen.'
|
||
const { previous, ...ohne } = stats()
|
||
assert.equal(summarize(ohne).lead, 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen.')
|
||
// Vorzeitraum unvollständig, firstDay vor dem Zeitraum: kein Vergleich, aber kein „Seit dem Start“
|
||
assert.equal(summarize(stats({ firstDay: '2026-08-20', previous: { ...prev, complete: false } })).lead, 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen.')
|
||
// Vorzeitraum zu klein: kein Vergleich
|
||
assert.equal(summarize(stats({ previous: { ...prev, totals: { ...prev.totals, pv: 12 } } })).lead, 'Deine Website wurde in den letzten 30 Tagen <b>6.930-mal</b> aufgerufen.')
|
||
// firstDay im Zeitraum (complete false): 'Seit dem Start der Analyse am 20.09. wurde deine Website <b>X-mal</b> aufgerufen.'
|
||
const neu = summarize(stats({ firstDay: '2026-09-20', previous: { ...prev, complete: false } }))
|
||
assert.equal(neu.lead, 'Seit dem Start der Analyse am 20.09. wurde deine Website <b>6.930-mal</b> aufgerufen.')
|
||
// auch das Startdatum wird maskiert, falls der Collector je Unsinn liefert
|
||
assert.ok(!summarize(stats({ firstDay: '<i>x</i>', previous: { ...prev, complete: false } })).lead.includes('<i>'))
|
||
// ohne Vergleich kein „Das sind … mehr als zuvor“
|
||
assert.equal(neu.items[0].html, '188-mal hat dich jemand direkt kontaktiert, meist per Telefon (96).')
|
||
// 7 Tage: „7 Tagen“
|
||
assert.match(summarize(stats({ range: 7 })).lead, /in den letzten 7 Tagen .* als in den 7 Tagen davor\.$/)
|
||
})
|
||
|
||
test('summarize: zu wenig Daten → null; keine Kontakte → Hinweis nur ab 30 Besuchern', () => {
|
||
assert.equal(summarize(stats({ totals: { ...t, pv: 29 } })), null)
|
||
assert.equal(summarize(null), null)
|
||
assert.notEqual(summarize(stats({ totals: { ...t, pv: 30 } })), null)
|
||
// contacts 0, visitors 40: 'Von den Besuchern mit Zustimmung hat noch niemand über die Website angerufen oder geschrieben.'
|
||
const keine = summarize(stats({ totals: { ...t, contacts: 0, visitors: 40 }, contacts: [] }))
|
||
assert.deepEqual(keine.items[0], { icon: 'contact', html: 'Von den Besuchern mit Zustimmung hat noch niemand über die Website angerufen oder geschrieben.' })
|
||
const wenig = summarize(stats({ totals: { ...t, contacts: 0, visitors: 29 }, contacts: [] }))
|
||
assert.notEqual(wenig.items[0].icon, 'contact')
|
||
})
|
||
|
||
test('summarize: Kontakte – Vergleichssatz und häufigste Art', () => {
|
||
const html = (contactsNow, contactsBefore, kinds) => summarize(stats({
|
||
totals: { ...t, contacts: contactsNow },
|
||
contacts: kinds,
|
||
previous: { ...prev, totals: { ...prev.totals, contacts: contactsBefore } },
|
||
})).items[0].html
|
||
assert.equal(html(188, 200, [{ key: 'tel', count: 96 }]), '188-mal hat dich jemand direkt kontaktiert, meist per Telefon (96). Das sind 12 weniger als zuvor.')
|
||
assert.equal(html(188, 188, [{ key: 'mail', count: 100 }]), '188-mal hat dich jemand direkt kontaktiert, meist per E-Mail (100). Genauso oft wie zuvor.')
|
||
assert.equal(html(5, 0, [{ key: 'wa', count: 3 }]), '5-mal hat dich jemand direkt kontaktiert, meist per WhatsApp (3).') // Vorwert 0: kein Vergleichssatz
|
||
assert.equal(html(5, 2, [{ key: 'out', count: 3 }]), '5-mal hat dich jemand direkt kontaktiert, meist über einen Link nach außen (3). Das sind 3 mehr als zuvor.')
|
||
})
|
||
|
||
test('summarize: Herkunft, Gerät, Seite und Reihenfolge, höchstens 4', () => {
|
||
// refs google.com 880 von 2270 → 'Die meisten, die über einen Link kamen, kommen von Google (880).'
|
||
const s = summarize(stats())
|
||
const byIcon = Object.fromEntries(s.items.map((i) => [i.icon, i.html]))
|
||
assert.equal(byIcon.source, 'Die meisten, die über einen Link kamen, kommen von Google (880).')
|
||
// devices m 4590/6930 → '66 % der Aufrufe kommen vom Handy.'
|
||
assert.equal(byIcon.device, '66 % der Aufrufe kommen vom Handy.')
|
||
// pages / dann /preise → 'Am meisten angesehen: die Startseite, danach /preise (1.310).'
|
||
assert.equal(byIcon.page, 'Am meisten angesehen: die Startseite, danach /preise (1.310).')
|
||
assert.deepEqual(s.items.map((i) => i.icon), ['contact', 'source', 'device', 'page'])
|
||
// Computer, Seite ohne Startseite
|
||
const pc = summarize(stats({ devices: [{ key: 'd', count: 600 }, { key: 'm', count: 300 }, { key: 't', count: 100 }], pages: [{ key: '/preise', count: 1310 }, { key: '/', count: 900 }] }))
|
||
assert.equal(pc.items.find((i) => i.icon === 'device').html, '60 % der Aufrufe kommen vom Computer.')
|
||
assert.equal(pc.items.find((i) => i.icon === 'page').html, 'Am meisten angesehen: /preise (1.310 Aufrufe).')
|
||
// nur Startseite
|
||
assert.equal(summarize(stats({ pages: [{ key: '/', count: 50 }] })).items.find((i) => i.icon === 'page').html, 'Am meisten angesehen: die Startseite (50 Aufrufe).')
|
||
// Gerät unter 55 %, Herkunft unter 20 %: beide Hinweise entfallen
|
||
const flach = summarize(stats({ devices: [{ key: 'm', count: 50 }, { key: 'd', count: 50 }], refs: [{ key: 'google.com', count: 10 }, { key: '', count: 90 }] }))
|
||
assert.ok(!flach.items.some((i) => i.icon === 'device' || i.icon === 'source'))
|
||
// Direkt (leerer Host) zählt nie als Quelle, auch wenn er vorne steht
|
||
const direkt = summarize(stats({ refs: [{ key: '', count: 900 }, { key: 'google.com', count: 300 }] }))
|
||
assert.equal(direkt.items.find((i) => i.icon === 'source').html, 'Die meisten, die über einen Link kamen, kommen von Google (300).')
|
||
// Samstage 2× → Wochentag-Hinweis; alle 5 Bedingungen wahr → Seite fällt weg
|
||
const sat = (i, day) => (new Date(day + 'T00:00:00Z').getUTCDay() === 6 ? 20 : 10)
|
||
const voll = summarize(stats({ series: days('2026-09-05', 30, sat) }))
|
||
assert.deepEqual(voll.items.map((i) => i.icon), ['contact', 'day', 'source', 'device'])
|
||
assert.equal(voll.items[1].html, 'Samstag ist im Schnitt dein stärkster Tag.')
|
||
})
|
||
|
||
test('summarize: Wochentag und Spitzentag in einem Hinweis', () => {
|
||
// Samstage 20, Spitzentag Sa., 19.09. mit 371, sonst 10: beides trifft zu
|
||
const both = (i, day) => (day === '2026-09-19' ? 371 : new Date(day + 'T00:00:00Z').getUTCDay() === 6 ? 40 : 10)
|
||
const s = summarize(stats({ series: days('2026-09-05', 30, both) }))
|
||
assert.equal(s.items.find((i) => i.icon === 'day').html, 'Samstag ist im Schnitt dein stärkster Tag. Spitzentag war Sa., 19.09. mit 371 Aufrufen.')
|
||
// nur Spitzentag am Mittwoch 23.09.: die anderen Mittwoche (0) gleichen den Wochentagsschnitt aus (alle Wochentage Ø 25)
|
||
const spike = (i, day) => (day === '2026-09-23' ? 100 : new Date(day + 'T00:00:00Z').getUTCDay() === 3 ? 0 : 25)
|
||
const p = summarize(stats({ series: days('2026-09-05', 30, spike) }))
|
||
assert.equal(p.items.find((i) => i.icon === 'day').html, 'Spitzentag war Mi., 23.09. mit 100 Aufrufen.')
|
||
// weniger als 70 Aufrufe oder weniger als 7 Tage: kein Wochentag-Hinweis
|
||
assert.ok(!summarize(stats({ totals: { ...t, pv: 69 }, series: days('2026-09-05', 30, spike) })).items.some((i) => i.icon === 'day'))
|
||
assert.ok(!summarize(stats({ firstDay: '2026-09-30', series: days('2026-09-05', 30, spike) })).items.some((i) => i.icon === 'day'))
|
||
})
|
||
|
||
test('summarize: der heutige, unvollständige Tag zählt beim Wochentag-Hinweis nicht mit', () => {
|
||
const isSat = (day) => new Date(day + 'T00:00:00Z').getUTCDay() === 6
|
||
const dayHtml = (s) => s.items.find((i) => i.icon === 'day')?.html
|
||
// Reihe 04.09.–03.10.2026, heute = Samstag 03.10. mit erst 5 Aufrufen; Samstage sonst 280, alle anderen Tage 200
|
||
const today = '2026-10-03'
|
||
const sat = (i, day) => (day === today ? 5 : isSat(day) ? 280 : 200)
|
||
const series = days('2026-09-04', 30, sat)
|
||
const base = { from: '2026-09-04', to: today, series }
|
||
// zählte der Tag mit, läge der Samstag-Schnitt (225) unter 1,2 × Gesamtschnitt (204) → kein Hinweis
|
||
assert.equal(weekdayStats(series, '').best, null)
|
||
// ohne den heutigen Tag: Samstag Ø 280 ≥ 1,2 × 211, stärkster Tag; Spitzentag nein (280 < 1,8 × 211)
|
||
assert.equal(dayHtml(summarize(stats(base))), 'Samstag ist im Schnitt dein stärkster Tag.')
|
||
// Spitzentag: Mi., 16.09. mit 365 (zählte heute mit, wäre 365 ≥ 1,8 × 199 ein Spitzentag; ohne heute 365 < 1,8 × 205,7)
|
||
const wed = days('2026-09-04', 30, (i, day) => (day === today ? 5 : day === '2026-09-16' ? 365 : 200))
|
||
assert.ok(weekdayStats(wed, '').peak)
|
||
assert.equal(dayHtml(summarize(stats({ ...base, series: wed }))), undefined)
|
||
// fehlt `to`, entfällt der letzte Tag trotzdem
|
||
assert.equal(dayHtml(summarize(stats({ ...base, to: undefined }))), 'Samstag ist im Schnitt dein stärkster Tag.')
|
||
// endet die Reihe nicht auf `to`, ist der letzte Tag ein abgeschlossener Tag und zählt mit
|
||
assert.equal(dayHtml(summarize(stats({ ...base, to: '2026-10-04' }))), undefined)
|
||
// 7-Tage-Ansicht: nur 6 abgeschlossene Tage → kein Wochentag-Hinweis, auch bei deutlichem Muster
|
||
const week = days('2026-09-28', 7, (i, day) => (isSat(day) ? 500 : 100))
|
||
assert.equal(dayHtml(summarize(stats({ range: 7, from: '2026-09-28', to: '2026-10-04', series: week }))), undefined)
|
||
assert.ok(weekdayStats(week, '')) // mit allen 7 Tagen wäre es ein Ergebnis gewesen
|
||
})
|
||
|
||
test('summarize: Pfade und Hosts werden maskiert', () => {
|
||
const s = summarize(stats({ pages: [{ key: '/<img src=x>', count: 900 }], refs: [{ key: 'a"b.de', count: 900 }] }))
|
||
assert.ok(!JSON.stringify(s).includes('<img'))
|
||
assert.ok(JSON.stringify(s).includes('<img'))
|
||
assert.equal(s.items.find((i) => i.icon === 'source').html, 'Die meisten, die über einen Link kamen, kommen von a"b.de (900).')
|
||
assert.equal(s.items.find((i) => i.icon === 'page').html, 'Am meisten angesehen: /<img src=x> (900 Aufrufe).')
|
||
// auch an zweiter Stelle hinter der Startseite
|
||
const z = summarize(stats({ pages: [{ key: '/', count: 900 }, { key: '/<script>', count: 100 }] }))
|
||
assert.ok(!JSON.stringify(z).includes('<script>'))
|
||
})
|
||
|
||
test('chartModel mit compare: gemeinsame Skala, prevLine', () => {
|
||
const cur = days('2026-09-05', 5, 10)
|
||
const before = days('2026-08-06', 5, (i) => (i === 2 ? 400 : 20))
|
||
const m = chartModel(cur, { compare: before })
|
||
assert.equal(m.top, 500) // über beide Reihen (Maximum 400)
|
||
assert.equal(m.points.length, 5)
|
||
assert.deepEqual(m.points.map((p) => p.prev), [20, 20, 400, 20, 20])
|
||
assert.match(m.prevLine, /^M[\d. ]+( L[\d. ]+){4}$/)
|
||
// nach Tagesindex ausgerichtet: gleiche x-Werte wie die aktuelle Linie, y am Vorwert
|
||
const first = m.prevLine.split(' L')[0].slice(1).split(' ').map(Number)
|
||
assert.equal(first[0], m.points[0].x)
|
||
const mid = m.prevLine.split(' L')[2].split(' ').map(Number)
|
||
assert.equal(mid[0], m.points[2].x)
|
||
assert.ok(mid[1] < first[1]) // 400 liegt höher als 20
|
||
// ohne compare: wie bisher, prevLine leer
|
||
const o = chartModel(cur)
|
||
assert.equal(o.top, 10)
|
||
assert.equal(o.prevLine, '')
|
||
assert.ok(o.points.every((p) => p.prev === null))
|
||
assert.equal(o.line, chartModel(cur, { compare: [] }).line)
|
||
assert.equal(chartModel(cur, { compare: [] }).prevLine, '')
|
||
// kürzerer Vergleich: nur die vorhandenen Tage, Rest null
|
||
const k = chartModel(cur, { compare: before.slice(0, 2) })
|
||
assert.deepEqual(k.points.map((p) => p.prev), [20, 20, null, null, null])
|
||
assert.match(k.prevLine, /^M[\d. ]+ L[\d. ]+$/)
|
||
})
|