Webshop: im Abo-Modell Anfrage ohne Unternehmer-Haken, Privat-Hinweis, Einschalten
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cabab2826
commit
28da4180bd
7 files changed
+147
-22
No files matched your search
+12
-9
@@ -31,10 +31,13 @@ export const anfrageListe = (anfrage) => h('dl', { class: 'sh-fakten' }, anfrage
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Formular „Webshop anfragen“ in root. optionen: projectId, vorgabe (frueherer Stand der Anfrage zum Vorausfuellen), preis ('299 €'),
|
* Formular „Webshop anfragen“ in root. optionen: projectId, vorgabe (frueherer Stand der Anfrage zum Vorausfuellen), preis ('299 €'),
|
||||||
* ustHinweis (Satz zur Umsatzsteuer), nachSenden(anfrage) (die Anfrage ist gespeichert: die Ansicht zeichnet sich neu).
|
* ustHinweis (Satz zur Umsatzsteuer), nachSenden(anfrage) (die Anfrage ist gespeichert: die Ansicht zeichnet sich neu), modell ('abo': kein
|
||||||
|
* Preis und keine Umsatzsteuer, kein Feld „unternehmer“, statt des Hakens „keine ausgeschlossenen Waren“ ein Satz über dem Knopf; gesendet wird
|
||||||
|
* keineAusgeschlossenen: true, unternehmer entfällt – die Kundenart kennt der Server).
|
||||||
* Der Server prueft alles; ein 409 (z. B. laeuft schon eine Anfrage) zeigt seinen Text und laesst die Ansicht neu laden.
|
* Der Server prueft alles; ein 409 (z. B. laeuft schon eine Anfrage) zeigt seinen Text und laesst die Ansicht neu laden.
|
||||||
*/
|
*/
|
||||||
export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis = '299 €', ustHinweis = '', nachSenden } = {}) {
|
export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis = '299 €', ustHinweis = '', nachSenden, modell } = {}) {
|
||||||
|
const abo = modell === 'abo' // Abo-Modell (strikt): ohne Preis, ohne Unternehmer-Haken, der Satz S2 statt des Hakens
|
||||||
const v = vorgabe && typeof vorgabe === 'object' ? vorgabe : {}
|
const v = vorgabe && typeof vorgabe === 'object' ? vorgabe : {}
|
||||||
const auswahl = (liste, aktuell) => {
|
const auswahl = (liste, aktuell) => {
|
||||||
const optionen = [h('option', { value: '' }, 'Bitte wählen …'), ...liste.map(([wert, name]) => h('option', { value: wert }, name))]
|
const optionen = [h('option', { value: '' }, 'Bitte wählen …'), ...liste.map(([wert, name]) => h('option', { value: wert }, name))]
|
||||||
@@ -64,10 +67,11 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
|
|||||||
h('legend', null, 'Wie bekommen deine Kunden die Ware?'),
|
h('legend', null, 'Wie bekommen deine Kunden die Ware?'),
|
||||||
hakenFeld('versand', versand, 'Versand'),
|
hakenFeld('versand', versand, 'Versand'),
|
||||||
hakenFeld('abholung', abholung, 'Abholung')),
|
hakenFeld('abholung', abholung, 'Abholung')),
|
||||||
hakenFeld('unternehmer', unternehmer, 'Ich verkaufe als Unternehmer (nicht privat).'),
|
abo ? null : hakenFeld('unternehmer', unternehmer, 'Ich verkaufe als Unternehmer (nicht privat).'),
|
||||||
hakenFeld('keineAusgeschlossenen', ausgeschlossen, `Ich verkaufe keine ausgeschlossenen Waren (${AUSGESCHLOSSEN}).`),
|
abo ? null : hakenFeld('keineAusgeschlossenen', ausgeschlossen, `Ich verkaufe keine ausgeschlossenen Waren (${AUSGESCHLOSSEN}).`),
|
||||||
feld('nachricht', 'Noch etwas, das wir wissen sollten? (freiwillig)', nachricht),
|
feld('nachricht', 'Noch etwas, das wir wissen sollten? (freiwillig)', nachricht),
|
||||||
allgemeinerFehler(),
|
allgemeinerFehler(),
|
||||||
|
abo ? h('p', { class: 'sh-hinweis', 'data-satz': 'ausgeschlossen' }, `Mit „Anfrage senden“ bestätigst du, dass du keine ausgeschlossenen Waren verkaufst (${AUSGESCHLOSSEN}).`) : null,
|
||||||
h('div', { class: 'sh-aktionen' }, absenden))
|
h('div', { class: 'sh-aktionen' }, absenden))
|
||||||
|
|
||||||
let laeuft = false
|
let laeuft = false
|
||||||
@@ -88,8 +92,7 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
|
|||||||
artikelzahl: artikelzahl.value,
|
artikelzahl: artikelzahl.value,
|
||||||
versand: versand.checked,
|
versand: versand.checked,
|
||||||
abholung: abholung.checked,
|
abholung: abholung.checked,
|
||||||
unternehmer: unternehmer.checked,
|
...(abo ? { keineAusgeschlossenen: true } : { unternehmer: unternehmer.checked, keineAusgeschlossenen: ausgeschlossen.checked }),
|
||||||
keineAusgeschlossenen: ausgeschlossen.checked,
|
|
||||||
nachricht: nachricht.value.trim(),
|
nachricht: nachricht.value.trim(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -112,12 +115,12 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
|
|||||||
h('h3', null, 'Webshop anfragen'),
|
h('h3', null, 'Webshop anfragen'),
|
||||||
h('p', null, 'Mit dem Webshop verkaufst du direkt auf deiner Website: deine Kunden legen Artikel in den Warenkorb und bezahlen online.'),
|
h('p', null, 'Mit dem Webshop verkaufst du direkt auf deiner Website: deine Kunden legen Artikel in den Warenkorb und bezahlen online.'),
|
||||||
h('ul', null,
|
h('ul', null,
|
||||||
h('li', null, `${preis} einmalig für den Einbau in deine Website, keine laufende Gebühr von WEBklar.`),
|
h('li', null, abo ? 'Im Wartungs-Abo enthalten.' : `${preis} einmalig für den Einbau in deine Website, keine laufende Gebühr von WEBklar.`),
|
||||||
h('li', null, 'Die Zahlungen gehen direkt auf dein eigenes Stripe-Konto. Die Gebühren von Stripe zahlst du direkt an Stripe.'),
|
h('li', null, 'Die Zahlungen gehen direkt auf dein eigenes Stripe-Konto. Die Gebühren von Stripe zahlst du direkt an Stripe.'),
|
||||||
h('li', null, 'Deine Kunden zahlen mit Karte, Apple Pay, Google Pay, Klarna oder SEPA-Lastschrift.'),
|
h('li', null, 'Deine Kunden zahlen mit Karte, Apple Pay, Google Pay, Klarna oder SEPA-Lastschrift.'),
|
||||||
h('li', null, 'PayPal ist über Stripe leider nicht möglich.'),
|
h('li', null, 'PayPal ist über Stripe leider nicht möglich.'),
|
||||||
h('li', null, 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du bestellen.')),
|
h('li', null, abo ? 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du einschalten.' : 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du bestellen.')),
|
||||||
ustHinweis ? h('p', { class: 'sh-hinweis' }, ustHinweis) : null)
|
ustHinweis && !abo ? h('p', { class: 'sh-hinweis' }, ustHinweis) : null)
|
||||||
|
|
||||||
const karte = h('section', { class: 'kb-panel sh-anfrage' }, erklaerung, form)
|
const karte = h('section', { class: 'kb-panel sh-anfrage' }, erklaerung, form)
|
||||||
root.replaceChildren(karte)
|
root.replaceChildren(karte)
|
||||||
|
|||||||
+19
-6
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
import { fehlerKarte, h, infoKarte, request } from './shop-dom.js'
|
import { fehlerKarte, h, infoKarte, request } from './shop-dom.js'
|
||||||
import { anfrageKarte, anfrageZeilen, renderAnfrageFormular } from './shop-anfrage.js'
|
import { anfrageKarte, anfrageZeilen, renderAnfrageFormular } from './shop-anfrage.js'
|
||||||
import { renderBestellen, statusKarte } from './funktion-bestellen.js'
|
import { renderBestellen, renderEinschaltSeite, statusKarte } from './funktion-bestellen.js'
|
||||||
import { meldeReiter, reiterEintraege } from './fn-reiter.js'
|
import { meldeReiter, reiterEintraege } from './fn-reiter.js'
|
||||||
|
|
||||||
/** Die Reiter: jeder ein Modul mit render(panel, ctx), erst beim ersten Oeffnen geladen. */
|
/** Die Reiter: jeder ein Modul mit render(panel, ctx), erst beim ersten Oeffnen geladen. */
|
||||||
@@ -31,6 +31,8 @@ const HINWEIS_SETUP = 'Wir bauen deinen Shop gerade ein. Du kannst schon Artikel
|
|||||||
const HINWEIS_ABGESCHALTET = 'Dein Webshop ist abgeschaltet. Du kannst Bestellungen noch ansehen, bearbeiten, erstatten und exportieren.'
|
const HINWEIS_ABGESCHALTET = 'Dein Webshop ist abgeschaltet. Du kannst Bestellungen noch ansehen, bearbeiten, erstatten und exportieren.'
|
||||||
const DANKE_SETUP = 'Danke! Die Zahlung ist bei uns angekommen.'
|
const DANKE_SETUP = 'Danke! Die Zahlung ist bei uns angekommen.'
|
||||||
const NICHT_VERFUEGBAR = 'Der Webshop ist noch nicht verfügbar.'
|
const NICHT_VERFUEGBAR = 'Der Webshop ist noch nicht verfügbar.'
|
||||||
|
const PRIVAT_TITEL = 'Der Webshop ist für Betriebe' // Abo-Modell, Text S3
|
||||||
|
const PRIVAT_TEXT = 'Für Privatpersonen gibt es den Webshop leider nicht. Stimmt das nicht? Schreib uns im Chat.'
|
||||||
const SHOP_PREIS = '299 €'
|
const SHOP_PREIS = '299 €'
|
||||||
const DOKUMENT_TITEL = [
|
const DOKUMENT_TITEL = [
|
||||||
['leistungsbeschreibung', 'Leistungsbeschreibung'],
|
['leistungsbeschreibung', 'Leistungsbeschreibung'],
|
||||||
@@ -66,22 +68,22 @@ function leseRueckkehr() {
|
|||||||
return { bestellt: query.get('bestellt') === '1', stripe: stripe === 'zurueck' || stripe === 'neu' ? stripe : '', projekt: query.get('projekt') || '' }
|
return { bestellt: query.get('bestellt') === '1', stripe: stripe === 'zurueck' || stripe === 'neu' ? stripe : '', projekt: query.get('projekt') || '' }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initShop(root, { projectId } = {}) {
|
export function initShop(root, { projectId, einschalten } = {}) {
|
||||||
if (!root) return Promise.resolve()
|
if (!root) return Promise.resolve()
|
||||||
return starte(root, projectId)
|
return starte(root, projectId, einschalten)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function starte(root, projectId) {
|
async function starte(root, projectId, einschalten) {
|
||||||
const rueckkehr = leseRueckkehr()
|
const rueckkehr = leseRueckkehr()
|
||||||
const wunsch = rueckkehr.projekt || projectId || root.dataset?.wunschProjekt || '' // die Kachel in Funktionen legt ihr Projekt hier ab
|
const wunsch = rueckkehr.projekt || projectId || root.dataset?.wunschProjekt || '' // die Kachel in Funktionen legt ihr Projekt hier ab
|
||||||
if (root.dataset) delete root.dataset.wunschProjekt
|
if (root.dataset) delete root.dataset.wunschProjekt
|
||||||
if (wunsch) auswahl.projectId = wunsch
|
if (wunsch) auswahl.projectId = wunsch
|
||||||
const s = { root, projects: [], funktionen: null, projekt: null, status: null, lauf: 0, buehne: null, meldungEl: null, danke: false, stripe: '', neueAnfrage: false }
|
const s = { root, projects: [], funktionen: null, projekt: null, status: null, lauf: 0, buehne: null, meldungEl: null, danke: false, stripe: '', neueAnfrage: false, einschalten }
|
||||||
S = s
|
S = s
|
||||||
try {
|
try {
|
||||||
await ladeFunktionen(s)
|
await ladeFunktionen(s)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (S === s) root.replaceChildren(fehlerKarte(err.message, () => starte(root, auswahl.projectId)))
|
if (S === s) root.replaceChildren(fehlerKarte(err.message, () => starte(root, auswahl.projectId, einschalten)))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (S !== s) return
|
if (S !== s) return
|
||||||
@@ -217,6 +219,11 @@ function zeigeAnfrage(s, { status, aktuell }) {
|
|||||||
s.buehne.replaceChildren(anfrageKarte(ansicht, { projectId, neu: () => { s.neueAnfrage = true; zeigeAnfrage(s, { status, aktuell }) } }))
|
s.buehne.replaceChildren(anfrageKarte(ansicht, { projectId, neu: () => { s.neueAnfrage = true; zeigeAnfrage(s, { status, aktuell }) } }))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Abo-Modell: den Webshop gibt es nur fuer Betriebe; Privatkunden und eine leere oder fehlende Kundenart (E4) bekommen den Hinweis statt des Formulars
|
||||||
|
if (istAbo(s) && s.funktionen.kundenart !== 'unternehmen') {
|
||||||
|
s.buehne.replaceChildren(infoKarte(PRIVAT_TITEL, PRIVAT_TEXT, { chat: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
// keine Anfrage, zurueckgezogen oder „Neue Anfrage stellen“: das Formular, mit den frueheren Angaben vorausgefuellt
|
// keine Anfrage, zurueckgezogen oder „Neue Anfrage stellen“: das Formular, mit den frueheren Angaben vorausgefuellt
|
||||||
const wurzel = h('div', { class: 'sh-wurzel' })
|
const wurzel = h('div', { class: 'sh-wurzel' })
|
||||||
s.buehne.replaceChildren(wurzel)
|
s.buehne.replaceChildren(wurzel)
|
||||||
@@ -225,6 +232,7 @@ function zeigeAnfrage(s, { status, aktuell }) {
|
|||||||
vorgabe: ansicht?.anfrage || null,
|
vorgabe: ansicht?.anfrage || null,
|
||||||
preis: text(f?.priceLabel) || SHOP_PREIS,
|
preis: text(f?.priceLabel) || SHOP_PREIS,
|
||||||
ustHinweis: text(s.funktionen?.ustHinweis),
|
ustHinweis: text(s.funktionen?.ustHinweis),
|
||||||
|
modell: s.funktionen?.modell,
|
||||||
nachSenden: async () => { s.neueAnfrage = false; await ladeNeu(s) },
|
nachSenden: async () => { s.neueAnfrage = false; await ladeNeu(s) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -274,6 +282,11 @@ function zeigeBestellseite(s, { status, aktuell }) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const daten = bestelldaten(s, status)
|
const daten = bestelldaten(s, status)
|
||||||
|
if (istAbo(s)) {
|
||||||
|
// Abo-Modell: die Zusage der Anfrage fuehrt zum Einschalten (kein Preis, keine Haken, kein Checkout)
|
||||||
|
renderEinschaltSeite(wurzel, { ctx, daten, key: 'shop', zusammenfassung: zusammenfassungVon(daten.anfrage), einschalten: s.einschalten, nachEinschalten: () => ladeNeu(s) })
|
||||||
|
return
|
||||||
|
}
|
||||||
renderBestellen(wurzel, { ctx, daten, key: 'shop', zusammenfassung: zusammenfassungVon(daten.anfrage) })
|
renderBestellen(wurzel, { ctx, daten, key: 'shop', zusammenfassung: zusammenfassungVon(daten.anfrage) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ test('mountet den Ablauf genau einmal in den Platz, mit key, projectId und viewA
|
|||||||
assert.deepEqual([a.aufrufe[0].key, a.aufrufe[0].projectId, a.aufrufe[0].viewAs], ['shop', 'p1', false])
|
assert.deepEqual([a.aufrufe[0].key, a.aufrufe[0].projectId, a.aufrufe[0].viewAs], ['shop', 'p1', false])
|
||||||
assert.ok(a.aufrufe[0].wurzel === fx.finde(wurzel, '[data-einschalten="shop"]'))
|
assert.ok(a.aufrufe[0].wurzel === fx.finde(wurzel, '[data-einschalten="shop"]'))
|
||||||
assert.ok(fx.finde(wurzel, '.tm-einschalten-platz'))
|
assert.ok(fx.finde(wurzel, '.tm-einschalten-platz'))
|
||||||
assert.equal(fx.finde(wurzel, 'h3'), null)
|
assert.ok(fx.finde(wurzel, 'h3') === null)
|
||||||
assert.equal(fx.alle(wurzel, 'input').length, 0)
|
assert.equal(fx.alle(wurzel, 'input').length, 0)
|
||||||
assert.doesNotMatch(fx.text(wurzel), KEIN_PREIS)
|
assert.doesNotMatch(fx.text(wurzel), KEIN_PREIS)
|
||||||
const hulle = fx.finde(wurzel, 'section.kb-panel.tm-bestellen.tm-einschalten')
|
const hulle = fx.finde(wurzel, 'section.kb-panel.tm-bestellen.tm-einschalten')
|
||||||
@@ -36,7 +36,7 @@ test('Zusammenfassung mit E1; offene Punkte sperren den Ablauf (E2); „Ändern
|
|||||||
const wurzel = seite({ einschalten: a.fn, zusammenfassung: GRUPPEN, zumSchritt: (k) => schritte.push(k), daten: { offen: [{ schritt: 'zeiten', text: 'Bitte wähle das Bundesland.' }], kundenansicht: false } })
|
const wurzel = seite({ einschalten: a.fn, zusammenfassung: GRUPPEN, zumSchritt: (k) => schritte.push(k), daten: { offen: [{ schritt: 'zeiten', text: 'Bitte wähle das Bundesland.' }], kundenansicht: false } })
|
||||||
assert.equal(fx.text(fx.finde(wurzel, 'h3')), 'Prüfen und einschalten')
|
assert.equal(fx.text(fx.finde(wurzel, 'h3')), 'Prüfen und einschalten')
|
||||||
assert.equal(a.aufrufe.length, 0)
|
assert.equal(a.aufrufe.length, 0)
|
||||||
assert.equal(fx.finde(wurzel, '.tm-einschalten-platz'), null)
|
assert.ok(fx.finde(wurzel, '.tm-einschalten-platz') === null)
|
||||||
assert.match(fx.text(wurzel), /Du kannst einschalten, sobald die offenen Punkte erledigt sind\./)
|
assert.match(fx.text(wurzel), /Du kannst einschalten, sobald die offenen Punkte erledigt sind\./)
|
||||||
assert.doesNotMatch(fx.text(wurzel), /bestellen/i)
|
assert.doesNotMatch(fx.text(wurzel), /bestellen/i)
|
||||||
await fx.klick(fx.finde(wurzel, '[data-aktion="zum-schritt"][data-schritt="zeiten"]'))
|
await fx.klick(fx.finde(wurzel, '[data-aktion="zum-schritt"][data-schritt="zeiten"]'))
|
||||||
@@ -51,7 +51,7 @@ test('Zusammenfassung ohne offene Punkte: Gruppe, dann der Platz mit dem Ablauf'
|
|||||||
assert.equal(fx.text(fx.finde(wurzel, '.tm-bestellen-gruppe h4')), 'Deine Anfrage')
|
assert.equal(fx.text(fx.finde(wurzel, '.tm-bestellen-gruppe h4')), 'Deine Anfrage')
|
||||||
assert.equal(a.aufrufe.length, 1)
|
assert.equal(a.aufrufe.length, 1)
|
||||||
assert.doesNotMatch(fx.text(wurzel), /Du kannst einschalten, sobald/)
|
assert.doesNotMatch(fx.text(wurzel), /Du kannst einschalten, sobald/)
|
||||||
assert.equal(fx.finde(wurzel, '[data-aktion="aendern"]'), null, 'ohne zumSchritt kein „Ändern“')
|
assert.ok(fx.finde(wurzel, '[data-aktion="aendern"]') === null, 'ohne zumSchritt kein „Ändern“')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
test('Kundenansicht: der Ablauf bekommt viewAs true (daten und ctx)', mitDom(async () => {
|
test('Kundenansicht: der Ablauf bekommt viewAs true (daten und ctx)', mitDom(async () => {
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ for (const f of FAELLE) {
|
|||||||
test(`${f.name}, Abo: keine eigene Leiste, kn:reiter mit Kurzlabels, bei() wechselt`, mitDom(async (fx) => {
|
test(`${f.name}, Abo: keine eigene Leiste, kn:reiter mit Kurzlabels, bei() wechselt`, mitDom(async (fx) => {
|
||||||
const k = hoereAufKn(fx)
|
const k = hoereAufKn(fx)
|
||||||
const root = await oeffne(fx, f, 'abo')
|
const root = await oeffne(fx, f, 'abo')
|
||||||
assert.equal(fx.finde(root, '[role="tablist"]'), null)
|
assert.ok(fx.finde(root, '[role="tablist"]') === null)
|
||||||
assert.equal(fx.finde(root, '[role="tab"]'), null)
|
assert.ok(fx.finde(root, '[role="tab"]') === null)
|
||||||
const panel = fx.finde(root, '[role="tabpanel"]')
|
const panel = fx.finde(root, '[role="tabpanel"]')
|
||||||
assert.ok(panel, 'das Panel bleibt')
|
assert.ok(panel, 'das Panel bleibt')
|
||||||
assert.equal(panel.hasAttribute('aria-labelledby'), false)
|
assert.equal(panel.hasAttribute('aria-labelledby'), false)
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { h } from '../public/shop-dom.js'
|
||||||
|
import { initShop, vergissAuswahl } from '../public/shop.js'
|
||||||
|
import { AUSGESCHLOSSEN } from '../public/shop-anfrage.js'
|
||||||
|
import { KEIN_PREIS, einschaltenAttrappe, hoereAufKn, mitDom, routen } from './support/modulAbo.js'
|
||||||
|
|
||||||
|
// Webshop im Abo-Modell (P4 Task 5): Anfrage ohne Unternehmer-Haken und ohne Preis, Satz über dem Knopf, Hinweis für Privatkunden,
|
||||||
|
// nach der Zusage der Ablauf des Einschaltens statt der Bestellseite. Ohne modell === 'abo' bleibt alles wie heute.
|
||||||
|
|
||||||
|
const ANFRAGE = { warenart: 'kleidung', beschreibung: 'Handgemachte Mützen und Schals.', artikelzahl: 'bis20', versand: true, abholung: false, unternehmer: true, keineAusgeschlossenen: true, nachricht: '' }
|
||||||
|
const funk = (state, kopf = {}) => ({
|
||||||
|
projects: [{ projectId: 'p1', projectName: 'Mein Laden', features: { shop: { state } } }],
|
||||||
|
features: { shop: { priceLabel: '299 €', consent: { version: 'v', knopf: 'Zahlungspflichtig bestellen · 299 €', items: [] } } },
|
||||||
|
ustHinweis: 'einmalig, ohne Umsatzsteuer gemäß § 19 UStG',
|
||||||
|
legal: { shop: {} },
|
||||||
|
shop: { verfuegbar: true },
|
||||||
|
...kopf,
|
||||||
|
})
|
||||||
|
async function oeffne(fx, { kopf = {}, state = 'off', anfrage = null, einschalten } = {}) {
|
||||||
|
vergissAuswahl()
|
||||||
|
routen(fx, {
|
||||||
|
'GET /api/funktionen': { body: funk(state, kopf) },
|
||||||
|
'GET /api/shop/status?projectId=p1': { body: { verfuegbar: true, state, ticketWoid: null, anfrage } },
|
||||||
|
'POST /api/shop/anfrage': { body: { anfrage: {} } },
|
||||||
|
})
|
||||||
|
const root = h('div')
|
||||||
|
document.body.append(root)
|
||||||
|
await initShop(root, { projectId: 'p1', einschalten })
|
||||||
|
await fx.ruhe()
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
const ZUGESAGT = { status: 'angenommen', anfrage: ANFRAGE, at: '2026-10-05T10:30:00.000Z' }
|
||||||
|
|
||||||
|
test('Abo, Betrieb: Anfrage ohne Unternehmer-Haken, Satz S2 über dem Knopf, kein Preis; Körper ohne unternehmer', mitDom(async (fx) => {
|
||||||
|
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' } })
|
||||||
|
assert.ok(fx.finde(root, '[data-feld="unternehmer"]') === null)
|
||||||
|
assert.ok(fx.finde(root, '[data-feld="keineAusgeschlossenen"]') === null)
|
||||||
|
const satz = fx.finde(root, '[data-satz="ausgeschlossen"]')
|
||||||
|
assert.equal(fx.text(satz), `Mit „Anfrage senden“ bestätigst du, dass du keine ausgeschlossenen Waren verkaufst (${AUSGESCHLOSSEN}).`)
|
||||||
|
const knoten = fx.alle(root, '*')
|
||||||
|
assert.ok(knoten.indexOf(satz) < knoten.indexOf(fx.finde(root, '[data-aktion="anfrage-senden"]')), 'Satz steht über dem Knopf')
|
||||||
|
const erklaerung = fx.text(fx.finde(root, '.sh-erklaerung'))
|
||||||
|
assert.match(erklaerung, /Im Wartungs-Abo enthalten\./)
|
||||||
|
assert.match(erklaerung, /Wir sehen uns deine Anfrage kurz an\. Erst nach unserer Zusage kannst du einschalten\./)
|
||||||
|
assert.doesNotMatch(erklaerung, KEIN_PREIS)
|
||||||
|
assert.doesNotMatch(erklaerung, /bestellen/)
|
||||||
|
assert.doesNotMatch(fx.text(root), KEIN_PREIS)
|
||||||
|
await fx.setze(fx.finde(root, '[data-feld="warenart"]'), 'kleidung')
|
||||||
|
await fx.setze(fx.finde(root, '[data-feld="beschreibung"]'), 'Handgemachte Mützen und Schals.')
|
||||||
|
await fx.setze(fx.finde(root, '[data-feld="artikelzahl"]'), 'bis20')
|
||||||
|
await fx.klick(fx.finde(root, '[data-feld="versand"]'))
|
||||||
|
await fx.klick(fx.finde(root, '[data-aktion="anfrage-senden"]'))
|
||||||
|
assert.deepEqual(fx.fetchLog.find((a) => a.method === 'POST').body, {
|
||||||
|
projectId: 'p1',
|
||||||
|
anfrage: { warenart: 'kleidung', beschreibung: 'Handgemachte Mützen und Schals.', artikelzahl: 'bis20', versand: true, abholung: false, keineAusgeschlossenen: true, nachricht: '' },
|
||||||
|
})
|
||||||
|
}))
|
||||||
|
|
||||||
|
test('Abo, privat oder Kundenart leer/fehlend: Hinweis S3 mit Chat, nie das Formular, kein POST, leere Reiterliste', mitDom(async (fx) => {
|
||||||
|
const k = hoereAufKn(fx)
|
||||||
|
for (const kundenart of ['privat', '', undefined]) {
|
||||||
|
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart } })
|
||||||
|
assert.ok(fx.finde(root, 'form') === null, String(kundenart))
|
||||||
|
assert.match(fx.text(root), /Der Webshop ist für Betriebe/)
|
||||||
|
assert.match(fx.text(root), /Für Privatpersonen gibt es den Webshop leider nicht\. Stimmt das nicht\? Schreib uns im Chat\./)
|
||||||
|
assert.ok(fx.finde(root, 'button[data-view="chat"]'))
|
||||||
|
}
|
||||||
|
assert.equal(fx.fetchLog.filter((a) => a.method === 'POST').length, 0)
|
||||||
|
assert.deepEqual(k.reiter.at(-1).eintraege, [])
|
||||||
|
}))
|
||||||
|
|
||||||
|
test('Abo, privat mit bestehendem Shop (active): die fünf Reiter bleiben', mitDom(async (fx) => {
|
||||||
|
const k = hoereAufKn(fx)
|
||||||
|
await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'privat' }, state: 'active' })
|
||||||
|
assert.equal(k.reiter.at(-1).eintraege.length, 5)
|
||||||
|
}))
|
||||||
|
|
||||||
|
test('Abo, Anfrage angenommen: Gruppe B1 und Ablauf key shop statt Bestellseite; onFertig lädt neu', mitDom(async (fx) => {
|
||||||
|
const a = einschaltenAttrappe()
|
||||||
|
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' }, anfrage: ZUGESAGT, einschalten: a.fn })
|
||||||
|
assert.equal(a.aufrufe[0].key, 'shop')
|
||||||
|
assert.equal(a.aufrufe[0].projectId, 'p1')
|
||||||
|
assert.equal(fx.text(fx.finde(root, '.tm-bestellen-gruppe h4')), 'Deine Anfrage')
|
||||||
|
assert.ok(fx.finde(root, '[data-aktion="bestellen"]') === null)
|
||||||
|
assert.doesNotMatch(fx.text(root), KEIN_PREIS)
|
||||||
|
a.fertig(0)
|
||||||
|
await fx.ruhe()
|
||||||
|
assert.equal(fx.fetchLog.filter((x) => x.url === '/api/shop/status?projectId=p1').length, 2)
|
||||||
|
}))
|
||||||
|
|
||||||
|
test('Abo, Anfrage angenommen, ohne Funktion einschalten: Fehlerkarte E4', mitDom(async (fx) => {
|
||||||
|
const root = await oeffne(fx, { kopf: { modell: 'abo', kundenart: 'unternehmen' }, anfrage: ZUGESAGT })
|
||||||
|
assert.match(fx.text(root), /Das Einschalten ist gerade nicht verfügbar\./)
|
||||||
|
}))
|
||||||
|
|
||||||
|
test('Ohne modell (fehlt, „Abo“): Formular mit Unternehmer-Haken und 299 €, nach der Zusage die Bestellseite, der Ablauf wird nie gerufen', mitDom(async (fx) => {
|
||||||
|
for (const modell of [undefined, 'Abo']) {
|
||||||
|
const a = einschaltenAttrappe()
|
||||||
|
const kopf = modell ? { modell } : {}
|
||||||
|
const formular = await oeffne(fx, { kopf, einschalten: a.fn })
|
||||||
|
assert.ok(fx.finde(formular, '[data-feld="unternehmer"]'))
|
||||||
|
assert.ok(fx.finde(formular, '[data-feld="keineAusgeschlossenen"]'))
|
||||||
|
assert.match(fx.text(formular), /299 € einmalig/)
|
||||||
|
const zugesagt = await oeffne(fx, { kopf, anfrage: ZUGESAGT, einschalten: a.fn })
|
||||||
|
assert.ok(fx.finde(zugesagt, '[data-aktion="bestellen"]'))
|
||||||
|
assert.equal(a.aufrufe.length, 0)
|
||||||
|
}
|
||||||
|
}))
|
||||||
@@ -45,7 +45,7 @@ test('Abo, alles bestätigt: letzter Schritt E1 mountet den Ablauf, ohne Haken,
|
|||||||
assert.match(fx.text(fx.finde(wurzel, '.tm-schritt-stand')), /Prüfen und einschalten/)
|
assert.match(fx.text(fx.finde(wurzel, '.tm-schritt-stand')), /Prüfen und einschalten/)
|
||||||
assert.deepEqual(a.aufrufe.map((x) => [x.key, x.projectId, x.viewAs]), [['termine', 'p1', false]])
|
assert.deepEqual(a.aufrufe.map((x) => [x.key, x.projectId, x.viewAs]), [['termine', 'p1', false]])
|
||||||
assert.equal(fx.alle(wurzel, 'input[type="checkbox"]').length, 0, 'auch kein Haken „gesundheit“')
|
assert.equal(fx.alle(wurzel, 'input[type="checkbox"]').length, 0, 'auch kein Haken „gesundheit“')
|
||||||
for (const sel of ['[data-aktion="bestellen"]', '.tm-bestellen-preis', '.tm-rechtstext', '.tm-bestellen-zustimmung']) assert.equal(fx.finde(wurzel, sel), null, sel)
|
for (const sel of ['[data-aktion="bestellen"]', '.tm-bestellen-preis', '.tm-rechtstext', '.tm-bestellen-zustimmung']) assert.ok(fx.finde(wurzel, sel) === null, sel)
|
||||||
assert.equal(fx.fetchLog.filter((x) => x.method === 'POST').length, 0)
|
assert.equal(fx.fetchLog.filter((x) => x.method === 'POST').length, 0)
|
||||||
assert.deepEqual(k.reiter.at(-1).eintraege, [])
|
assert.deepEqual(k.reiter.at(-1).eintraege, [])
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ test('app.js: VIEW_HASHES, viewRefreshers, FEATURE_LABELS und APP_PARENT kennen
|
|||||||
|
|
||||||
test('shop.js und shop.css: die Hülle exportiert initShop, die Styles hängen an #view-webshop und sh-', () => {
|
test('shop.js und shop.css: die Hülle exportiert initShop, die Styles hängen an #view-webshop und sh-', () => {
|
||||||
const js = lies('public/shop.js')
|
const js = lies('public/shop.js')
|
||||||
assert.match(js, /export function initShop\(root, \{ projectId \} = \{\}\)/)
|
assert.match(js, /export function initShop\(root, \{ projectId, einschalten \} = \{\}\)/)
|
||||||
assert.doesNotMatch(js.replace(/\/\*[\s\S]*?\*\//g, ''), /innerHTML|insertAdjacentHTML|outerHTML|document\.write/, 'Text nie als HTML')
|
assert.doesNotMatch(js.replace(/\/\*[\s\S]*?\*\//g, ''), /innerHTML|insertAdjacentHTML|outerHTML|document\.write/, 'Text nie als HTML')
|
||||||
const css = lies('public/shop.css')
|
const css = lies('public/shop.css')
|
||||||
assert.match(css, /#view-webshop/)
|
assert.match(css, /#view-webshop/)
|
||||||
|
|||||||
Reference in new issue
Block a user