Files
Inventory/frontend/e2e/product-category-crud.spec.ts
Matthieu 974a4a0781 refactor : merge Inventory_frontend submodule into frontend/ directory
Merges the full git history of Inventory_frontend into the monorepo
under frontend/. Removes the submodule in favor of a unified repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 14:17:57 +02:00

167 lines
7.7 KiB
TypeScript

import { test, expect } from '@playwright/test'
/**
* E2E tests for Product Category CRUD operations.
*
* Prerequisites:
* - Frontend running on http://localhost:3001 (npm run dev)
* - Backend running on http://localhost:8081 (docker compose up)
* - Auth setup must run first (profile selected)
*/
const UNIQUE = Date.now()
const CATEGORY_NAME = `E2E Catégorie Produit ${UNIQUE}`
const CATEGORY_NOTES = `Notes de test automatisé ${UNIQUE}`
const CATEGORY_NAME_UPDATED = `${CATEGORY_NAME} modifié`
const CATEGORY_NOTES_UPDATED = `${CATEGORY_NOTES} — mis à jour`
test.describe('Product Category CRUD', () => {
test.describe.configure({ mode: 'serial' })
// ──────────────────────────────────────────────
// CREATE
// ──────────────────────────────────────────────
test('should display the product category list page', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('heading', { name: 'Catégories de produit' })).toBeVisible({ timeout: 10_000 })
await expect(page.getByText('Catégories enregistrées')).toBeVisible()
})
test('should navigate to the create form', async ({ page }) => {
await page.goto('/product-category')
// The toolbar button text is "Créer" (with a plus icon)
await page.getByRole('button', { name: /créer/i }).click()
await expect(page).toHaveURL('/product-category/new')
await expect(page.getByRole('heading', { name: 'Nouvelle catégorie de produit' })).toBeVisible()
})
test('should show validation error for short name', async ({ page }) => {
await page.goto('/product-category/new')
await page.locator('#model-type-name').fill('A')
// The form submit button in ModelTypeForm is also "Créer"
await page.locator('button[type="submit"]').click()
await expect(page.getByText('Le nom doit contenir au moins 2 caractères')).toBeVisible()
})
test('should create a new product category', async ({ page }) => {
await page.goto('/product-category/new')
await page.locator('#model-type-name').fill(CATEGORY_NAME)
await page.locator('#model-type-notes').fill(CATEGORY_NOTES)
// Verify category is locked to PRODUCT
const categorySelect = page.locator('#model-type-category')
await expect(categorySelect).toBeDisabled()
await expect(categorySelect).toHaveValue('PRODUCT')
await page.locator('button[type="submit"]').click()
// Should redirect to list and show success toast
await expect(page).toHaveURL('/product-category', { timeout: 10_000 })
await expect(page.getByText('Catégorie de produit créée avec succès')).toBeVisible()
})
// ──────────────────────────────────────────────
// READ
// ──────────────────────────────────────────────
test('should display the created category in the list', async ({ page }) => {
await page.goto('/product-category')
// Target the table cell specifically (desktop view also renders a mobile card)
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
})
test('should find the category via search', async ({ page }) => {
await page.goto('/product-category')
// Type in search input (placeholder: "Rechercher par nom…")
const searchInput = page.getByPlaceholder('Rechercher par nom…')
await searchInput.fill(UNIQUE.toString())
// Wait for debounce (300ms) + API response
await page.waitForTimeout(500)
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 5_000 })
})
// ──────────────────────────────────────────────
// UPDATE
// ──────────────────────────────────────────────
test('should navigate to the edit page', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
// Find the row with our category and click "Éditer"
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME })
await row.getByRole('button', { name: 'Éditer' }).click()
await expect(page.getByRole('heading', { name: /modifier/i })).toBeVisible({ timeout: 10_000 })
})
test('should edit the category name and notes', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME })
await row.getByRole('button', { name: 'Éditer' }).click()
await expect(page.getByRole('heading', { name: /modifier/i })).toBeVisible({ timeout: 10_000 })
// Update name
const nameInput = page.locator('#model-type-name')
await nameInput.clear()
await nameInput.fill(CATEGORY_NAME_UPDATED)
// Update notes
const notesTextarea = page.locator('#model-type-notes')
await notesTextarea.clear()
await notesTextarea.fill(CATEGORY_NOTES_UPDATED)
await page.locator('button[type="submit"]').click()
// Should redirect and show success
await expect(page).toHaveURL('/product-category', { timeout: 10_000 })
await expect(page.getByText('Catégorie de produit mise à jour avec succès')).toBeVisible()
})
test('should display updated category in the list', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
})
// ──────────────────────────────────────────────
// DELETE
// ──────────────────────────────────────────────
test('should cancel deletion when clicking Annuler', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirmation modal should appear
await expect(page.getByText('Supprimer ce type ?')).toBeVisible()
await page.getByRole('button', { name: 'Annuler' }).click()
// Category should still be present
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible()
})
test('should delete the category', async ({ page }) => {
await page.goto('/product-category')
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).toBeVisible({ timeout: 10_000 })
const row = page.getByRole('row').filter({ hasText: CATEGORY_NAME_UPDATED })
await row.getByRole('button', { name: 'Supprimer' }).click()
// Confirm deletion in modal
await expect(page.getByText('Supprimer ce type ?')).toBeVisible()
// Click the confirm "Supprimer" button inside the modal (btn-error style)
await page.locator('button.btn-error').filter({ hasText: 'Supprimer' }).click()
// Should show success toast and category should disappear
await expect(page.getByText(/supprimé avec succès/i)).toBeVisible({ timeout: 10_000 })
await expect(page.getByRole('cell', { name: CATEGORY_NAME_UPDATED })).not.toBeVisible()
})
})