iter 2
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user