Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { test, expect } from '@playwright/test';
|
2026-04-22 17:24:52 +02:00
|
|
|
import { login, navigateTo } from './helpers';
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
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
|
2026-04-22 17:24:52 +02:00
|
|
|
const createBtn = page.getByRole('button', { name: 'New Field' }).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
await expect(createBtn).toBeVisible({ timeout: 5_000 });
|
|
|
|
|
await createBtn.click();
|
|
|
|
|
await page.waitForTimeout(1_000);
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
const dialog = page.getByRole('dialog').last();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
await expect(dialog).toBeVisible({ timeout: 3_000 });
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
// Fill entity type = client (target visible Radix combobox trigger by name)
|
|
|
|
|
const entitySelect = dialog.getByRole('combobox', { name: /entity type/i }).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
// Fill field name (snake_case) — Radix combobox inputs are hidden so use placeholder
|
|
|
|
|
const nameInput = dialog.getByPlaceholder(/vessel_type/i).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
await nameInput.fill('custom_text_test');
|
|
|
|
|
|
|
|
|
|
// Fill field label
|
2026-04-22 17:24:52 +02:00
|
|
|
const labelInput = dialog.getByPlaceholder(/Vessel Type/i).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
if (await labelInput.isVisible({ timeout: 1_000 }).catch(() => false)) {
|
|
|
|
|
await labelInput.fill(fieldLabel);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
// Field type should default to text or select text (target visible Radix combobox trigger)
|
|
|
|
|
const typeSelect = dialog.getByRole('combobox', { name: /field type/i }).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
if (await typeSelect.isVisible({ timeout: 2_000 }).catch(() => false)) {
|
|
|
|
|
await typeSelect.click();
|
|
|
|
|
await page.waitForTimeout(300);
|
2026-04-22 17:24:52 +02:00
|
|
|
const textOption = page.getByRole('option', { name: /^text$/i }).first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
// Custom fields section should be present (even if collapsed) — non-strict smoke
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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
|
2026-04-22 17:24:52 +02:00
|
|
|
const customInput = page
|
|
|
|
|
.locator('input[name*="custom"], [data-testid*="custom-field"]')
|
|
|
|
|
.first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
const reloadedInput = page
|
|
|
|
|
.locator('input[name*="custom"], [data-testid*="custom-field"]')
|
|
|
|
|
.first();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
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);
|
|
|
|
|
|
2026-04-22 17:24:52 +02:00
|
|
|
const dialog = page.getByRole('dialog').last();
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
// 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
|
2026-04-22 17:24:52 +02:00
|
|
|
const typeField = dialog.locator(
|
|
|
|
|
'select[disabled], [role="combobox"][aria-disabled="true"], [data-disabled]',
|
|
|
|
|
);
|
|
|
|
|
const isDisabled = (await typeField.count()) > 0;
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
|
|
|
|
|
expect(hasNote || isDisabled).toBeTruthy();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|