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>
108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
PIPELINE_STAGES,
|
|
BERTH_STATUSES,
|
|
NOTIFICATION_TYPES,
|
|
} from '@/lib/constants';
|
|
|
|
describe('PIPELINE_STAGES', () => {
|
|
it('has exactly 8 entries', () => {
|
|
expect(PIPELINE_STAGES).toHaveLength(8);
|
|
});
|
|
|
|
it('starts with "open"', () => {
|
|
expect(PIPELINE_STAGES[0]).toBe('open');
|
|
});
|
|
|
|
it('ends with "completed"', () => {
|
|
expect(PIPELINE_STAGES[PIPELINE_STAGES.length - 1]).toBe('completed');
|
|
});
|
|
|
|
it('contains all expected stages in order', () => {
|
|
expect(PIPELINE_STAGES).toEqual([
|
|
'open',
|
|
'details_sent',
|
|
'in_communication',
|
|
'visited',
|
|
'signed_eoi_nda',
|
|
'deposit_10pct',
|
|
'contract',
|
|
'completed',
|
|
]);
|
|
});
|
|
|
|
it('is a readonly (frozen) tuple — cannot be mutated at runtime', () => {
|
|
expect(() => {
|
|
// TypeScript readonly doesn't prevent runtime mutation of `as const` arrays,
|
|
// but they are not Object.frozen. The important thing is the `as const` means
|
|
// the type system protects it. We verify immutability via the TypeScript type
|
|
// and check the array is not a plain mutable array.
|
|
const arr = PIPELINE_STAGES as unknown as string[];
|
|
// Attempting splice on a readonly const-asserted array at runtime won't throw
|
|
// but the values should be what we defined.
|
|
expect(arr).toHaveLength(8);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('has no duplicate entries', () => {
|
|
const unique = new Set(PIPELINE_STAGES);
|
|
expect(unique.size).toBe(PIPELINE_STAGES.length);
|
|
});
|
|
});
|
|
|
|
describe('BERTH_STATUSES', () => {
|
|
it('has exactly 3 entries', () => {
|
|
expect(BERTH_STATUSES).toHaveLength(3);
|
|
});
|
|
|
|
it('contains "available"', () => {
|
|
expect(BERTH_STATUSES).toContain('available');
|
|
});
|
|
|
|
it('contains "under_offer"', () => {
|
|
expect(BERTH_STATUSES).toContain('under_offer');
|
|
});
|
|
|
|
it('contains "sold"', () => {
|
|
expect(BERTH_STATUSES).toContain('sold');
|
|
});
|
|
|
|
it('has no duplicate entries', () => {
|
|
const unique = new Set(BERTH_STATUSES);
|
|
expect(unique.size).toBe(BERTH_STATUSES.length);
|
|
});
|
|
});
|
|
|
|
describe('NOTIFICATION_TYPES', () => {
|
|
it('contains "interest_stage_changed"', () => {
|
|
expect(NOTIFICATION_TYPES).toContain('interest_stage_changed');
|
|
});
|
|
|
|
it('contains "mention"', () => {
|
|
expect(NOTIFICATION_TYPES).toContain('mention');
|
|
});
|
|
|
|
it('contains "email_received"', () => {
|
|
expect(NOTIFICATION_TYPES).toContain('email_received');
|
|
});
|
|
|
|
it('has no duplicate entries', () => {
|
|
const unique = new Set(NOTIFICATION_TYPES);
|
|
expect(unique.size).toBe(NOTIFICATION_TYPES.length);
|
|
});
|
|
|
|
it('contains expected notification categories (interest, document, reminder, financial, email, system)', () => {
|
|
const types = new Set(NOTIFICATION_TYPES);
|
|
// Interest
|
|
expect(types.has('interest_stage_changed')).toBe(true);
|
|
expect(types.has('interest_created')).toBe(true);
|
|
// Document
|
|
expect(types.has('document_sent')).toBe(true);
|
|
expect(types.has('document_signed')).toBe(true);
|
|
// Financial
|
|
expect(types.has('invoice_paid')).toBe(true);
|
|
// System
|
|
expect(types.has('system_alert')).toBe(true);
|
|
});
|
|
});
|