Files
Webklar-Kundenbereich/test/knAnsichtBestellen.test.js
T

169 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test'
import assert from 'node:assert/strict'
import { zeigeBestellen, produktAusRoute } from '../public/neu/ansicht-bestellen.js'
import { mit, me, aboAntwort, angebot, neueWurzel } from './support/knDaten.js'
import { NEU, TERMS_URL, meMit, aboNeu } from './support/knBestellDaten.js'
// Bestellseite im schlanken Muster (Spec 5.6, Fassung 6): Block, Linkzeile, Häkchen nur privat, Knopf, Kundenart-Zeile
const route = (produkt) => ({ bereich: 'vertraege', etage: 'bestellen', produkt, query: '' })
// fmtEuro setzt ein geschütztes Leerzeichen vor das Euro-Zeichen
const eur = (text) => text.replaceAll(' €', '\u00a0€')
const IDS = ['abo-block', 'abo-linkzeile', 'abo-beginn', 'abo-error', 'abo-buy', 'abo-kundenart']
/** Seite zeigen; die Titel, die die Ansicht meldet (Ereignis kn:titel), sammelt fx.titel. */
async function zeige(fx, daten, meDaten, produkt = 'wartung') {
fx.titel = fx.titel ?? []
const w = neueWurzel(fx)
w.addEventListener('kn:titel', (e) => fx.titel.push(e.detail.text))
await zeigeBestellen(w, { route: route(produkt), me: meDaten, nav: 'push', daten })
await fx.ruhe()
return w
}
/** Die Ids aus IDS in Dokumentreihenfolge unterhalb von #abo-order. */
const reihe = (fx, w) => fx.alle(fx.finde(w, '#abo-order'), (el) => IDS.includes(el.id)).map((el) => el.id)
test('Betrieb: Block, Linkzeile, Knopf, Kundenart-Zeile in dieser Reihenfolge, ohne Häkchen und ohne Klappe', mit({}, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('unternehmen'))
assert.deepEqual(reihe(fx, w), ['abo-block', 'abo-linkzeile', 'abo-error', 'abo-buy', 'abo-kundenart'])
assert.equal(fx.finde(w, '#abo-buy').textContent, 'Zahlungspflichtig bestellen')
assert.equal(fx.alle(w, 'details, dialog, input[type="radio"], input[type="checkbox"]').length, 0)
const t = fx.text(fx.finde(w, '#abo-block'))
for (const s of ['Leistung', 'Preis', 'Fällig', 'Beginn', 'Laufzeit', 'Zahlung', '50,00', 'gemäß § 19 UStG ohne Umsatzsteuer', 'Heute fällig: 50,00']) assert.ok(t.includes(s), s)
// die Zeilen „Anbieter“ und „Abrechnung“ entfallen (N37); das Wort steht nur im Satz zur Kündigung („Abrechnungsmonats“)
assert.deepEqual(fx.alle(fx.finde(w, '#abo-block'), 'dt').map((e) => fx.text(e)), ['Leistung', 'Preis', 'Fällig', 'Beginn', 'Laufzeit', 'Zahlung'])
assert.equal(fx.text(fx.finde(w, '#abo-start-summary')), 'sofort')
assert.equal(fx.text(fx.finde(w, '#abo-kundenart')), 'Du bestellst als Betrieb · Stimmt nicht? Schreib uns im Chat')
assert.equal(fx.finde(w, '#abo-linkzeile a').getAttribute('href'), TERMS_URL)
assert.equal(fx.text(fx.finde(w, '#abo-linkzeile')), 'Es gelten die Vertragsbedingungen mit Leistungsbeschreibung, Auftragsverarbeitung und Widerrufsbelehrung (Fassung ' + NEU + '). Alles kommt danach auch als PDF per Mail. Datenschutz')
}))
test('Block: feste Zeilentexte (Preis, Laufzeit, Zahlung)', mit({}, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('unternehmen'))
const zeilen = fx.alle(fx.finde(w, '#abo-block'), '.kn-block-zeile').map((z) => [fx.text(fx.finde(z, 'dt')), fx.text(fx.finde(z, 'dd'))])
assert.deepEqual(zeilen.map(([l]) => l), ['Leistung', 'Preis', 'Fällig', 'Beginn', 'Laufzeit', 'Zahlung'])
assert.deepEqual(zeilen[0], ['Leistung', 'WEBklar Wartungs-Abo: Wartung'])
assert.deepEqual(zeilen[1], ['Preis', eur('50,00 € pro Monat (Gesamtpreis, gemäß § 19 UStG ohne Umsatzsteuer), keine weiteren Kosten')])
assert.deepEqual(zeilen[4], ['Laufzeit', 'Mindestlaufzeit 12 Monate ab Leistungsbeginn, kündbar zum Ende der Mindestlaufzeit bis einen Monat vorher; danach jederzeit zum Ende des Abrechnungsmonats kündbar'])
assert.deepEqual(zeilen[5], ['Zahlung', 'Kreditkarte oder SEPA-Lastschrift über Stripe; die Zahlungsart hinterlegst du im nächsten Schritt, bis zum Abschluss dort kannst du abbrechen'])
}))
test('Titel: Produktname als Ereignis, keine eigene h1 (P2, P1 VF11)', mit({}, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('unternehmen'))
assert.deepEqual(fx.titel, ['Wartungs-Abo'])
assert.equal(fx.alle(w, 'h1').length, 0)
assert.deepEqual(fx.alle(w, 'h2').map((e) => fx.text(e)), ['Was du bestellst'])
}))
test('Betrieb: Body ohne earlyStart, lateStartAt, startChoice und Kundenart', mit({ 'POST /api/abo/checkout': { url: 'https://checkout.stripe.test/x' } }, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('unternehmen'))
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.deepEqual(fx.fetchLog.at(-1), { method: 'POST', url: '/api/abo/checkout', body: { productKey: 'wartung', acceptTerms: true, termsVersion: NEU, offerId: '' } })
assert.equal(fx.window.location.href, 'https://checkout.stripe.test/x')
}))
test('Privat und leer: ein Häkchen aus dem Wortlaut, ungehakt nach der Frist, gehakt sofort', mit({ 'POST /api/abo/checkout': { url: 'https://checkout.stripe.test/x' } }, async (fx) => {
for (const m of [meMit('privat'), meMit('')]) {
const w = await zeige(fx, aboNeu(), m)
assert.deepEqual(reihe(fx, w), ['abo-block', 'abo-linkzeile', 'abo-beginn', 'abo-error', 'abo-buy', 'abo-kundenart'])
assert.equal(fx.finde(w, '#abo-beginn').checked, false)
assert.equal(fx.text(fx.finde(w, '#abo-beginn-text')), aboNeu().bestelltexte.abo.haken)
assert.ok(fx.text(fx.finde(w, '#abo-start-summary')).startsWith('nach Ablauf der 14-tägigen Widerrufsfrist (am '))
assert.ok(fx.text(fx.finde(w, '#abo-due-summary')).startsWith('Erste Abbuchung am '))
assert.ok(fx.text(fx.finde(w, '#abo-kundenart')).startsWith('Du bestellst privat · '))
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.deepEqual(fx.fetchLog.at(-1).body, { productKey: 'wartung', acceptTerms: true, earlyStart: false, termsVersion: NEU, offerId: '', lateStartAt: aboNeu().lateStartAt })
const v = await zeige(fx, aboNeu(), m)
await fx.klick(fx.finde(v, '#abo-beginn'))
assert.ok(fx.text(fx.finde(v, '#abo-start-summary')).startsWith('sofort, auf deinen ausdrücklichen Wunsch'))
assert.ok(fx.text(fx.finde(v, '#abo-due-summary')).startsWith('Heute fällig: '))
await fx.klick(fx.finde(v, '#abo-buy'))
await fx.ruhe()
assert.equal(fx.fetchLog.at(-1).body.earlyStart, true)
await fx.klick(fx.finde(v, '#abo-beginn')) // Häkchen wieder weg: die Zeilen folgen
assert.ok(fx.text(fx.finde(v, '#abo-start-summary')).startsWith('nach Ablauf der 14-tägigen Widerrufsfrist'))
}
}))
test('Der Body enthält nie kundenart oder startChoice', mit({ 'POST /api/abo/checkout': { url: 'https://checkout.stripe.test/x' } }, async (fx) => {
for (const m of [meMit('unternehmen'), meMit('privat'), meMit('')]) {
const w = await zeige(fx, aboNeu(), m)
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.deepEqual(Object.keys(fx.fetchLog.at(-1).body).filter((k) => /kundenart|startChoice|unternehmer/i.test(k)), [])
}
}))
test('Angebot: Angebotsnummer im Body, Text in Abschnitt und Preiszeile', mit({}, async (fx) => {
const d = aboNeu({ offers: { hosting: null, wartung: angebot() } })
const w = await zeige(fx, d, meMit('unternehmen'))
assert.ok(fx.text(fx.finde(w, '#abo-angebot')).includes(angebot().text))
assert.ok(fx.text(fx.finde(w, '#abo-block')).includes(`individuelles Angebot: ${angebot().text}`))
assert.ok(fx.text(fx.finde(w, '#abo-block')).includes(eur('25,00 € pro Monat für die ersten 2 Monate, danach 50,00 € pro Monat')))
assert.ok(fx.text(fx.finde(w, '#abo-due-summary')).startsWith('Heute fällig: 25,00'))
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.equal(fx.fetchLog.at(-1).body.offerId, angebot().id)
}))
test('Ohne Wortlaut, nicht verfügbar oder unbekanntes Produkt: nur der Satz, kein Knopf; unsichere Linkadressen werden Text', mit({}, async (fx) => {
const satz = 'Die Online-Bestellung wird gerade eingerichtet – schreib uns gern im Chat.'
for (const d of [aboAntwort({ termsVersion: NEU }), aboNeu({ available: false })]) {
const w = await zeige(fx, d, meMit('unternehmen'))
assert.ok(fx.text(w).includes(satz))
assert.equal(fx.finde(w, '#abo-buy'), null)
}
assert.equal(fx.finde(await zeige(fx, aboNeu(), meMit('unternehmen'), 'gibtsnicht'), '#abo-buy'), null)
const boese = aboNeu()
boese.bestelltexte.abo.linkzeile.urls = ['javascript:alert(1)', '//evil.example']
assert.equal(fx.alle(await zeige(fx, boese, meMit('unternehmen')), '#abo-linkzeile a[href]').length, 0)
}))
test('produktAusRoute', () => {
assert.equal(produktAusRoute({ produkt: 'wartung' }, ''), 'wartung')
assert.equal(produktAusRoute({}, '#bestellen-hosting'), 'hosting')
assert.equal(produktAusRoute({}, '#abo'), '')
})
test('Fehler der Zahlungsseite: Text in #abo-error, Knopf zurück; unsichere Zahlungsadresse leitet nicht weiter', mit({ 'POST /api/abo/checkout': () => ({ status: 400, body: { error: 'Kaputt.' } }) }, async (fx) => {
const w = await zeige(fx, aboNeu(), meMit('unternehmen'))
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.equal(fx.text(fx.finde(w, '#abo-error')), 'Kaputt.')
assert.equal(fx.finde(w, '#abo-error').hasAttribute('hidden'), false)
assert.equal(fx.finde(w, '#abo-buy').hasAttribute('disabled'), false)
assert.equal(fx.finde(w, '#abo-buy').textContent, 'Zahlungspflichtig bestellen')
fx.tabelle['POST /api/abo/checkout'] = () => ({ body: { url: 'javascript:alert(1)' } })
await fx.klick(fx.finde(w, '#abo-buy'))
await fx.ruhe()
assert.notEqual(fx.window.location.href, 'javascript:alert(1)')
assert.equal(fx.text(fx.finde(w, '#abo-error')), 'Die Zahlungsseite konnte nicht geöffnet werden. Bitte versuche es später noch einmal.')
assert.equal(fx.finde(w, '#abo-buy').hasAttribute('disabled'), false)
}))
test('Ansichtsmodus sperrt Knopf und Häkchen, Testkonto-Hinweis in du', mit({}, async (fx) => {
const v = await zeige(fx, aboNeu({ testAccount: true }), meMit('privat', { viewAs: true }))
assert.equal(fx.finde(v, '#abo-buy').hasAttribute('disabled'), true)
assert.equal(fx.finde(v, '#abo-beginn').hasAttribute('disabled'), true)
assert.match(fx.text(v), /Im Ansichtsmodus nicht möglich\./)
assert.match(fx.text(v), /Testkonto: Bei Stripe wird ein Test-Rabatt verrechnet, abgebucht werden nur 0,50 € pro Monat\./) // Literal im Quelltext mit normalem Leerzeichen
await fx.klick(fx.finde(v, '#abo-buy'))
assert.equal(fx.fetchLog.length, 0)
}))
test('Kein Abo geladen: nur der Hinweis', mit({ 'GET /api/abo': () => ({ status: 500, body: { error: 'x' } }) }, async (fx) => {
const v = await zeige(fx, undefined, me())
assert.match(fx.text(v), /Deine Verträge konnten gerade nicht geladen werden\./)
}))
test('Quelltext: kein Sie, keine Klappe, kein startChoice', async () => {
const { readFileSync } = await import('node:fs')
const q = readFileSync(new URL('../public/neu/ansicht-bestellen.js', import.meta.url), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
assert.doesNotMatch(q, /\b(Sie|Ihr(e[nmrs]?)?|Ihnen)\b/)
assert.doesNotMatch(q, /details|dialog|innerHTML/)
const aufruf = q.slice(q.indexOf("api('/api/abo/checkout'"), q.indexOf('const ziel'))
assert.ok(aufruf.includes('acceptTerms: true'), 'der Aufruf der Bestellung ist gefunden')
assert.doesNotMatch(aufruf, /startChoice|kundenart|unternehmer/)
})