feat(T06): Mietkatalog frontend – equipment list, detail, search/filter, SSR

- composables/useApi.ts: base API client with runtimeConfig
- composables/useEquipment.ts: equipment API wrapper (list, detail, categories)
- components/EquipmentCard.vue: card with image/placeholder, badge, name, price, add-to-cart
- pages/mietkatalog.vue: SSR list with search, category chips, sort, pagination, skeleton, empty/error states
- pages/mietkatalog/[id].vue: SSR detail with specs table, related items, breadcrumb, add-to-cart
- nuxt.config.ts: runtimeConfig with apiBase
- 101 vitest tests passing (5 files)
- Build: 0 errors, 2.09 MB
This commit is contained in:
Implementation Engineer
2026-07-10 00:54:40 +02:00
parent 88cff68f73
commit e9da21b57e
12 changed files with 1230 additions and 66 deletions
+76
View File
@@ -0,0 +1,76 @@
import { test, expect } from "@playwright/test";
test.describe("Mietkatalog Page", () => {
test("should render SSR HTML with Mietkatalog heading", async ({ page }) => {
await page.goto("/mietkatalog");
await expect(page.locator("h1")).toContainText("Mietkatalog");
});
test("should have search input visible", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await expect(searchInput).toBeVisible();
});
test("should have sort dropdown with name options", async ({ page }) => {
await page.goto("/mietkatalog");
const sortSelect = page.getByTestId("sort-select");
await expect(sortSelect).toBeVisible();
await expect(sortSelect.locator("option")).toHaveCount(2);
});
test("should have category filter chips", async ({ page }) => {
await page.goto("/mietkatalog");
const chips = page.getByTestId("category-chips");
await expect(chips).toBeVisible();
});
test("should have reset filters button", async ({ page }) => {
await page.goto("/mietkatalog");
const resetBtn = page.getByTestId("reset-filters");
await expect(resetBtn).toBeVisible();
});
test("should filter equipment by search input", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await searchInput.fill("Lautsprecher");
await page.waitForTimeout(500);
const grid = page.getByTestId("equipment-grid");
await expect(grid).toBeVisible();
});
test("should reset all filters when reset button clicked", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await searchInput.fill("Test");
const resetBtn = page.getByTestId("reset-filters");
await resetBtn.click();
await expect(searchInput).toHaveValue("");
});
test("should show result count", async ({ page }) => {
await page.goto("/mietkatalog");
const resultCount = page.getByTestId("result-count");
await expect(resultCount).toBeVisible();
});
test("should display equipment cards or empty state", async ({ page }) => {
await page.goto("/mietkatalog");
const grid = page.getByTestId("equipment-grid");
const emptyState = page.getByText("Keine Artikel gefunden");
const isVisible = await grid.isVisible().catch(() => false) || await emptyState.isVisible().catch(() => false);
expect(isVisible).toBeTruthy();
});
test("should navigate to detail page on card click", async ({ page }) => {
await page.goto("/mietkatalog");
const card = page.getByTestId("equipment-card").first();
if (await card.isVisible()) {
const link = card.locator("a[href*='/mietkatalog/']").first();
await link.click();
await page.waitForURL(/\/mietkatalog\/\d+/);
await expect(page).toHaveURL(/\/mietkatalog\/\d+/);
}
});
});