test(project): Add Phase 1 pending tests for Project Lifecycle
Capability 3: Project Lifecycle Management - Phase 1 (RED) E2E Tests (12 test.fixme): - Create project with unique code - Reject duplicate project code - Valid/invalid status transitions - Estimate approved requires estimate > 0 - Workflow progression - Estimate rework path - Project on hold - Cancelled project - Set approved estimate - Update forecasted effort - Validate forecasted effort API Tests (9 markTestIncomplete): - POST /api/projects - Project code uniqueness - Status transition validation - Estimate/forecast endpoints Unit Tests (3 markTestIncomplete): - Project status state machine - ProjectPolicy authorization - Forecasted effort validation All 173 tests passing (31 backend, 32 frontend, 110 E2E)
This commit is contained in:
@@ -53,3 +53,178 @@ test.describe('Projects Page', () => {
|
||||
expect(filteredRows).toBeLessThanOrEqual(initialRows);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Project Lifecycle Management - Phase 1 Tests (RED)', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login first
|
||||
await page.goto('/login');
|
||||
await page.fill('input[type="email"]', 'superuser@headroom.test');
|
||||
await page.fill('input[type="password"]', 'password');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL('/dashboard');
|
||||
|
||||
// Navigate to projects
|
||||
await page.goto('/projects');
|
||||
});
|
||||
|
||||
// 3.1.1 E2E test: Create project with unique code
|
||||
test.fixme('create project with unique code', async ({ page }) => {
|
||||
// Click New Project button
|
||||
await page.getByRole('button', { name: /New Project/i }).click();
|
||||
|
||||
// Fill in the form
|
||||
await page.fill('input[name="code"]', 'PROJ-TEST-001');
|
||||
await page.fill('input[name="title"]', 'Test Project E2E');
|
||||
await page.selectOption('select[name="type_id"]', { index: 1 });
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', { name: /Create/i }).click();
|
||||
|
||||
// Verify the project was created
|
||||
await expect(page.getByText('PROJ-TEST-001')).toBeVisible();
|
||||
await expect(page.getByText('Test Project E2E')).toBeVisible();
|
||||
});
|
||||
|
||||
// 3.1.2 E2E test: Reject duplicate project code
|
||||
test.fixme('reject duplicate project code', async ({ page }) => {
|
||||
// Click New Project button
|
||||
await page.getByRole('button', { name: /New Project/i }).click();
|
||||
|
||||
// Fill in the form with a code that already exists
|
||||
await page.fill('input[name="code"]', 'PROJ-001'); // Assume this exists from seed
|
||||
await page.fill('input[name="title"]', 'Duplicate Code Project');
|
||||
await page.selectOption('select[name="type_id"]', { index: 1 });
|
||||
|
||||
// Submit the form
|
||||
await page.getByRole('button', { name: /Create/i }).click();
|
||||
|
||||
// Verify validation error
|
||||
await expect(page.getByText('Project code must be unique')).toBeVisible();
|
||||
});
|
||||
|
||||
// 3.1.3 E2E test: Valid status transitions
|
||||
test.fixme('valid status transitions', async ({ page }) => {
|
||||
// Wait for table to load
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Click on first project to edit
|
||||
await page.locator('table tbody tr').first().click();
|
||||
|
||||
// Wait for modal
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Change status to next valid state
|
||||
await page.selectOption('select[name="status_id"]', { label: 'Gathering Estimates' });
|
||||
|
||||
// Submit
|
||||
await page.getByRole('button', { name: /Update/i }).click();
|
||||
|
||||
// Modal should close
|
||||
await expect(page.locator('.modal-box')).not.toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// 3.1.4 E2E test: Invalid status transitions rejected
|
||||
test.fixme('invalid status transitions rejected', async ({ page }) => {
|
||||
// Wait for table
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Click on a project in Initial status
|
||||
await page.locator('table tbody tr').first().click();
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Try to skip to a status that's not directly reachable
|
||||
await page.selectOption('select[name="status_id"]', { label: 'Done' });
|
||||
|
||||
// Submit
|
||||
await page.getByRole('button', { name: /Update/i }).click();
|
||||
|
||||
// Should show error
|
||||
await expect(page.locator('.alert-error')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// 3.1.5 E2E test: Estimate approved requires approved_estimate > 0
|
||||
test.fixme('estimate approved requires approved estimate', async ({ page }) => {
|
||||
// Wait for table
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
|
||||
// Click on project that can transition to Estimate Approved
|
||||
await page.locator('table tbody tr').first().click();
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Try to set status to Estimate Approved without approved estimate
|
||||
await page.selectOption('select[name="status_id"]', { label: 'Estimate Approved' });
|
||||
await page.getByRole('button', { name: /Update/i }).click();
|
||||
|
||||
// Should show validation error
|
||||
await expect(page.getByText('approved estimate')).toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// 3.1.6 E2E test: Workflow progression through all statuses
|
||||
test.fixme('workflow progression through all statuses', async ({ page }) => {
|
||||
// This is a complex test that would progress through the entire workflow
|
||||
// For now, just verify the status dropdown has expected options
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
await page.locator('table tbody tr').first().click();
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Check that status dropdown has key statuses
|
||||
const statusSelect = page.locator('select[name="status_id"]');
|
||||
await expect(statusSelect).toBeVisible();
|
||||
|
||||
// Just verify modal can be closed
|
||||
await page.getByRole('button', { name: /Cancel/i }).click();
|
||||
await expect(page.locator('.modal-box')).not.toBeVisible();
|
||||
});
|
||||
|
||||
// 3.1.7 E2E test: Estimate rework path
|
||||
test.fixme('estimate rework path', async ({ page }) => {
|
||||
// This tests the rework workflow path
|
||||
await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible();
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
// 3.1.8 E2E test: Project on hold preserves allocations
|
||||
test.fixme('project on hold preserves allocations', async ({ page }) => {
|
||||
// This tests that putting a project on hold doesn't delete allocations
|
||||
await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible();
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
// 3.1.9 E2E test: Cancelled project prevents new allocations
|
||||
test.fixme('cancelled project prevents new allocations', async ({ page }) => {
|
||||
// This tests that cancelled projects can't have new allocations
|
||||
await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible();
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
|
||||
// 3.1.10 E2E test: Set approved estimate
|
||||
test.fixme('set approved estimate', async ({ page }) => {
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
await page.locator('table tbody tr').first().click();
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Set approved estimate
|
||||
await page.fill('input[name="approved_estimate"]', '120');
|
||||
|
||||
// Submit
|
||||
await page.getByRole('button', { name: /Update/i }).click();
|
||||
await expect(page.locator('.modal-box')).not.toBeVisible({ timeout: 5000 });
|
||||
});
|
||||
|
||||
// 3.1.11 E2E test: Update forecasted effort
|
||||
test.fixme('update forecasted effort', async ({ page }) => {
|
||||
await expect(page.locator('table tbody tr').first()).toBeVisible({ timeout: 5000 });
|
||||
await page.locator('table tbody tr').first().click();
|
||||
await expect(page.locator('.modal-box')).toBeVisible();
|
||||
|
||||
// Just verify the form can be closed
|
||||
await page.getByRole('button', { name: /Cancel/i }).click();
|
||||
await expect(page.locator('.modal-box')).not.toBeVisible();
|
||||
});
|
||||
|
||||
// 3.1.12 E2E test: Validate forecasted effort equals approved estimate
|
||||
test.fixme('validate forecasted effort equals approved estimate', async ({ page }) => {
|
||||
await expect(page.locator('h1', { hasText: 'Projects' })).toBeVisible();
|
||||
expect(true).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user