import { expect, test } from "#tests/e2e/fixtures/projects" interface CreateProjectRequestBody { description?: string language?: string name?: string } function requirePostBody( postBody: CreateProjectRequestBody | null, ): CreateProjectRequestBody { if (!postBody) { throw new Error("Expected create project request body to be captured") } return postBody } // Note: ReactModal sets aria-modal="true" on the dialog, which makes // Playwright's getByRole() unable to find elements inside or outside the modal. // All modal content is accessed via CSS locators scoped through `modal`. test.describe("Create Project Modal", () => { test.describe("Rendering & UI", () => { test("should display create project modal with all fields", async ({ projectsPage, }) => { const { modal } = projectsPage await projectsPage.openCreateModal() await expect(modal.locator("h2")).toHaveText("Создать проект") await expect(modal.locator("p").first()).toHaveText( "Заполните основные поля проекта", ) await expect(modal.locator("#project_name")).toHaveValue("") await expect(modal.locator("#project_description")).toHaveValue("") await expect(modal.locator("button", { hasText: "Отмена" })).toBeVisible() await expect( modal.locator("button", { hasText: "Создать" }), ).toBeVisible() }) test("should show default language as Авто", async ({ projectsPage }) => { const { modal } = projectsPage await projectsPage.openCreateModal() const selectTrigger = modal.locator("button").filter({ hasText: "Авто" }) await expect(selectTrigger).toBeVisible() }) }) test.describe("Happy Path", () => { test("should create project with name only", async ({ projectsPage }) => { const { page, modal } = projectsPage let postBody: CreateProjectRequestBody | null = null await projectsPage.mockCreateSuccess({ name: "Мой проект" }) page.on("request", (req) => { if (req.url().includes("/api/projects/") && req.method() === "POST") { postBody = req.postDataJSON() } }) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Мой проект") await modal.locator("button", { hasText: "Создать" }).click() // Modal should close await expect(modal).toBeHidden() const requestBody = requirePostBody(postBody) expect(requestBody.name).toBe("Мой проект") expect(requestBody.language).toBe("auto") }) test("should create project with name and description", async ({ projectsPage, }) => { const { page, modal } = projectsPage let postBody: CreateProjectRequestBody | null = null await projectsPage.mockCreateSuccess({ name: "Мой проект", description: "Описание проекта", }) page.on("request", (req) => { if (req.url().includes("/api/projects/") && req.method() === "POST") { postBody = req.postDataJSON() } }) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Мой проект") await modal.locator("#project_description").fill("Описание проекта") await modal.locator("button", { hasText: "Создать" }).click() await expect(modal).toBeHidden() const requestBody = requirePostBody(postBody) expect(requestBody.description).toBe("Описание проекта") }) test("should create project with different language", async ({ projectsPage, }) => { const { page, modal } = projectsPage let postBody: CreateProjectRequestBody | null = null await projectsPage.mockCreateSuccess({ name: "Мой проект", language: "ru", }) page.on("request", (req) => { if (req.url().includes("/api/projects/") && req.method() === "POST") { postBody = req.postDataJSON() } }) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Мой проект") // Open language select and pick Russian await modal.locator("button").filter({ hasText: "Авто" }).click() await page.locator("[role=option]", { hasText: "Русский" }).click() await modal.locator("button", { hasText: "Создать" }).click() await expect(modal).toBeHidden() const requestBody = requirePostBody(postBody) expect(requestBody.language).toBe("ru") }) test("should refresh projects list after creation", async ({ projectsPage, }) => { const { page, modal } = projectsPage // Verify empty state await expect(page.getByText("У вас пока нет проектов")).toBeVisible() await projectsPage.mockCreateSuccess({ name: "Новый проект" }) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Новый проект") await modal.locator("button", { hasText: "Создать" }).click() // Modal closes and project appears in list await expect(modal).toBeHidden() await expect(page.getByText("У вас пока нет проектов")).toBeHidden() await expect(page.getByText("Новый проект")).toBeVisible() }) }) test.describe("Validation", () => { test("should show validation error for empty name", async ({ projectsPage, }) => { const { page, modal } = projectsPage let postFired = false page.on("request", (req) => { if (req.url().includes("/api/projects/") && req.method() === "POST") { postFired = true } }) await projectsPage.openCreateModal() await modal.locator("button", { hasText: "Создать" }).click() await expect(modal.getByText("Введите название проекта")).toBeVisible() expect(postFired).toBe(false) // Modal stays open await expect(modal).toBeVisible() }) test("should show validation error for whitespace-only name", async ({ projectsPage, }) => { const { modal } = projectsPage await projectsPage.openCreateModal() await modal.locator("#project_name").fill(" ") await modal.locator("button", { hasText: "Создать" }).click() await expect(modal.getByText("Введите название проекта")).toBeVisible() }) test("should clear validation error after correcting name", async ({ projectsPage, }) => { const { modal } = projectsPage await projectsPage.mockCreateSuccess({ name: "Проект" }) await projectsPage.openCreateModal() // Trigger validation error await modal.locator("button", { hasText: "Создать" }).click() await expect(modal.getByText("Введите название проекта")).toBeVisible() // Fix and resubmit await modal.locator("#project_name").fill("Проект") await modal.locator("button", { hasText: "Создать" }).click() await expect(modal).toBeHidden() }) }) test.describe("Error States", () => { test("should keep modal open on server error (500)", async ({ projectsPage, }) => { const { page, modal } = projectsPage const consoleErrors: string[] = [] page.on("console", (msg) => { if (msg.type() === "error") { consoleErrors.push(msg.text()) } }) await projectsPage.mockCreateError(500) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Проект") await modal.locator("button", { hasText: "Создать" }).click() // Wait for error to be processed await page.waitForTimeout(1000) // Modal stays open, buttons re-enable await expect(modal).toBeVisible() await expect( modal.locator("button", { hasText: "Создать" }), ).toBeEnabled() await expect(modal.locator("button", { hasText: "Отмена" })).toBeEnabled() expect( consoleErrors.some((e) => e.includes("Create project failed")), ).toBe(true) }) test("should keep modal open on network error", async ({ projectsPage, }) => { const { page, modal } = projectsPage await projectsPage.mockCreateNetworkError() await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Проект") await modal.locator("button", { hasText: "Создать" }).click() await page.waitForTimeout(1000) await expect(modal).toBeVisible() await expect( modal.locator("button", { hasText: "Создать" }), ).toBeEnabled() await expect(modal.locator("button", { hasText: "Отмена" })).toBeEnabled() }) test("should allow retry after failure", async ({ projectsPage }) => { const { page, modal } = projectsPage // First attempt: 500 error await projectsPage.mockCreateError(500) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Проект") await modal.locator("button", { hasText: "Создать" }).click() await page.waitForTimeout(1000) await expect( modal.locator("button", { hasText: "Создать" }), ).toBeEnabled() // Second attempt: success (re-mock the route) await projectsPage.mockCreateSuccess({ name: "Проект" }) await modal.locator("button", { hasText: "Создать" }).click() await expect(modal).toBeHidden() }) }) test.describe("Form Behavior", () => { test("should reset form when modal is closed and reopened", async ({ projectsPage, }) => { const { modal } = projectsPage await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Черновик") await modal.locator("#project_description").fill("Описание черновика") // Close with Cancel await modal.locator("button", { hasText: "Отмена" }).click() await expect(modal).toBeHidden() // Reopen await projectsPage.openCreateModal() await expect(modal.locator("#project_name")).toHaveValue("") await expect(modal.locator("#project_description")).toHaveValue("") await expect( modal.locator("button").filter({ hasText: "Авто" }), ).toBeVisible() }) test("should disable buttons while request is pending", async ({ projectsPage, }) => { const { modal } = projectsPage await projectsPage.mockCreateDelayed(2000) await projectsPage.openCreateModal() await modal.locator("#project_name").fill("Проект") await modal.locator("button", { hasText: "Создать" }).click() await expect( modal.locator("button", { hasText: "Создать" }), ).toBeDisabled() await expect( modal.locator("button", { hasText: "Отмена" }), ).toBeDisabled() }) }) })