Files
Webklar-Kundenbereich/test/buchungenUi.test.js
T
2026-10-07 14:06:48 +00:00

167 lines
8.4 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 { installFakeDom } from './support/fakeDom.js'
import { h, request } from '../public/shop-dom.js'
import { REITER } from '../public/buchungen.js'
import { render, zeitraumText, naechteText } from '../public/buchungen-tab-anfragen.js'
import { summeLive } from '../public/buchungen-posten.js'
// Task 12: Gerüst und Reiter „Anfragen“ gegen eine Anfrage-Attrappe nach docs/funktionen/unterkunft-api.md.
const POSTEN = [
{ text: 'Übernachtung', menge: 7, einzelCent: 5500, ustProzent: 7 },
{ text: 'Reinigung', menge: 1, einzelCent: 6000, ustProzent: 7 },
{ text: 'Direktbuchungsrabatt 10 %', menge: 1, einzelCent: -3850, ustProzent: 7 },
]
const zeile = (nr, extra = {}) => ({ id: `a${nr}`, nummer: `A-${nr}`, von: '2026-11-12', bis: '2026-11-30', gast: { name: `Gast ${nr}` }, status: 'neu', summen: { bruttoCent: 40650 }, ...extra })
const anfrage = (extra = {}) => ({
id: 'a1', nummer: 'A-1', von: '2026-11-12', bis: '2026-11-19', personen: 2, status: 'neu', steuerart: 'regelbesteuert', unterkunftName: 'Haus am See',
gast: { name: 'Anna <b>Muster</b>', email: 'anna@example.com', telefon: '0151', nachricht: 'Hund dabei' }, ...extra,
})
const vorschlag = { naechte: 7, posten: POSTEN, summen: { bruttoCent: 40650 } }
const mit = (route, fn) => async () => {
const fx = installFakeDom({ antworten: Array.from({ length: 40 }, () => route) })
try { await fn(fx) } finally { fx.restore() }
}
function baueCtx() {
const ctx = { projectId: 'p1', status: { state: 'active' }, nurLesen: false, meldungen: [], aktuell: () => true, request, zeigeMeldung: (t, a) => ctx.meldungen.push({ text: t, art: a }), neuLaden: async () => ctx.status }
return ctx
}
async function zeige(fx) {
const ctx = baueCtx()
const panel = h('div')
fx.document.body.append(panel)
await render(panel, ctx)
await fx.ruhe()
return { panel, ctx }
}
const server = (o = {}) => (a) => {
const key = `${a.method} ${a.url.split('?')[0]}`
if (o[key]) return o[key](a)
if (a.method === 'GET' && a.url.startsWith('/api/unterkunft/anfragen?')) return { body: { anfragen: [zeile(1), zeile(2, { status: 'gebucht' })] } }
if (a.method === 'GET' && a.url.startsWith('/api/unterkunft/anfragen/')) return { body: { anfrage: anfrage(), vorschlag } }
return { body: {} }
}
const posts = (fx) => fx.fetchLog.filter((a) => a.method !== 'GET')
const aktion = (fx, el, n) => fx.finde(el, `[data-aktion="${n}"]`)
const oeffne = async (fx, panel, id = 'a1') => { await fx.klick(fx.finde(panel, `[data-anfrage="${id}"]`)); await fx.ruhe() }
test('REITER: fünf Reiter mit den Labels', () => {
assert.deepEqual(REITER.map((r) => r.key), ['anfragen', 'kalender', 'unterkuenfte', 'rechnungen', 'einstellungen'])
assert.deepEqual(REITER.map((r) => r.label), ['Anfragen', 'Kalender', 'Unterkünfte & Preise', 'Rechnungen', 'Einstellungen'])
})
test('summeLive spiegelt summiere: 40650 / 37991 / 2659', () => {
const s = summeLive(POSTEN, 'regelbesteuert')
assert.equal(s.bruttoCent, 40650)
assert.equal(s.nettoCent, 37991)
assert.equal(s.ust[0].steuerCent, 2659)
const k = summeLive(POSTEN, 'kleinunternehmer')
assert.deepEqual(k, { bruttoCent: 40650, nettoCent: 40650, ust: [] })
})
test('Zeitraum und Nächte', () => {
assert.equal(zeitraumText('2026-11-12', '2026-11-30'), '12.–30.11.2026')
assert.equal(zeitraumText('2026-10-28', '2026-11-03'), '28.10.–03.11.2026')
assert.equal(zeitraumText('2026-12-28', '2027-01-03'), '28.12.2026–03.01.2027')
assert.equal(naechteText('2026-11-12', '2026-11-30'), '18 Nächte')
assert.equal(naechteText('2026-11-12', '2026-11-13'), '1 Nacht')
})
test('Liste: Nummer, Zeitraum, Gast, Status-Pille, Betrag; Filter offen als Standard', mit(server(), async (fx) => {
const { panel } = await zeige(fx)
assert.equal(fx.fetchLog[0].url, '/api/unterkunft/anfragen?projectId=p1&filter=offen')
const z = fx.alle(panel, '[data-anfrage]')
assert.equal(z.length, 2)
const t = fx.text(z[0])
for (const re of [/A-1/, /12\.–30\.11\.2026 · 18 Nächte/, /Gast 1/, /Neu/, /406,50\s€/]) assert.match(t, re)
assert.match(fx.text(z[1]), /Gebucht/)
await fx.setze(fx.finde(panel, '#bk-a-filter'), 'alle')
await fx.ruhe()
assert.equal(fx.fetchLog.at(-1).url, '/api/unterkunft/anfragen?projectId=p1&filter=alle')
}))
test('Detail: Vorschlag editierbar, Summe aktualisiert sich, Freigeben sendet posten/fristTage/nachricht und zeigt den Link', mit(server({
'POST /api/unterkunft/anfragen/a1/freigeben': () => ({ body: { buchungsLink: 'https://x.test/buchen/a1?t=abc' } }),
}), async (fx) => {
const { panel, ctx } = await zeige(fx)
await oeffne(fx, panel)
assert.match(fx.text(panel), /Anna <b>Muster<\/b>/)
assert.equal(fx.finde(panel, 'b'), null, 'Fremdtext bleibt Text')
assert.match(fx.text(fx.finde(panel, '[data-summen]')), /Gesamt 406,50\s€/)
const zeilen = fx.alle(panel, '[data-zeile]')
assert.equal(zeilen.length, 3)
const einzel = fx.finde(zeilen[1], '[data-posten="einzel"]')
await fx.setze(einzel, '70,00')
assert.match(fx.text(fx.finde(panel, '[data-summen]')), /Gesamt 416,50\s€/)
await fx.setze(einzel, '60,00')
await fx.setze(fx.finde(panel, '#bk-a-frist'), '5')
await fx.setze(fx.finde(panel, '#bk-a-nachricht'), 'Gern!')
await fx.klick(aktion(fx, panel, 'freigeben'))
await fx.ruhe()
const [p] = posts(fx)
assert.equal(p.url, '/api/unterkunft/anfragen/a1/freigeben?projectId=p1')
assert.deepEqual(p.body, { posten: POSTEN, fristTage: 5, nachricht: 'Gern!' })
assert.equal(fx.finde(panel, '[data-link]').getAttribute('value'), 'https://x.test/buchen/a1?t=abc')
assert.ok(aktion(fx, panel, 'link-kopieren'))
assert.equal(ctx.meldungen.at(-1).art, 'ok')
}))
test('Freigeben: 409 zeigt „Der Zeitraum ist inzwischen belegt.“', mit(server({
'POST /api/unterkunft/anfragen/a1/freigeben': () => ({ status: 409, body: { fehler: 'zeitraum_belegt' } }),
}), async (fx) => {
const { panel } = await zeige(fx)
await oeffne(fx, panel)
await fx.klick(aktion(fx, panel, 'freigeben'))
await fx.ruhe()
assert.match(fx.text(panel), /Der Zeitraum ist inzwischen belegt\./)
assert.equal(aktion(fx, panel, 'freigeben').disabled, false)
}))
test('Ablehnen sendet den Text', mit(server({ 'POST /api/unterkunft/anfragen/a1/ablehnen': () => ({ body: {} }) }), async (fx) => {
const { panel } = await zeige(fx)
await oeffne(fx, panel)
await fx.klick(aktion(fx, panel, 'ablehnen'))
await fx.setze(fx.finde(panel, '#bk-a-ablehn'), 'Leider belegt')
await fx.klick(aktion(fx, panel, 'ablehnen-senden'))
await fx.ruhe()
assert.deepEqual(posts(fx)[0].body, { text: 'Leider belegt' })
}))
const gebucht = (extra = {}) => ({ anfrage: anfrage({ status: 'gebucht', posten: POSTEN, summen: { bruttoCent: 40650 }, rechnungId: 'r1', rechnungNummer: 'R-2026-001', ...extra }) })
test('Gebucht: Rechnung-Link, „Als bezahlt (vor Ort)“, Storno ohne Erstattungsfeld bei Vor-Ort-Zahlung', mit(server({
'GET /api/unterkunft/anfragen/a1': () => ({ body: gebucht({ zahlart: 'vorort' }) }),
'POST /api/unterkunft/anfragen/a1/bezahlt': () => ({ body: {} }),
'POST /api/unterkunft/anfragen/a1/stornieren': () => ({ body: {} }),
}), async (fx) => {
const { panel } = await zeige(fx)
await oeffne(fx, panel)
assert.equal(aktion(fx, panel, 'rechnung').getAttribute('href'), '/api/unterkunft/rechnungen/r1.pdf?projectId=p1')
await fx.klick(aktion(fx, panel, 'bezahlt'))
await fx.ruhe()
assert.equal(posts(fx)[0].url, '/api/unterkunft/anfragen/a1/bezahlt?projectId=p1')
await fx.klick(aktion(fx, panel, 'stornieren'))
assert.equal(fx.finde(panel, '#bk-a-erstattung'), null)
await fx.setze(fx.finde(panel, '#bk-a-grund'), 'Gast krank')
await fx.klick(aktion(fx, panel, 'storno-senden'))
await fx.ruhe()
assert.deepEqual(posts(fx).at(-1).body, { grund: 'Gast krank', erstattungCent: 0 })
}))
test('Storno bei Online-Zahlung: Erstattungsbetrag wird gesendet', mit(server({
'GET /api/unterkunft/anfragen/a1': () => ({ body: gebucht({ status: 'bezahlt', zahlart: 'online', bezahltVia: 'online' }) }),
'POST /api/unterkunft/anfragen/a1/stornieren': () => ({ body: {} }),
}), async (fx) => {
const { panel } = await zeige(fx)
await oeffne(fx, panel)
assert.equal(aktion(fx, panel, 'bezahlt'), null)
await fx.klick(aktion(fx, panel, 'stornieren'))
assert.equal(fx.finde(panel, '#bk-a-erstattung').value, '406,50')
await fx.setze(fx.finde(panel, '#bk-a-erstattung'), '200,00')
await fx.klick(aktion(fx, panel, 'storno-senden'))
await fx.ruhe()
assert.equal(posts(fx)[0].body.erstattungCent, 20000)
}))