personal-lifecycle-tracker / e2e / spec-compliance.spec.ts
spec-compliance.spec.ts
Raw
import { test, expect } from '@playwright/test';
import { resetDatabase } from './helpers';

test.describe('Full MVP Specification Compliance Journey (@spec.md Section 6)', () => {
  test.beforeEach(async () => {
    await resetDatabase();
  });

  test('end-to-end acceptance flow validating all Section 6 criteria', async ({ page }) => {
    const timestamp = Date.now();
    const userEmail = `auditor_${timestamp}@example.com`;
    const userName = `Auditor ${timestamp}`;
    const groupName = `Audited Home Group ${timestamp}`;
    const renewalName = `Vehicle Fleet Registration ${timestamp}`;
    const maintenanceName = `Solar Inverter Servicing ${timestamp}`;
    const warrantyName = `Heat Pump Warranty ${timestamp}`;

    // ----------------------------------------------------
    // 1. Register and log in with email and password
    // ----------------------------------------------------
    await page.goto('/');
    await page.waitForLoadState('networkidle');

    // Open user menu -> Switch or Sign In User
    const userBtn = page.locator('header').locator('button').filter({ hasText: /Alex Taylor|Sarah|Account/i }).first();
    await userBtn.click();
    await page.getByRole('button', { name: /Switch or Sign In User/i }).click();

    const authModal = page.locator('.fixed.inset-0').filter({ hasText: /Sign In|Create New Account/i });
    await expect(authModal).toBeVisible();

    // Sign up
    await authModal.getByRole('button', { name: 'Sign Up', exact: true }).click();
    await authModal.locator('#auth-name').fill(userName);
    await authModal.locator('#auth-email').fill(userEmail);
    await authModal.locator('#auth-password').fill('pass12345');
    await authModal.getByRole('button', { name: 'Create Account' }).click();

    await expect(authModal).toBeHidden();
    await expect(page.locator('header').getByText(userName)).toBeVisible();

    // ----------------------------------------------------
    // 2. Create a group and invite members by email
    // ----------------------------------------------------
    await page.getByRole('button', { name: 'Groups' }).click();
    await expect(page.getByRole('heading', { name: 'Groups & Shared Access' })).toBeVisible();

    const groupInput = page.locator('input[placeholder="e.g. Oakridge Family & Home"]');
    await groupInput.fill(groupName);
    await page.getByRole('button', { name: 'Create Group' }).click();

    const groupCard = page.locator('.bg-white').filter({ hasText: groupName });
    await expect(groupCard).toBeVisible();

    // Invite member
    const invitee = `technician_${timestamp}@example.com`;
    await groupCard.locator('input[type="email"]').fill(invitee);
    await groupCard.getByRole('button', { name: 'Invite' }).click();
    await expect(groupCard.getByText(invitee)).toBeVisible();

    // ----------------------------------------------------
    // 3. Create renewal, maintenance, and warranty items using a guided form
    //    with common fields (Name, Type, Due date, Status, Group)
    // ----------------------------------------------------
    // 3a. Create Renewal (due in 15 days -> upcoming)
    await page.getByRole('button', { name: /Track Item/i }).click();
    const createModal = page.locator('.fixed.inset-0').filter({ hasText: /Step [12]:/i });
    await expect(createModal).toBeVisible();
    await createModal.getByRole('button', { name: /Renewal/i }).click();

    await page.locator('#item-name').fill(renewalName);
    const date15 = new Date();
    date15.setDate(date15.getDate() + 15);
    await page.locator('#item-due-date').fill(date15.toISOString().slice(0, 10));
    await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click();
    await expect(createModal).toBeHidden();

    // 3b. Create Maintenance (due 5 days ago -> overdue)
    await page.getByRole('button', { name: /Track Item/i }).click();
    await createModal.getByRole('button', { name: /Maintenance/i }).click();
    await page.locator('#item-name').fill(maintenanceName);
    const datePast = new Date();
    datePast.setDate(datePast.getDate() - 5);
    await page.locator('#item-due-date').fill(datePast.toISOString().slice(0, 10));
    // Assign to group
    await page.locator('#item-group').selectOption({ label: `Group: ${groupName}` });
    await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click();
    await expect(createModal).toBeHidden();

    // 3c. Create Warranty without due date (Decision 29)
    await page.getByRole('button', { name: /Track Item/i }).click();
    await createModal.getByRole('button', { name: /Warranty/i }).click();
    await page.locator('#item-name').fill(warrantyName);
    await page.locator('.fixed.inset-0 input[type="checkbox"]').uncheck();
    await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click();
    await expect(createModal).toBeHidden();

    // ----------------------------------------------------
    // 4. View items in lists separated by lifecycle type
    // ----------------------------------------------------
    // Renewals view
    await page.getByRole('button', { name: 'Renewals' }).click();
    await expect(page.locator(`text=${renewalName}`).first()).toBeVisible();

    // Maintenance view
    await page.getByRole('button', { name: 'Maintenance' }).click();
    await expect(page.locator(`text=${maintenanceName}`).first()).toBeVisible();

    // Warranties view
    await page.getByRole('button', { name: 'Warranties' }).click();
    await expect(page.locator(`text=${warrantyName}`).first()).toBeVisible();

    // ----------------------------------------------------
    // 5. See upcoming actions and overdue items on Dashboard
    // ----------------------------------------------------
    await page.getByRole('button', { name: 'Dashboard' }).click();

    // Overdue section must contain Maintenance
    const overdueSection = page.locator('section').filter({ hasText: /Overdue Items/i });
    await expect(overdueSection.getByText(maintenanceName)).toBeVisible();

    // Upcoming section must contain Renewal
    const upcomingSection = page.locator('section').filter({ hasText: /Upcoming Actions/i });
    await expect(upcomingSection.getByText(renewalName)).toBeVisible();

    // ----------------------------------------------------
    // 6. Mark an item complete while retaining it in the main list
    // ----------------------------------------------------
    const overdueRow = overdueSection.locator('.group').filter({ hasText: maintenanceName });
    await overdueRow.getByRole('button', { name: /Mark as complete/i }).click();

    // Retained in main list under Completed Records section with line-through
    const completedSection = page.locator('section').filter({ hasText: /Completed Records/i });
    await expect(completedSection.getByText(maintenanceName)).toBeVisible();
    await expect(completedSection.locator('.group').filter({ hasText: maintenanceName }).locator('span.line-through')).toContainText(maintenanceName);

    // ----------------------------------------------------
    // 7. Receive in-app reminders and daily email digest
    // ----------------------------------------------------
    // In-app reminders
    const bellBtn = page.getByRole('button', { name: /Open Reminders/i });
    await bellBtn.click();
    const reminderDrawer = page.locator('.fixed.inset-0').filter({ hasText: /In-App Reminders/i });
    await expect(reminderDrawer).toBeVisible();
    await expect(reminderDrawer.getByText(renewalName)).toBeVisible();
    await reminderDrawer.getByRole('button', { name: /Close drawer/i }).click();

    // Daily digest
    await page.getByRole('button', { name: 'Daily Digest', exact: true }).click();
    await expect(page.getByText('Dual Channel Enabled')).toBeVisible();
    // Configure delivery time
    await page.locator('button').filter({ hasText: 'Option C' }).first().click();
    await expect(page.getByText('Saved')).toBeVisible();

    // Simulate digest
    await page.getByRole('button', { name: /Simulate \/ Send Digest Now/i }).click();
    await expect(page.getByText('Email Digest Preview')).toBeVisible();
    await expect(page.getByText(/\[Action Required\]|\[Upcoming Actions\]|\[Lifecycle Tracker\]/i).first()).toBeVisible();

    // ----------------------------------------------------
    // 8. Permanently delete item, group, and account
    // ----------------------------------------------------
    // Delete item
    await page.getByRole('button', { name: 'Renewals' }).click();
    const renewalRow = page.locator('.group').filter({ hasText: renewalName });
    page.once('dialog', async (dialog) => {
      await dialog.accept();
    });
    await renewalRow.getByRole('button', { name: /Permanently delete item/i }).click();
    await expect(page.locator('.group').filter({ hasText: renewalName })).toHaveCount(0);

    // Delete group
    await page.getByRole('button', { name: 'Groups' }).click();
    const groupToDelete = page.locator('.bg-white').filter({ hasText: groupName });
    page.once('dialog', async (dialog) => {
      await dialog.accept();
    });
    await groupToDelete.getByRole('button', { name: 'Delete group' }).click();
    await expect(page.locator('.bg-white').filter({ hasText: groupName })).toHaveCount(0);

    // Delete account
    const finalProfileBtn = page.locator('header').locator('button').filter({ hasText: userName });
    await finalProfileBtn.click();
    page.once('dialog', async (dialog) => {
      await dialog.accept();
    });
    await page.getByRole('button', { name: /Delete Account/i }).click();

    // Verified account deletion completed and redirected/reset
    await expect(page.locator('header').getByText(userName)).toHaveCount(0);
  });
});