2026-06-02 17:22:08 +02:00
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
|
|
|
|
|
|
import { extractInquiryFields } from '@/lib/services/website-intake-fields';
|
|
|
|
|
|
|
|
|
|
describe('extractInquiryFields', () => {
|
|
|
|
|
it('maps a berth inquiry payload (berth -> mooringNumber)', () => {
|
|
|
|
|
const f = extractInquiryFields({
|
|
|
|
|
first_name: 'Jane',
|
|
|
|
|
last_name: 'Doe',
|
|
|
|
|
email: 'jane@example.com',
|
|
|
|
|
phone: '+15551234',
|
|
|
|
|
berth: 'A1',
|
|
|
|
|
interest: 'berths',
|
|
|
|
|
});
|
|
|
|
|
expect(f).toMatchObject({
|
|
|
|
|
firstName: 'Jane',
|
|
|
|
|
lastName: 'Doe',
|
|
|
|
|
fullName: 'Jane Doe',
|
|
|
|
|
email: 'jane@example.com',
|
|
|
|
|
phone: '+15551234',
|
|
|
|
|
mooringNumber: 'A1',
|
|
|
|
|
placeOfResidence: null,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('maps a residence inquiry payload (address -> placeOfResidence, no mooring)', () => {
|
|
|
|
|
const f = extractInquiryFields({
|
|
|
|
|
first_name: 'Sam',
|
|
|
|
|
last_name: 'Lee',
|
|
|
|
|
email: 's@example.com',
|
|
|
|
|
phone: '2',
|
|
|
|
|
address: 'London',
|
|
|
|
|
interest: 'residences',
|
|
|
|
|
});
|
|
|
|
|
expect(f.mooringNumber).toBeNull();
|
|
|
|
|
expect(f.placeOfResidence).toBe('London');
|
|
|
|
|
expect(f.fullName).toBe('Sam Lee');
|
|
|
|
|
});
|
|
|
|
|
|
feat(intake): residence-type capture + CRM-owned inquiry emails for website cutover
Website register-interest form now offers the 3 residence types as a
multi-select; the choice + preferred-contact flow into the CRM inquiry
payload, the inbox detail, and the residential emails.
- inquiry inbox detail surfaces residence type(s), preferred contact,
type-of-interest, comments (full data capture)
- residential-inquiry emails: client confirmation names the chosen
villa(s); sales alert converted to the canonical detail-line format
(uniform with berth/contact) + residence type(s)/preferred contact +
plain-text part
- website-intake-fields parses residence_types[] + method_of_contact
- contact_form alerts split to their own recipient key
(contact_notification_recipients)
- Residential Interests section: new residence_type field (schema +
migration 0099, validators, inline select on the detail)
- contact-form-alert email refactor shipped (interest-alert style)
Tests: website-intake-fields, residential-inquiry templates,
contact-form-alert, residential-interest validators.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2qc3xZTfif7N4Wq3QDa8X
2026-06-25 20:58:53 +02:00
|
|
|
it('maps residence_types[] + method_of_contact from the register form', () => {
|
|
|
|
|
const f = extractInquiryFields({
|
|
|
|
|
first_name: 'Mia',
|
|
|
|
|
last_name: 'Ng',
|
|
|
|
|
email: 'mia@example.com',
|
|
|
|
|
interest: 'residences',
|
|
|
|
|
residence_types: ['Two Bedroom Marina Villa', 'Five Bedroom Oceanfront Villa'],
|
|
|
|
|
method_of_contact: 'phone',
|
|
|
|
|
});
|
|
|
|
|
expect(f.residenceTypes).toEqual(['Two Bedroom Marina Villa', 'Five Bedroom Oceanfront Villa']);
|
|
|
|
|
expect(f.preferredContact).toBe('phone');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('coerces a lone residence_types string to a single-item array and filters blanks', () => {
|
|
|
|
|
const f = extractInquiryFields({
|
|
|
|
|
residence_types: ['Two Bedroom Marina Villa', '', 7 as unknown as string],
|
|
|
|
|
method_of_contact: 'EMAIL',
|
|
|
|
|
});
|
|
|
|
|
expect(f.residenceTypes).toEqual(['Two Bedroom Marina Villa']);
|
|
|
|
|
expect(f.preferredContact).toBe('email');
|
|
|
|
|
|
|
|
|
|
const single = extractInquiryFields({ residence_types: 'Four Bedroom Oceanfront Villa' });
|
|
|
|
|
expect(single.residenceTypes).toEqual(['Four Bedroom Oceanfront Villa']);
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-02 17:22:08 +02:00
|
|
|
it('maps a contact form payload (interest[] -> joined interestType + comments)', () => {
|
|
|
|
|
const f = extractInquiryFields({
|
|
|
|
|
first_name: 'Ann',
|
|
|
|
|
last_name: 'Poe',
|
|
|
|
|
email: 'a@example.com',
|
|
|
|
|
interest: ['owner', 'broker'],
|
|
|
|
|
comments: 'Please call me',
|
|
|
|
|
});
|
|
|
|
|
expect(f.interestType).toBe('owner, broker');
|
|
|
|
|
expect(f.comments).toBe('Please call me');
|
|
|
|
|
expect(f.phone).toBe('');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('trims whitespace and degrades missing/garbage fields safely', () => {
|
|
|
|
|
const f = extractInquiryFields({ first_name: ' Jo ', last_name: 42 as unknown });
|
|
|
|
|
expect(f.firstName).toBe('Jo');
|
|
|
|
|
expect(f.fullName).toBe('Jo');
|
|
|
|
|
expect(f.email).toBe('');
|
|
|
|
|
expect(f.mooringNumber).toBeNull();
|
|
|
|
|
expect(f.interestType).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('returns all-empty for an empty payload', () => {
|
|
|
|
|
expect(extractInquiryFields({})).toMatchObject({
|
|
|
|
|
firstName: '',
|
|
|
|
|
lastName: '',
|
|
|
|
|
fullName: '',
|
|
|
|
|
email: '',
|
|
|
|
|
phone: '',
|
|
|
|
|
mooringNumber: null,
|
|
|
|
|
placeOfResidence: null,
|
|
|
|
|
comments: null,
|
|
|
|
|
interestType: null,
|
feat(intake): residence-type capture + CRM-owned inquiry emails for website cutover
Website register-interest form now offers the 3 residence types as a
multi-select; the choice + preferred-contact flow into the CRM inquiry
payload, the inbox detail, and the residential emails.
- inquiry inbox detail surfaces residence type(s), preferred contact,
type-of-interest, comments (full data capture)
- residential-inquiry emails: client confirmation names the chosen
villa(s); sales alert converted to the canonical detail-line format
(uniform with berth/contact) + residence type(s)/preferred contact +
plain-text part
- website-intake-fields parses residence_types[] + method_of_contact
- contact_form alerts split to their own recipient key
(contact_notification_recipients)
- Residential Interests section: new residence_type field (schema +
migration 0099, validators, inline select on the detail)
- contact-form-alert email refactor shipped (interest-alert style)
Tests: website-intake-fields, residential-inquiry templates,
contact-form-alert, residential-interest validators.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L2qc3xZTfif7N4Wq3QDa8X
2026-06-25 20:58:53 +02:00
|
|
|
residenceTypes: [],
|
|
|
|
|
preferredContact: null,
|
2026-06-02 17:22:08 +02:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|