feat: Phase 4-5 — demo mode, polish, deploy, and bug fixes
Add demo mode with mock data provider, Docker deployment, Playwright tests, PostHog analytics, error boundaries, and SEO metadata. Fix residences API response unwrapping, kanban drag-and-drop with optimistic updates, trailing slash proxy redirects, and column name mismatches with Go API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Authentication", () => {
|
||||
test("shows login page", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/log in|sign in/i);
|
||||
});
|
||||
|
||||
test("shows validation errors for empty form", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('[role="alert"]').first()).toBeVisible();
|
||||
});
|
||||
|
||||
test("shows register page", async ({ page }) => {
|
||||
await page.goto("/register");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/register|sign up|create account/i);
|
||||
});
|
||||
|
||||
test("navigates from login to register", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await page.click('a[href="/register"]');
|
||||
await expect(page).toHaveURL("/register");
|
||||
});
|
||||
|
||||
test("navigates from login to forgot password", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
await page.click('a[href="/forgot-password"]');
|
||||
await expect(page).toHaveURL("/forgot-password");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Contractors (Demo Mode)", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/demo/app/contractors");
|
||||
});
|
||||
|
||||
test("displays contractor list", async ({ page }) => {
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/contractors/i);
|
||||
});
|
||||
|
||||
test("navigate to add contractor", async ({ page }) => {
|
||||
await page.click("text=Add Contractor");
|
||||
await expect(page).toHaveURL("/demo/app/contractors/new");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Demo Mode", () => {
|
||||
test("shows demo landing page", async ({ page }) => {
|
||||
await page.goto("/demo");
|
||||
await expect(page.locator("text=Start Demo")).toBeVisible();
|
||||
});
|
||||
|
||||
test("enters demo app", async ({ page }) => {
|
||||
await page.goto("/demo");
|
||||
await page.click("text=Start Demo");
|
||||
await expect(page).toHaveURL("/demo/app");
|
||||
});
|
||||
|
||||
test("shows residences in demo", async ({ page }) => {
|
||||
await page.goto("/demo/app/residences");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/residences|properties/i);
|
||||
});
|
||||
|
||||
test("shows tasks in demo", async ({ page }) => {
|
||||
await page.goto("/demo/app/tasks");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/tasks/i);
|
||||
});
|
||||
|
||||
test("shows contractors in demo", async ({ page }) => {
|
||||
await page.goto("/demo/app/contractors");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/contractors/i);
|
||||
});
|
||||
|
||||
test("shows documents in demo", async ({ page }) => {
|
||||
await page.goto("/demo/app/documents");
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/documents/i);
|
||||
});
|
||||
|
||||
test("can navigate to add task in demo", async ({ page }) => {
|
||||
await page.goto("/demo/app/tasks");
|
||||
await page.click("text=Add Task");
|
||||
await expect(page).toHaveURL("/demo/app/tasks/new");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Documents (Demo Mode)", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/demo/app/documents");
|
||||
});
|
||||
|
||||
test("displays document list", async ({ page }) => {
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/documents/i);
|
||||
});
|
||||
|
||||
test("navigate to add document", async ({ page }) => {
|
||||
await page.click("text=Add Document");
|
||||
await expect(page).toHaveURL("/demo/app/documents/new");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Residences (Demo Mode)", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/demo/app/residences");
|
||||
});
|
||||
|
||||
test("displays residence list", async ({ page }) => {
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/residences|properties/i);
|
||||
});
|
||||
|
||||
test("navigate to add residence", async ({ page }) => {
|
||||
await page.click("text=Add Residence");
|
||||
await expect(page).toHaveURL("/demo/app/residences/new");
|
||||
});
|
||||
|
||||
test("add residence form has required fields", async ({ page }) => {
|
||||
await page.goto("/demo/app/residences/new");
|
||||
await expect(page.locator('input[name="name"], input[name="title"]').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import { test, expect, devices } from "@playwright/test";
|
||||
|
||||
test.describe("Responsive Design", () => {
|
||||
test("shows sidebar on desktop", async ({ browser }) => {
|
||||
const context = await browser.newContext({
|
||||
viewport: { width: 1280, height: 720 },
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto("/demo/app/residences");
|
||||
const sidebar = page.locator('nav[aria-label="Main navigation"]').first();
|
||||
await expect(sidebar).toBeVisible();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test("shows bottom nav on mobile", async ({ browser }) => {
|
||||
const context = await browser.newContext({
|
||||
...devices["iPhone 14"],
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto("/demo/app/residences");
|
||||
// Bottom nav should be visible on mobile
|
||||
const bottomNav = page.locator('nav[aria-label="Main navigation"]').last();
|
||||
await expect(bottomNav).toBeVisible();
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test("kanban board scrolls horizontally on mobile", async ({ browser }) => {
|
||||
const context = await browser.newContext({
|
||||
...devices["iPhone 14"],
|
||||
});
|
||||
const page = await context.newPage();
|
||||
await page.goto("/demo/app/tasks");
|
||||
// The kanban container should have overflow-x-auto
|
||||
const kanban = page.locator(".snap-x").first();
|
||||
if (await kanban.isVisible()) {
|
||||
await expect(kanban).toHaveCSS("overflow-x", "auto");
|
||||
}
|
||||
await context.close();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
test.describe("Tasks (Demo Mode)", () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto("/demo/app/tasks");
|
||||
});
|
||||
|
||||
test("displays task page", async ({ page }) => {
|
||||
await expect(page.locator("h1, h2").first()).toContainText(/tasks/i);
|
||||
});
|
||||
|
||||
test("navigate to add task", async ({ page }) => {
|
||||
await page.click("text=Add Task");
|
||||
await expect(page).toHaveURL("/demo/app/tasks/new");
|
||||
});
|
||||
|
||||
test("add task form has required fields", async ({ page }) => {
|
||||
await page.goto("/demo/app/tasks/new");
|
||||
await expect(page.locator('input[name="title"]').first()).toBeVisible();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user