Files
pn-new-crm/tests/e2e/smoke/15-custom-fields.spec.ts
Matt 221ae5784e chore(autonomous-session): consolidate uncommitted work from prior session
Bundles the prior autonomous-session output that was sitting unstaged:

- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
  never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
  after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
  redirects (ocr to ai, reports to dashboard, invitations to users),
  docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
  flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
  let-reassign), set-state-in-effect disables in CountryFlag and
  UploadForSigning preview-bytes effect, unused 'confirm' destructures in
  interest contract + reservation tabs, unescaped apostrophe in test-template
  card copy
2026-05-23 00:52:59 +02:00

161 lines
6.1 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { login, navigateTo } from './helpers';
test.describe('Custom Fields', () => {
const fieldLabel = 'Test Custom Field';
test.beforeEach(async ({ page }) => {
await login(page, 'super_admin');
});
// Test 24: Navigate to custom fields admin
test('custom fields admin page loads', async ({ page }) => {
await navigateTo(page, '/admin/custom-fields');
await page.waitForTimeout(2_000);
const heading = page.getByText(/custom field/i).first();
await expect(heading).toBeVisible({ timeout: 10_000 });
// Should see entity type tabs
const clientsTab = page.getByRole('tab', { name: /client/i }).first();
await expect(clientsTab).toBeVisible({ timeout: 5_000 });
});
// Test 25: Create a text field for clients
test('create a text custom field for clients', async ({ page }) => {
await navigateTo(page, '/admin/custom-fields');
await page.waitForTimeout(2_000);
// Click create button
const createBtn = page.getByRole('button', { name: 'New Field' }).first();
await expect(createBtn).toBeVisible({ timeout: 5_000 });
await createBtn.click();
await page.waitForTimeout(1_000);
const dialog = page.getByRole('dialog').last();
await expect(dialog).toBeVisible({ timeout: 3_000 });
// Fill entity type = client (target visible Radix combobox trigger by name)
const entitySelect = dialog.getByRole('combobox', { name: /entity type/i }).first();
if (await entitySelect.isVisible({ timeout: 2_000 }).catch(() => false)) {
await entitySelect.click();
await page.waitForTimeout(300);
const clientOption = page.getByRole('option', { name: /client/i }).first();
if (await clientOption.isVisible({ timeout: 2_000 }).catch(() => false)) {
await clientOption.click();
}
}
// Fill field name (snake_case) - Radix combobox inputs are hidden so use placeholder
const nameInput = dialog.getByPlaceholder(/vessel_type/i).first();
await nameInput.fill('custom_text_test');
// Fill field label
const labelInput = dialog.getByPlaceholder(/Vessel Type/i).first();
if (await labelInput.isVisible({ timeout: 1_000 }).catch(() => false)) {
await labelInput.fill(fieldLabel);
}
// Field type should default to text or select text (target visible Radix combobox trigger)
const typeSelect = dialog.getByRole('combobox', { name: /field type/i }).first();
if (await typeSelect.isVisible({ timeout: 2_000 }).catch(() => false)) {
await typeSelect.click();
await page.waitForTimeout(300);
const textOption = page.getByRole('option', { name: /^text$/i }).first();
if (await textOption.isVisible({ timeout: 2_000 }).catch(() => false)) {
await textOption.click();
}
}
// Save
const saveBtn = dialog.getByRole('button', { name: /save|create|submit/i }).first();
if (await saveBtn.isVisible({ timeout: 2_000 }).catch(() => false)) {
await saveBtn.click();
await page.waitForTimeout(3_000);
}
// Verify the field appears in the table
const fieldRow = page.getByText(fieldLabel).or(page.getByText('custom_text_test'));
await expect(fieldRow.first()).toBeVisible({ timeout: 5_000 });
});
// Test 26: Custom field appears in client form
test('custom field appears on client detail page', async ({ page }) => {
await navigateTo(page, '/clients');
await page.waitForTimeout(2_000);
// Click into the first client
const clientRow = page.locator('table tbody tr').first();
if (await clientRow.isVisible({ timeout: 5_000 }).catch(() => false)) {
await clientRow.click();
await page.waitForTimeout(3_000);
// Custom fields section should be present (even if collapsed) - non-strict smoke
expect(true).toBeTruthy();
}
});
// Test 27: Fill custom field, save, reload, verify persistence
test('custom field value persists after reload', async ({ page }) => {
await navigateTo(page, '/clients');
await page.waitForTimeout(2_000);
const clientRow = page.locator('table tbody tr').first();
if (await clientRow.isVisible({ timeout: 5_000 }).catch(() => false)) {
await clientRow.click();
await page.waitForTimeout(3_000);
// Find custom field input and fill it
const customInput = page
.locator('input[name*="custom"], [data-testid*="custom-field"]')
.first();
if (await customInput.isVisible({ timeout: 5_000 }).catch(() => false)) {
await customInput.fill('Test Value 123');
// Trigger blur for auto-save
await customInput.blur();
await page.waitForTimeout(2_000);
// Reload and verify
await page.reload();
await page.waitForTimeout(3_000);
const reloadedInput = page
.locator('input[name*="custom"], [data-testid*="custom-field"]')
.first();
if (await reloadedInput.isVisible({ timeout: 5_000 }).catch(() => false)) {
const value = await reloadedInput.inputValue();
expect(value).toBe('Test Value 123');
}
}
}
expect(true).toBeTruthy();
});
// Test 28: Field type is locked on existing fields
test('field type dropdown is disabled for existing fields', async ({ page }) => {
await navigateTo(page, '/admin/custom-fields');
await page.waitForTimeout(2_000);
// Click edit on an existing field
const editBtn = page.getByRole('button', { name: /edit/i }).first();
if (await editBtn.isVisible({ timeout: 5_000 }).catch(() => false)) {
await editBtn.click();
await page.waitForTimeout(1_000);
const dialog = page.getByRole('dialog').last();
// Look for disabled type select or "cannot be changed" text
const disabledNote = dialog.getByText(/cannot be changed|immutable|locked/i);
const hasNote = await disabledNote.isVisible({ timeout: 3_000 }).catch(() => false);
// Or check that the type field is disabled
const typeField = dialog.locator(
'select[disabled], [role="combobox"][aria-disabled="true"], [data-disabled]',
);
const isDisabled = (await typeField.count()) > 0;
expect(hasNote || isDisabled).toBeTruthy();
}
});
});