feat(T07): Warenkorb & Mietanfrage frontend – cart store, drawer, pages, form, tests

This commit is contained in:
Implementation Engineer
2026-07-10 01:32:19 +02:00
parent e220ff7db9
commit a1bba48cd7
19 changed files with 1874 additions and 92 deletions
+68
View File
@@ -0,0 +1,68 @@
import { test, expect } from "@playwright/test";
test.describe("Cart Flow", () => {
test("should render warenkorb page with heading", async ({ page }) => {
await page.goto("/warenkorb");
await expect(page.locator("h1")).toContainText("Warenkorb");
});
test("should show empty state when cart is empty", async ({ page }) => {
await page.goto("/warenkorb");
await expect(page.getByTestId("warenkorb-empty")).toBeVisible();
await expect(page.getByTestId("warenkorb-empty-cta")).toBeVisible();
});
test("should have link to mietkatalog from empty state", async ({ page }) => {
await page.goto("/warenkorb");
const cta = page.getByTestId("warenkorb-empty-cta");
await expect(cta).toHaveAttribute("href", "/mietkatalog");
});
test("should show header cart icon", async ({ page }) => {
await page.goto("/");
await expect(page.getByTestId("header-cart-button")).toBeVisible();
});
test("should open cart drawer when header cart icon clicked", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
});
test("should show empty state in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
await expect(page.locator("text=Ihr Warenkorb ist leer")).toBeVisible();
});
test("should close cart drawer on backdrop click", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
await page.getByTestId("cart-drawer-backdrop").click();
await expect(page.getByTestId("cart-drawer")).not.toBeVisible();
});
test("should have checkout link in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
const checkout = page.getByTestId("cart-drawer-checkout");
await expect(checkout).toHaveAttribute("href", "/mietanfrage");
});
test("should have view cart link in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
const viewCart = page.getByTestId("cart-drawer-view-cart");
await expect(viewCart).toHaveAttribute("href", "/warenkorb");
});
test("should have mobile cart button", async ({ page }) => {
await page.goto("/");
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.getByTestId("header-cart-button-mobile")).toBeVisible();
});
});
+27
View File
@@ -0,0 +1,27 @@
import { test, expect } from "@playwright/test";
test.describe("Mietanfrage Page", () => {
test("should render mietanfrage page with heading", async ({ page }) => {
await page.goto("/mietanfrage");
await expect(page.locator("h1")).toContainText("Mietanfrage");
});
test("should show empty cart state when no items in cart", async ({ page }) => {
await page.goto("/mietanfrage");
await expect(page.getByTestId("mietanfrage-empty-cart")).toBeVisible();
});
test("should have link to mietkatalog from empty cart state", async ({ page }) => {
await page.goto("/mietanfrage");
const cta = page.getByTestId("mietanfrage-empty-cta");
await expect(cta).toBeVisible();
await expect(cta).toHaveAttribute("href", "/mietkatalog");
});
test("should have breadcrumb with warenkorb link", async ({ page }) => {
await page.goto("/mietanfrage");
const breadcrumb = page.locator('nav[aria-label="Breadcrumb"]');
await expect(breadcrumb).toBeVisible();
await expect(breadcrumb.locator('a[href="/warenkorb"]')).toBeVisible();
});
});
+101
View File
@@ -0,0 +1,101 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Cart Store", () => {
const storePath = resolve(__dirname, "../../stores/cart.ts");
const content = readFileSync(storePath, "utf-8");
it("should define useCartStore with defineStore", () => {
expect(content).toContain("defineStore");
expect(content).toContain("useCartStore");
});
it("should have CartItem interface with required fields", () => {
expect(content).toContain("equipment_id");
expect(content).toContain("name");
expect(content).toContain("quantity");
expect(content).toContain("rental_price");
expect(content).toContain("image_url");
});
it("should have totalCount getter", () => {
expect(content).toContain("totalCount");
expect(content).toContain("reduce");
});
it("should have isEmpty getter", () => {
expect(content).toContain("isEmpty");
});
it("should have hasItems getter", () => {
expect(content).toContain("hasItems");
});
it("should have apiItems getter for rental request payload", () => {
expect(content).toContain("apiItems");
expect(content).toContain("equipment_id");
expect(content).toContain("quantity");
});
it("should have addItem action", () => {
expect(content).toContain("addItem");
expect(content).toContain("existing");
expect(content).toContain("quantity += 1");
});
it("should have addItemByFields action", () => {
expect(content).toContain("addItemByFields");
});
it("should have removeItem action", () => {
expect(content).toContain("removeItem");
expect(content).toContain("splice");
});
it("should have updateQuantity action", () => {
expect(content).toContain("updateQuantity");
expect(content).toContain("quantity <= 0");
});
it("should have incrementQuantity action", () => {
expect(content).toContain("incrementQuantity");
});
it("should have decrementQuantity action", () => {
expect(content).toContain("decrementQuantity");
});
it("should have clearCart action", () => {
expect(content).toContain("clearCart");
expect(content).toContain("items = []");
});
it("should have RentalRequestPayload interface", () => {
expect(content).toContain("RentalRequestPayload");
expect(content).toContain("event_name");
expect(content).toContain("date_start");
expect(content).toContain("date_end");
expect(content).toContain("location");
expect(content).toContain("person_count");
expect(content).toContain("contact_name");
expect(content).toContain("contact_email");
expect(content).toContain("contact_phone");
expect(content).toContain("contact_street");
expect(content).toContain("contact_postalcode");
expect(content).toContain("contact_city");
expect(content).toContain("message");
expect(content).toContain("items");
});
it("should have RentalRequestResponse interface", () => {
expect(content).toContain("RentalRequestResponse");
expect(content).toContain("reference_number");
expect(content).toContain("status");
});
it("should use TypeScript", () => {
expect(content).toContain("import type");
expect(content).toContain("export interface");
});
});
+173
View File
@@ -0,0 +1,173 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Mietanfrage Page", () => {
const pagePath = resolve(__dirname, "../../pages/mietanfrage.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Mietanfrage", () => {
expect(content).toContain("Mietanfrage");
expect(content).toContain('data-testid="mietanfrage-title"');
});
it("should use useCart composable", () => {
expect(content).toContain("useCart");
});
it("should use useApi composable", () => {
expect(content).toContain("useApi");
});
it("should have breadcrumb navigation", () => {
expect(content).toContain("Breadcrumb");
expect(content).toContain('to="/"');
expect(content).toContain('to="/warenkorb"');
});
it("should have empty cart state with redirect to mietkatalog", () => {
expect(content).toContain('data-testid="mietanfrage-empty-cart"');
expect(content).toContain('to="/mietkatalog"');
expect(content).toContain("Ihr Warenkorb ist leer");
});
it("should have cart overview section (read-only)", () => {
expect(content).toContain('data-testid="mietanfrage-cart-overview"');
expect(content).toContain('data-testid="mietanfrage-cart-item"');
});
it("should have event name field (required)", () => {
expect(content).toContain('data-testid="form-event-name"');
expect(content).toContain("Veranstaltung");
});
it("should have date start field (required)", () => {
expect(content).toContain('data-testid="form-date-start"');
expect(content).toContain("Von");
});
it("should have date end field (required)", () => {
expect(content).toContain('data-testid="form-date-end"');
expect(content).toContain("Bis");
});
it("should have location field (required)", () => {
expect(content).toContain('data-testid="form-location"');
expect(content).toContain("Veranstaltungsort");
});
it("should have person count field (required)", () => {
expect(content).toContain('data-testid="form-person-count"');
expect(content).toContain("Personenanzahl");
});
it("should have contact name field (required)", () => {
expect(content).toContain('data-testid="form-contact-name"');
expect(content).toContain("Name");
});
it("should have contact company field (optional)", () => {
expect(content).toContain('data-testid="form-contact-company"');
expect(content).toContain("Firma");
});
it("should have contact email field (required)", () => {
expect(content).toContain('data-testid="form-contact-email"');
expect(content).toContain("E-Mail");
});
it("should have contact phone field (optional)", () => {
expect(content).toContain('data-testid="form-contact-phone"');
expect(content).toContain("Telefon");
});
it("should have contact street field", () => {
expect(content).toContain('data-testid="form-contact-street"');
});
it("should have contact postal code field", () => {
expect(content).toContain('data-testid="form-contact-postalcode"');
});
it("should have contact city field", () => {
expect(content).toContain('data-testid="form-contact-city"');
});
it("should have message textarea", () => {
expect(content).toContain('data-testid="form-message"');
expect(content).toContain("Nachricht");
});
it("should have email validation with regex", () => {
expect(content).toContain("emailRegex");
expect(content).toContain("emailRegex.test");
});
it("should have validateField function", () => {
expect(content).toContain("validateField");
expect(content).toContain("validateAll");
});
it("should have error display for event_name", () => {
expect(content).toContain('data-testid="error-event-name"');
});
it("should have error display for contact_email", () => {
expect(content).toContain('data-testid="error-contact-email"');
});
it("should have submit button", () => {
expect(content).toContain('data-testid="mietanfrage-submit"');
expect(content).toContain("Anfrage senden");
});
it("should have submitting state with spinner", () => {
expect(content).toContain("submitting");
expect(content).toContain("Wird gesendet");
expect(content).toContain("animate-spin");
});
it("should have success state with reference number", () => {
expect(content).toContain('data-testid="mietanfrage-success"');
expect(content).toContain('data-testid="mietanfrage-reference-number"');
expect(content).toContain("Vielen Dank");
expect(content).toContain("reference_number");
});
it("should have error state", () => {
expect(content).toContain('data-testid="mietanfrage-error"');
expect(content).toContain("Fehler");
});
it("should POST to /api/rental-requests", () => {
expect(content).toContain("/api/rental-requests");
expect(content).toContain("post");
});
it("should clear cart after successful submit", () => {
expect(content).toContain("clearCart");
expect(content).toContain("success");
});
it("should have back link to warenkorb", () => {
expect(content).toContain('data-testid="mietanfrage-back"');
expect(content).toContain('to="/warenkorb"');
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
it("should use Tailwind classes, no inline styles", () => {
expect(content).not.toContain('style="');
});
it("should use useHead for page title", () => {
expect(content).toContain("useHead");
});
it("should import RentalRequestPayload and RentalRequestResponse types", () => {
expect(content).toContain("RentalRequestPayload");
expect(content).toContain("RentalRequestResponse");
});
});
+88
View File
@@ -0,0 +1,88 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Warenkorb Page", () => {
const pagePath = resolve(__dirname, "../../pages/warenkorb.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Warenkorb", () => {
expect(content).toContain("Warenkorb");
expect(content).toContain('data-testid="warenkorb-title"');
});
it("should use useCart composable", () => {
expect(content).toContain("useCart");
});
it("should have breadcrumb navigation", () => {
expect(content).toContain("Breadcrumb");
expect(content).toContain('to="/"');
});
it("should have empty state with link to mietkatalog", () => {
expect(content).toContain('data-testid="warenkorb-empty"');
expect(content).toContain('to="/mietkatalog"');
expect(content).toContain("Ihr Warenkorb ist leer");
});
it("should have cart item list with data-testid", () => {
expect(content).toContain('data-testid="warenkorb-item"');
});
it("should have item name with link to detail page", () => {
expect(content).toContain('data-testid="warenkorb-item-name"');
expect(content).toContain("/mietkatalog/");
});
it("should have quantity stepper with increment and decrement", () => {
expect(content).toContain('data-testid="warenkorb-item-stepper"');
expect(content).toContain('data-testid="warenkorb-item-decrement"');
expect(content).toContain('data-testid="warenkorb-item-increment"');
expect(content).toContain("decrementQuantity");
expect(content).toContain("incrementQuantity");
});
it("should display item quantity", () => {
expect(content).toContain('data-testid="warenkorb-item-quantity"');
expect(content).toContain("item.quantity");
});
it("should have remove button", () => {
expect(content).toContain('data-testid="warenkorb-item-remove"');
expect(content).toContain("removeItem");
});
it("should have clear cart button", () => {
expect(content).toContain('data-testid="warenkorb-clear"');
expect(content).toContain("clearCart");
expect(content).toContain("Warenkorb leeren");
});
it("should have checkout link to mietanfrage", () => {
expect(content).toContain('data-testid="warenkorb-checkout"');
expect(content).toContain('to="/mietanfrage"');
expect(content).toContain("Zur Mietanfrage");
});
it("should display total count", () => {
expect(content).toContain("totalCount");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
it("should use Tailwind classes, no inline styles", () => {
expect(content).not.toContain('style="');
});
it("should use useHead for page title", () => {
expect(content).toContain("useHead");
});
it("should have formatPrice function", () => {
expect(content).toContain("formatPrice");
expect(content).toContain("Intl.NumberFormat");
});
});