Angebotsübersicht und Buchung verbinden, Mailpit einrichten
parent
762f21dfa1
commit
4ed3ab54e8
|
|
@ -0,0 +1,5 @@
|
|||
# Version bewusst festhalten, damit alle dieselbe Mailpit-Version verwenden.
|
||||
FROM axllent/mailpit:v1.31.2 AS mailpit
|
||||
FROM mcr.microsoft.com/devcontainers/javascript-node:24-bookworm
|
||||
|
||||
COPY --from=mailpit /mailpit /usr/local/bin/mailpit
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
{
|
||||
"name": "Ernaehrungsberatung",
|
||||
"image": "mcr.microsoft.com/devcontainers/javascript-node:24-bookworm",
|
||||
"remoteUser": "node",
|
||||
"forwardPorts": [5173],
|
||||
"forwardPorts": [5173, 8025],
|
||||
"portsAttributes": {
|
||||
"5173": {
|
||||
"label": "Webseite",
|
||||
"onAutoForward": "notify"
|
||||
},
|
||||
"8025": {
|
||||
"label": "Mailpit – Testmails",
|
||||
"onAutoForward": "notify"
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "if [ -f package-lock.json ]; then npm ci; else npm install; fi",
|
||||
"postCreateCommand": "bash .devcontainer/setup.sh",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
|
|
@ -17,5 +20,8 @@
|
|||
"EditorConfig.EditorConfig"
|
||||
]
|
||||
}
|
||||
},
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
npm ci
|
||||
if [ ! -e .env ]; then
|
||||
cp .env.example .env
|
||||
fi
|
||||
|
||||
# Buchungsdatei nicht vorab anlegen: Das Backend erstellt sie bei der ersten Buchung.
|
||||
echo "Bereit. In drei Terminals starten: npm run dev:mail, npm run dev:server, npm run dev"
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
# Tri-Hub Ernährungsberatung
|
||||
|
||||
## Im Dev Container starten
|
||||
|
||||
1. Repository klonen und in VS Code „Dev Containers: Reopen in Container“ wählen.
|
||||
Bei einem bereits vorhandenen Container einmal „Dev Containers: Rebuild Container“
|
||||
ausführen, damit Mailpit installiert wird.
|
||||
2. Die Einrichtung installiert automatisch die npm-Abhängigkeiten und erstellt
|
||||
`.env` aus `.env.example`, sofern noch keine `.env` vorhanden ist.
|
||||
3. In drei getrennten Terminals in dieser Reihenfolge starten:
|
||||
|
||||
```bash
|
||||
npm run dev:mail
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run dev:server
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Startseite: http://localhost:5173 — über „Beratung kennenlernen“ zur
|
||||
Angebotsübersicht und von dort zum Buchungsformular des gewählten Pakets.
|
||||
Direkt: http://localhost:5173/angebotsuebersicht.html
|
||||
Mailansicht: http://localhost:8025. Beide Web-Ports werden vom Dev Container
|
||||
weitergeleitet. Mailpit läuft im selben Container auf SMTP-Port 1025 und fängt
|
||||
Testmails lokal ab, ohne sie extern zu versenden.
|
||||
|
||||
Die Container-Konfiguration installiert Mailpit v1.31.2 aus dem
|
||||
[offiziellen Docker-Image](https://mailpit.axllent.org/docs/install/docker/).
|
||||
Die Binärdatei, lokale Mails, `.env` und Buchungsdaten werden nicht committed.
|
||||
Mailpit verwendet ohne Datenbankpfad einen temporären Speicher; Testmails müssen
|
||||
nach einem Neustart nicht erhalten bleiben.
|
||||
|
||||
Ohne Dev Container: Node.js 24 und Mailpit installieren, `npm ci` ausführen und
|
||||
`.env.example` nach `.env` kopieren, falls `.env` fehlt. Danach dieselben drei
|
||||
Startbefehle verwenden.
|
||||
|
||||
Ohne Mailpit wird die Buchung gespeichert, aber der fehlgeschlagene Mailversand
|
||||
angezeigt. Der Ablauf bleibt eine Simulation ohne Zahlung oder verbindliche
|
||||
Beratung. Buchungen liegen lokal in `src/server/data/bookings.csv`. Diese Datei
|
||||
nicht vorab leer anlegen: Das Backend erzeugt sie bei der ersten Buchung selbst.
|
||||
|
||||
## Prüfen
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run build
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
Die Browsertests starten eigene Server auf 3000 und 5173; diese Ports vorher
|
||||
freihalten. Sie verwenden eine temporäre CSV und simulierten Mailversand.
|
||||
Falls Chromium fehlt: `npx playwright install chromium`.
|
||||
|
||||
Der Build enthält Startseite, Angebotsübersicht und Buchungsseite. Für ein
|
||||
Deployment zusätzlich das Node-Backend betreiben und `/api` dorthin weiterleiten.
|
||||
|
|
@ -1,11 +1,19 @@
|
|||
<section class="packages-section" id="angebotsuebersicht-section">
|
||||
<div class="packages-header">
|
||||
<!-- Hier könnte noch eine Überschrift hin, falls gewünscht -->
|
||||
<h2>Unsere Ernährungs-Pakete</h2>
|
||||
</div>
|
||||
|
||||
<!-- Das ist der Container, den unser JS mit den Karten füllt -->
|
||||
<div class="packages-container" id="packages-container">
|
||||
<!-- Karten werden hier dynamisch von angebotsuebersicht.js injiziert -->
|
||||
</div>
|
||||
</section>
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Ernährungs-Pakete – Tri-Hub</title>
|
||||
</head>
|
||||
<body>
|
||||
<header class="container"><a href="/">Zur Startseite</a></header>
|
||||
<main class="packages-section" id="angebotsuebersicht-section">
|
||||
<div class="packages-header"><h1>Unsere Ernährungs-Pakete</h1></div>
|
||||
<div class="packages-container" id="packages-container"></div>
|
||||
</main>
|
||||
<script
|
||||
type="module"
|
||||
src="/src/features/angebote/angebote-entry.js"
|
||||
></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -33,14 +33,16 @@ cp .env.example .env
|
|||
|
||||
Eine vorhandene `.env` vorher prüfen und nicht überschreiben. Die Beispielwerte
|
||||
aktivieren das klar gekennzeichnete Testpaket und verwenden nur einen Mailfänger.
|
||||
Ohne `.env` bleibt der Demo-Modus ausgeschaltet und der echte Paketkatalog leer.
|
||||
Ohne `.env` bleibt der Demo-Modus ausgeschaltet; die drei regulären Pakete bleiben verfügbar.
|
||||
|
||||
Einmalig [Mailpit installieren](https://mailpit.axllent.org/docs/install/).
|
||||
Mailpit ist über `.devcontainer/Dockerfile` bereits Bestandteil neuer Dev Container.
|
||||
Bestehende Container einmal neu bauen. Außerhalb davon einmalig
|
||||
[Mailpit installieren](https://mailpit.axllent.org/docs/install/).
|
||||
Es läuft als einzelne Binärdatei auch direkt im Dev Container. Kein Docker im
|
||||
Container erforderlich. Mailpit ohne Weiterleitung an echte SMTP-Server starten:
|
||||
|
||||
```bash
|
||||
mailpit --listen 127.0.0.1:8025 --smtp 127.0.0.1:1025 --disable-version-check
|
||||
npm run dev:mail
|
||||
```
|
||||
|
||||
In Terminal 2 das Backend starten:
|
||||
|
|
@ -56,8 +58,8 @@ npm run dev
|
|||
```
|
||||
|
||||
Öffnen: <http://localhost:5173/booking.html#/buchen/demo-booking>.
|
||||
Mailfänger: <http://localhost:8025>. Im Dev Container bei Bedarf Port 8025 über
|
||||
VS Codes Ansicht „Ports“ weiterleiten. Port 5173 ist bereits vorkonfiguriert.
|
||||
Mailfänger: <http://localhost:8025>. Im Dev Container sind die Ports 8025 und 5173
|
||||
für die Weiterleitung vorkonfiguriert.
|
||||
Port 3000 muss für den Browser nicht weitergeleitet werden: Vite vermittelt `/api`.
|
||||
Diese Beschreibung setzt voraus, dass Mailpit im selben Container läuft.
|
||||
|
||||
|
|
@ -92,7 +94,9 @@ Die `.env.example` enthält nur Testwerte und keine Geheimnisse.
|
|||
## Paketdaten und spätere Router-Anbindung
|
||||
|
||||
Person 3 pflegt `packages` in **`src/shared/packages.js`**.
|
||||
Das derzeit leere Array enthält später ausschließlich abgestimmte Angebote:
|
||||
Das Array enthält die drei Angebote Starter, Standard und Premium. Übersicht und
|
||||
Buchung verwenden gemeinsam `id`, `name` und `summary`; zusätzliche Felder
|
||||
beschreiben die Darstellung der Angebotskarten.
|
||||
|
||||
```js
|
||||
// Nur Schema-Beispiel, kein verbindliches Angebot:
|
||||
|
|
@ -127,7 +131,7 @@ cleanup();
|
|||
Das Modul bringt seine begrenzten `.booking`-Styles mit. Farben und Schrift
|
||||
orientieren sich am vorhandenen Basis-CSS. Gemeinsame Designvariablen und eine
|
||||
Button-Komponente gibt es bisher nicht. Layout und Navigation bleiben Aufgabe
|
||||
der anderen Teammitglieder. „Zur Angebotsseite“ zeigt vorerst auf `/#beratung`.
|
||||
der anderen Teammitglieder. „Zur Angebotsseite“ führt zu `/angebotsuebersicht.html`.
|
||||
|
||||
## API-Beispiele
|
||||
|
||||
|
|
@ -336,7 +340,7 @@ Binärdatei außerhalb des Repositorys.
|
|||
|
||||
## Offene Punkte und gemeinsame Dateien
|
||||
|
||||
Offen: echte Paketdaten, Anschluss an den gemeinsamen Router, abschließendes
|
||||
Die Angebotsübersicht ist als eigene HTML-Seite angebunden. Offen: Anschluss an einen gemeinsamen Router, abschließendes
|
||||
Teamdesign, reale Zustellprüfung und Screenreader-Abnahme. Vor öffentlichem
|
||||
Betrieb sind außerdem Betriebsfragen wie Zugriff auf CSV/Backups, Aufbewahrung
|
||||
und Schutz des öffentlichen Buchungsendpunkts vor automatisiertem Massenversand
|
||||
|
|
@ -347,7 +351,7 @@ Gemeinsame Änderungen: `package.json` und Lockdatei (Abhängigkeiten/Startbefeh
|
|||
`.prettierignore` (private Daten/Testergebnisse ausschließen). Neu hinzugefügt:
|
||||
`vite.config.js` (Proxy, Dateizugriffsschutz, zwei HTML-Einstiegspunkte),
|
||||
`src/shared/packages.js` (gemeinsamer Paketvertrag), `.env.example`, `booking.html`
|
||||
und `playwright.config.js`. Die Dev-Container-Konfiguration bleibt unverändert.
|
||||
und `playwright.config.js`. Die Dev-Container-Konfiguration installiert Mailpit und leitet dessen Web-Port weiter.
|
||||
|
||||
Technische Referenzen: [CSV-Parser](https://csv.js.org/parse/api/sync/),
|
||||
[Playwright-Testserver](https://playwright.dev/docs/test-webserver).
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@
|
|||
Individuelle 1:1-Beratung für deinen Alltag und deine
|
||||
sportlichen Ziele.
|
||||
</p>
|
||||
<a href="#beratung">Beratung kennenlernen</a>
|
||||
<a href="/angebotsuebersicht.html">Beratung kennenlernen</a>
|
||||
|
||||
<section id="beratung" aria-labelledby="beratung-heading">
|
||||
<h2 id="beratung-heading">Deine persönliche Beratung</h2>
|
||||
<p>
|
||||
Hier stellen wir bald unseren Ablauf und unser Angebot vor.
|
||||
Entdecke unsere Pakete für deine Ernährung im Alltag und
|
||||
Sport.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,9 @@
|
|||
"format:check": "prettier --check .",
|
||||
"dev:server": "node --env-file-if-exists=.env --watch src/server/index.js",
|
||||
"start:server": "node --env-file-if-exists=.env src/server/index.js",
|
||||
"test": "node --test src/tests/bookings.test.js",
|
||||
"test:browser": "playwright test"
|
||||
"test": "node --test src/tests/*.test.js",
|
||||
"test:browser": "playwright test",
|
||||
"dev:mail": "mailpit --listen 127.0.0.1:8025 --smtp 127.0.0.1:1025 --disable-version-check"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.63.0",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
import "../../styles/styles.css";
|
||||
import "../../styles/packages.css";
|
||||
import { renderAngebotsuebersicht } from "./angebotsuebersicht.js";
|
||||
|
||||
renderAngebotsuebersicht();
|
||||
|
|
@ -28,8 +28,8 @@ export function renderAngebotsuebersicht() {
|
|||
${highlightHtml}
|
||||
</div>
|
||||
|
||||
<h3 class="package-title">${pkg.title}</h3>
|
||||
<p class="package-desc">${pkg.description}</p>
|
||||
<h3 class="package-title">${pkg.name}</h3>
|
||||
<p class="package-desc">${pkg.summary}</p>
|
||||
|
||||
<div class="package-details-box">
|
||||
<div class="detail-item">
|
||||
|
|
@ -47,7 +47,7 @@ export function renderAngebotsuebersicht() {
|
|||
</div>
|
||||
|
||||
<!-- HIER ist der wichtige Link für deinen Kollegen: -->
|
||||
<a href="booking.html?id=${pkg.id}" class="package-btn">${pkg.buttonText}</a>
|
||||
<a href="/booking.html#/buchen/${encodeURIComponent(pkg.id)}" class="package-btn">${pkg.buttonText}</a>
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export function mountBookingPage(root, { packageId } = {}) {
|
|||
const { signal } = controller;
|
||||
root.innerHTML = `
|
||||
<div class="booking">
|
||||
<a href="/#beratung">Zur Angebotsseite</a>
|
||||
<a href="/angebotsuebersicht.html">Zur Angebotsseite</a>
|
||||
<h1>Paket buchen</h1>
|
||||
<p class="booking__intro" role="status">Paket wird geladen …</p>
|
||||
<div class="booking__content"></div>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ export const packages = [
|
|||
{
|
||||
id: "ernaehrung-starter",
|
||||
type: "STARTER",
|
||||
title: "Für den sicheren Einstieg in die Sporternährung",
|
||||
description:
|
||||
name: "Für den sicheren Einstieg in die Sporternährung",
|
||||
summary:
|
||||
"Für Menschen 50+, die mit Triathlon beginnen und ihre Ernährung an die neue Belastung anpassen möchten.",
|
||||
fokus: "Grundlagen der Makronährstoffe, Hydration im Training und Gewichtsmanagement.",
|
||||
anamnese:
|
||||
|
|
@ -16,8 +16,8 @@ export const packages = [
|
|||
{
|
||||
id: "ernaehrung-standard",
|
||||
type: "STANDARD",
|
||||
title: "Für Best Ager mit ersten Erfahrungen und klaren Zielen",
|
||||
description:
|
||||
name: "Für Best Ager mit ersten Erfahrungen und klaren Zielen",
|
||||
summary:
|
||||
"Für Athletinnen und Athleten, die ihre Ernährung für die Kurzdistanz und im Alltag gezielt weiterentwickeln wollen.",
|
||||
fokus: "Carb-Loading, Pacing-Strategien für den Magen und Timing der Nahrungsaufnahme.",
|
||||
anamnese:
|
||||
|
|
@ -30,8 +30,8 @@ export const packages = [
|
|||
{
|
||||
id: "ernaehrung-premium",
|
||||
type: "PREMIUM",
|
||||
title: "Für maximale Individualität mit klarem Fokus",
|
||||
description:
|
||||
name: "Für maximale Individualität mit klarem Fokus",
|
||||
summary:
|
||||
"Für Menschen, die sich eine besonders enge Coaching-Beziehung und laufende Anpassung ihrer Ernährung wünschen.",
|
||||
fokus: "Individuelle Stoffwechselanalyse, Mikronährstoff-Optimierung und Raceday-Nutrition.",
|
||||
anamnese:
|
||||
|
|
@ -42,3 +42,13 @@ export const packages = [
|
|||
highlight: null,
|
||||
},
|
||||
];
|
||||
|
||||
// Nur für lokale Demonstrationen und isolierte Tests.
|
||||
export const demoPackages = [
|
||||
{
|
||||
id: "demo-booking",
|
||||
name: "1:1-Ernährungsberatung",
|
||||
summary: "Testpaket für den simulierten Buchungsablauf.",
|
||||
testOnly: true,
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
.packages-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
|
||||
gap: 2rem;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,30 @@
|
|||
export async function renderAngebotsuebersicht() {
|
||||
const container = document.getElementById("packages-container");
|
||||
if (!container) return;
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createApp } from "../server/index.js";
|
||||
import { packages } from "../shared/packages.js";
|
||||
|
||||
try {
|
||||
// 1. Daten vom Backend des Kollegen abfragen
|
||||
const response = await fetch("/api/angebote"); // Die genaue URL musst du den Kollegen fragen
|
||||
const backendPackages = await response.json();
|
||||
|
||||
container.innerHTML = "";
|
||||
|
||||
// 2. Mit den echten Daten die Karten generieren
|
||||
backendPackages.forEach((pkg) => {
|
||||
const highlightHtml = pkg.highlight
|
||||
? `<span class="package-highlight">${pkg.highlight}</span>`
|
||||
: "";
|
||||
|
||||
const cardHtml = `
|
||||
<div class="package-card">
|
||||
<div class="package-type-container">
|
||||
<span class="package-type">${pkg.type}</span>
|
||||
${highlightHtml}
|
||||
</div>
|
||||
<h3 class="package-title">${pkg.title}</h3>
|
||||
<p class="package-desc">${pkg.description}</p>
|
||||
<!-- ... Rest des HTMLs wie gehabt ... -->
|
||||
<a href="buchung.html?id=${pkg.id}" class="package-btn">${pkg.buttonText}</a>
|
||||
</div>
|
||||
`;
|
||||
container.innerHTML += cardHtml;
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Fehler beim Laden der Angebote vom Backend:", error);
|
||||
container.innerHTML = "<p>Angebote konnten nicht geladen werden.</p>";
|
||||
}
|
||||
}
|
||||
test("API liefert für jedes angezeigte Angebot dieselbe ID, Namen und Beschreibung", async (t) => {
|
||||
const app = createApp({ sendConfirmation: async () => {} });
|
||||
await new Promise((resolve) => app.listen(0, "127.0.0.1", resolve));
|
||||
t.after(
|
||||
() =>
|
||||
new Promise((resolve) => {
|
||||
app.closeAllConnections();
|
||||
app.close(resolve);
|
||||
}),
|
||||
);
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${app.address().port}/api/angebote`,
|
||||
);
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
assert.deepEqual(
|
||||
body.packages,
|
||||
packages.map(({ id, name, summary }) => ({
|
||||
id,
|
||||
name,
|
||||
summary,
|
||||
testOnly: false,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
|
||||
import { packages } from "../../shared/packages.js";
|
||||
|
||||
const pageUrl = "/booking.html#/buchen/demo-booking";
|
||||
async function fill(page) {
|
||||
await page
|
||||
|
|
@ -179,3 +181,53 @@ test("Vite liefert Serverdateien und Umgebungsdateien nicht aus", async ({
|
|||
expect([403, 404]).toContain(response.status());
|
||||
}
|
||||
});
|
||||
|
||||
for (const pkg of packages) {
|
||||
test(`Von der Startseite zur Buchung: ${pkg.type}`, async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await page.getByRole("link", { name: "Beratung kennenlernen" }).click();
|
||||
await expect(page.locator(".package-card")).toHaveCount(
|
||||
packages.length,
|
||||
);
|
||||
await page.getByRole("link", { name: pkg.buttonText }).click();
|
||||
await expect(page).toHaveURL(
|
||||
new RegExp(`booking\\.html#/buchen/${pkg.id}$`),
|
||||
);
|
||||
await expect(
|
||||
page.getByRole("heading", { name: pkg.name, exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(page.locator(".booking__summary")).toHaveText(pkg.summary);
|
||||
await fill(page);
|
||||
const responsePromise = page.waitForResponse(
|
||||
(response) =>
|
||||
response.url().endsWith("/api/bookings") &&
|
||||
response.request().method() === "POST",
|
||||
);
|
||||
await page
|
||||
.getByRole("button", { name: "Paket buchen", exact: true })
|
||||
.click();
|
||||
const response = await responsePromise;
|
||||
expect(response.status()).toBe(201);
|
||||
const booking = await response.json();
|
||||
expect(booking.packageId).toBe(pkg.id);
|
||||
expect(booking.packageName).toBe(pkg.name);
|
||||
await expect(page.locator(".booking__result")).toContainText(
|
||||
booking.bookingId,
|
||||
);
|
||||
await page.getByRole("link", { name: "Zur Angebotsseite" }).click();
|
||||
await expect(page.locator(".package-card")).toHaveCount(
|
||||
packages.length,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
test("Angebotsübersicht passt auf ein schmales Display", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 320, height: 640 });
|
||||
await page.goto("/angebotsuebersicht.html");
|
||||
await expect(page.locator(".package-card")).toHaveCount(packages.length);
|
||||
expect(
|
||||
await page.evaluate(
|
||||
() => document.documentElement.scrollWidth <= window.innerWidth,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import { tmpdir } from "node:os";
|
|||
import { join } from "node:path";
|
||||
import { createApp } from "../../server/index.js";
|
||||
import { createCsvStorage } from "../../server/services/csv-storage.js";
|
||||
import { demoPackages } from "../../shared/packages.js";
|
||||
import { packages, demoPackages } from "../../shared/packages.js";
|
||||
|
||||
const directory = await mkdtemp(join(tmpdir(), "trihub-browser-"));
|
||||
const server = createApp({
|
||||
catalog: demoPackages,
|
||||
catalog: [...packages, ...demoPackages],
|
||||
storage: createCsvStorage(join(directory, "bookings.csv")),
|
||||
sendConfirmation: async () => {},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,19 +1,28 @@
|
|||
import { packages } from "../shared/packages.js";
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { packages, demoPackages } from "../shared/packages.js";
|
||||
import { createApp } from "../server/index.js";
|
||||
|
||||
describe("Angebots-Daten (packages.js)", () => {
|
||||
test("sollte ein Array mit Angeboten exportieren", () => {
|
||||
expect(Array.isArray(nutritionPackages)).toBe(true);
|
||||
expect(nutritionPackages.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("jedes Paket muss eine ID und einen Titel für das Routing besitzen", () => {
|
||||
nutritionPackages.forEach((pkg) => {
|
||||
expect(pkg).toHaveProperty("id");
|
||||
expect(pkg).toHaveProperty("title");
|
||||
expect(pkg).toHaveProperty("buttonText");
|
||||
// Prüft, ob die ID das korrekte Format für die URLs hat
|
||||
expect(typeof pkg.id).toBe("string");
|
||||
expect(pkg.id.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
test("Angebote erfüllen den gemeinsamen Vertrag von Übersicht und Buchung", () => {
|
||||
assert.ok(packages.length > 0);
|
||||
assert.doesNotThrow(() =>
|
||||
createApp({
|
||||
catalog: [...packages, ...demoPackages],
|
||||
sendConfirmation: async () => {},
|
||||
}),
|
||||
);
|
||||
for (const pkg of packages) {
|
||||
for (const field of [
|
||||
"name",
|
||||
"summary",
|
||||
"type",
|
||||
"fokus",
|
||||
"anamnese",
|
||||
"begleitung",
|
||||
"buttonText",
|
||||
]) {
|
||||
assert.equal(typeof pkg[field], "string");
|
||||
assert.ok(pkg[field].trim());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ export default defineConfig({
|
|||
build: {
|
||||
rollupOptions: {
|
||||
input: {
|
||||
angebote: fileURLToPath(
|
||||
new URL("./angebotsuebersicht.html", import.meta.url),
|
||||
),
|
||||
main: fileURLToPath(new URL("./index.html", import.meta.url)),
|
||||
booking: fileURLToPath(
|
||||
new URL("./booking.html", import.meta.url),
|
||||
|
|
|
|||
Loading…
Reference in New Issue