125 lines
4.7 KiB
TypeScript
125 lines
4.7 KiB
TypeScript
import { test, expect, Page } from '@playwright/test';
|
||
import {
|
||
login,
|
||
navigateAndWait,
|
||
waitForElement,
|
||
hoverAndWaitForVisible,
|
||
createUniqueEntity,
|
||
waitForNetworkIdle
|
||
} from '../helpers/testHelpers';
|
||
|
||
// Helper to create a task
|
||
async function createTask(page: Page, taskName: string) {
|
||
const taskInput = page.locator('[data-testid="new-task-input"]');
|
||
await taskInput.fill(taskName);
|
||
await taskInput.press('Enter');
|
||
await waitForNetworkIdle(page);
|
||
}
|
||
|
||
// Helper to open task edit modal
|
||
async function openTaskEditModal(page: Page, taskName: string) {
|
||
const taskContainer = page.locator('[data-testid*="task-item"]').filter({ hasText: taskName });
|
||
await expect(taskContainer).toBeVisible({ timeout: 15000 });
|
||
|
||
const editButton = taskContainer.locator('[data-testid*="task-edit"]');
|
||
await hoverAndWaitForVisible(taskContainer, editButton);
|
||
|
||
await editButton.click();
|
||
|
||
const taskNameInput = page.locator('[data-testid="task-name-input"]');
|
||
await waitForElement(taskNameInput, { timeout: 15000 });
|
||
|
||
return taskNameInput;
|
||
}
|
||
|
||
// Helper to create a project
|
||
async function createProject(page: Page, projectName: string) {
|
||
const addProjectButton = page.locator('button[aria-label="Add Project"]');
|
||
await expect(addProjectButton).toBeVisible();
|
||
await addProjectButton.click();
|
||
|
||
await expect(page.locator('input[name="name"]')).toBeVisible({ timeout: 10000 });
|
||
|
||
const nameInput = page.locator('[data-testid="project-name-input"]');
|
||
await nameInput.click();
|
||
await nameInput.clear();
|
||
await nameInput.fill(projectName);
|
||
|
||
await expect(nameInput).toHaveValue(projectName, { timeout: 2000 });
|
||
|
||
const saveButton = page.locator('[data-testid="project-save-button"]');
|
||
await expect(saveButton).toBeEnabled();
|
||
await saveButton.click();
|
||
|
||
await expect(page.locator('[data-testid="project-modal"]')).not.toBeVisible({ timeout: 15000 });
|
||
}
|
||
|
||
test('comprehensive project assignment functionality in task modal', async ({ page, baseURL }) => {
|
||
const appUrl = await login(page, baseURL);
|
||
|
||
// === SETUP: Create two projects ===
|
||
await navigateAndWait(page, appUrl + '/projects');
|
||
const project1Name = createUniqueEntity('First Project');
|
||
await createProject(page, project1Name);
|
||
|
||
const project2Name = createUniqueEntity('Second Project');
|
||
await createProject(page, project2Name);
|
||
|
||
// Navigate to tasks page
|
||
await navigateAndWait(page, appUrl + '/tasks');
|
||
|
||
// Create a task
|
||
const taskName = createUniqueEntity('Task with Project');
|
||
await createTask(page, taskName);
|
||
|
||
// === TEST 1: Assign project using keyboard navigation ===
|
||
await openTaskEditModal(page, taskName);
|
||
await expect(page.locator('[data-testid="task-modal"][data-state="idle"]')).toBeVisible();
|
||
|
||
let taskModal = page.locator('[data-testid="task-modal"]');
|
||
let projectSectionButton = taskModal.locator('button[title="Project"]').filter({ has: page.locator('svg') });
|
||
await projectSectionButton.click();
|
||
|
||
let projectSearchInput = page.locator('input[placeholder*="Search or create"]');
|
||
await expect(projectSearchInput).toBeVisible({ timeout: 5000 });
|
||
await projectSearchInput.fill(project1Name);
|
||
await page.waitForTimeout(500);
|
||
|
||
// Use arrow down + Enter
|
||
await projectSearchInput.press('ArrowDown');
|
||
await projectSearchInput.press('Enter');
|
||
|
||
// Verify badge appears and input is hidden
|
||
let projectBadge = taskModal.locator('span').filter({ hasText: project1Name });
|
||
await expect(projectBadge).toBeVisible();
|
||
await expect(projectSearchInput).not.toBeVisible();
|
||
|
||
// === TEST 2: Cannot add a second project (single project restriction) ===
|
||
// Badge should be visible, no input available
|
||
const inputStillGone = page.locator('input[placeholder*="Search or create"]');
|
||
await expect(inputStillGone).not.toBeVisible();
|
||
|
||
// === TEST 3: Remove project by clicking × button ===
|
||
const removeButton = projectBadge.locator('button');
|
||
await expect(removeButton).toBeVisible();
|
||
await removeButton.click();
|
||
|
||
// Badge should disappear, input should reappear
|
||
await expect(projectBadge).not.toBeVisible();
|
||
projectSearchInput = page.locator('input[placeholder*="Search or create"]');
|
||
await expect(projectSearchInput).toBeVisible();
|
||
|
||
// === TEST 4: Exact match auto-selection with Enter ===
|
||
// Type exact project name and press Enter (no arrow keys needed)
|
||
await projectSearchInput.fill(project2Name);
|
||
await page.waitForTimeout(500);
|
||
await projectSearchInput.press('Enter');
|
||
|
||
// Verify project is selected immediately
|
||
projectBadge = taskModal.locator('span').filter({ hasText: project2Name });
|
||
await expect(projectBadge).toBeVisible();
|
||
await expect(projectSearchInput).not.toBeVisible();
|
||
|
||
// Test complete - all project assignment functionality verified
|
||
// No need to close modal as test is finished
|
||
});
|