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

test.describe('Lifecycle Items Management Flow (@spec.md Decision 5, 7, 8, 11, 12, 19, 25, 28, 29)', () => {
  test.beforeEach(async () => {
    await resetDatabase();
  });

  test('should create a renewal item using the guided two-step form', async ({ page }) => {
    await page.goto('/');

    // Click Track Item in navbar
    await page.getByRole('button', { name: /Track Item/i }).click();

    // Step 1: Choose Lifecycle Type modal
    const modal = page.locator('.fixed.inset-0').filter({ hasText: /Step [12]:/i });
    await expect(modal).toBeVisible();

    // Select Renewal
    await modal.getByRole('button', { name: /Renewal/i }).click();

    // Step 2: Enter common fields
    await expect(page.locator('.fixed.inset-0').getByText(/Step 2: Track New/i)).toBeVisible();

    const testItemName = 'E2E Motorcycle Registration';
    await page.locator('#item-name').fill(testItemName);

    // Set a future date (e.g., 20 days ahead)
    const futureDate = new Date();
    futureDate.setDate(futureDate.getDate() + 20);
    const dateStr = futureDate.toISOString().slice(0, 10);
    await page.locator('#item-due-date').fill(dateStr);

    // Submit form
    await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click();
    await expect(modal).toBeHidden();

    // Verify item appears on Dashboard in Upcoming section
    await expect(page.locator(`text=${testItemName}`).first()).toBeVisible();

    // Navigate to Renewals tab and verify item is listed
    await page.getByRole('button', { name: 'Renewals' }).click();
    await expect(page.locator(`text=${testItemName}`).first()).toBeVisible();
  });

  test('should create an item with missing due date (allowed per Decision 29)', async ({ page }) => {
    await page.goto('/');

    await page.getByRole('button', { name: /Track Item/i }).click();
    const modal = page.locator('.fixed.inset-0').filter({ hasText: /Step [12]:/i });
    await modal.getByRole('button', { name: /Warranty/i }).click();

    const warrantyName = 'Vintage Mechanical Watch Warranty';
    await page.locator('#item-name').fill(warrantyName);

    // Uncheck "Has specific due date"
    const checkbox = page.locator('.fixed.inset-0 input[type="checkbox"]');
    await checkbox.uncheck();
    await expect(page.locator('.fixed.inset-0').getByText(/No due date specified/i)).toBeVisible();

    // Submit form
    await page.locator('.fixed.inset-0').getByRole('button', { name: /Track Item/i }).click();
    await expect(modal).toBeHidden();

    // Navigate to Warranties tab
    await page.getByRole('button', { name: 'Warranties' }).click();
    const itemRow = page.locator('.group').filter({ hasText: warrantyName });
    await expect(itemRow).toBeVisible();
    await expect(itemRow.getByText(/No due date/i)).toBeVisible();
  });

  test('should toggle item completion and retain completed item in main list (Decision 28)', async ({ page }) => {
    await page.goto('/');

    // Find the overdue item "Vehicle Auto Insurance Policy"
    const itemRow = page.locator('.group').filter({ hasText: 'Vehicle Auto Insurance Policy' }).first();
    await expect(itemRow).toBeVisible();

    // Click completion toggle button
    const toggleBtn = itemRow.getByRole('button', { name: /Mark as complete/i });
    await toggleBtn.click();

    // Item should now be marked complete (line-through style, check icon)
    await expect(itemRow.getByRole('button', { name: /Mark as active/i })).toBeVisible();
    await expect(itemRow.locator('span.line-through')).toContainText('Vehicle Auto Insurance Policy');

    // Per MVP Decision 28: Item remains visible in the list!
    await expect(itemRow).toBeVisible();

    // Toggle back to active
    await itemRow.getByRole('button', { name: /Mark as active/i }).click();
    await expect(itemRow.getByRole('button', { name: /Mark as complete/i })).toBeVisible();
  });

  test('should edit an existing item via edit modal', async ({ page }) => {
    await page.goto('/');

    const itemRow = page.locator('.group').filter({ hasText: 'International Passport Renewal' }).first();
    await expect(itemRow).toBeVisible();

    // Click edit button
    await itemRow.getByRole('button', { name: /Edit item/i }).click();

    const editModal = page.locator('.fixed.inset-0').filter({ hasText: /Edit Lifecycle Item/i });
    await expect(editModal).toBeVisible();

    // Change name
    const updatedName = 'International Passport Renewal (Expedited)';
    await editModal.locator('#edit-name').fill(updatedName);
    await editModal.getByRole('button', { name: /Save Changes/i }).click();

    await expect(editModal).toBeHidden();
    await expect(page.locator(`text=${updatedName}`).first()).toBeVisible();
  });

  test('should permanently delete an item (Decision 19)', async ({ page }) => {
    await page.goto('/');

    const itemName = 'International Passport Renewal';
    const itemRow = page.locator('.group').filter({ hasText: itemName }).first();
    await expect(itemRow).toBeVisible();

    // Accept window confirmation dialog
    page.once('dialog', async (dialog) => {
      await dialog.accept();
    });

    // Click delete
    await itemRow.getByRole('button', { name: /Permanently delete item/i }).click();

    // Verify item is permanently removed
    await expect(page.locator('.group').filter({ hasText: itemName })).toHaveCount(0);
  });

  test('should filter dashboard items by scope (All vs Personal vs Group)', async ({ page }) => {
    await page.goto('/');

    const scopeSelect = page.locator('select').filter({ hasText: /All Accessible Items/i }).first();
    await expect(scopeSelect).toBeVisible();

    // Select Personal Only
    await scopeSelect.selectOption('PERSONAL');
    // Group items like "Oakridge Family & Home" should not appear with group badge
    await expect(page.locator('.group').filter({ hasText: 'Shared Vehicle Maintenance' })).toHaveCount(0);

    // Select Shared Vehicle Maintenance group
    await scopeSelect.selectOption({ label: 'Group: Shared Vehicle Maintenance' });
    await expect(page.locator('.group').filter({ hasText: 'Vehicle Auto Insurance Policy' }).first()).toBeVisible();
  });
});