Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions event-libs/v1/blocks/rsvp-form/config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { getMetadata } from '../../utils/utils.js';
import { parseRsvpFieldLimit } from '../../utils/sanitize-utils.js';

/** Valid `displayAs` flavors for the `text` substrate; each is also a valid
* native `<input type>` (or `text-area`, handled by its own dispatch entry). */
const TEXT_DISPLAY_AS = new Set(['text', 'email', 'phone', 'number', 'date', 'url', 'text-area']);

function lowercaseKeys(obj) {
return Object.keys(obj).reduce((acc, key) => {
acc[key.toLowerCase() === 'default' ? 'defval' : key.toLowerCase()] = obj[key];
Expand Down Expand Up @@ -34,12 +38,26 @@ export function resolveRsvpConfig() {
field.options = field.options.map((o) => (typeof o === 'object' ? o.value : o)).join(';');
}

// ESP's field type enum has no dedicated multi-select value — `select` is
// single-choice and `checkbox` is multi-choice. `displayas` (EMC's
// displayAs) carries the render-style hint; remap to the widget types
// this block implements.
if (field.type === 'select' && field.displayas === 'radio') field.type = 'radio-group';
if (field.type === 'checkbox' && field.displayas === 'dropdown') field.type = 'multi-select';
// ESP's field `type` names only the substrate (text/select/multi-select);
// `displayas` (EMC's displayAs) picks the concrete widget within it.
// Remap to the internal dispatch types this block implements. Any other
// `type` (heading/legal/divider/submit/clear, or a legacy direct value
// like `email`/`checkbox` from before this taxonomy) passes through
// untouched.
const da = field.displayas;
if (field.type === 'text') {
field.type = TEXT_DISPLAY_AS.has(da) ? da : 'text';
}
else if (field.type === 'select') {
field.type = da === 'radio' ? 'radio-group' : 'select';
}
else if (field.type === 'multi-select') {
field.type = da === 'combobox' ? 'multi-select' : 'checkbox-group';
}
else if (field.type === 'checkbox') {
// Legacy wire value from before the substrate/displayAs taxonomy.
field.type = da === 'dropdown' ? 'multi-select' : 'checkbox-group';
}
return field;
});

Expand Down
2 changes: 2 additions & 0 deletions event-libs/v1/blocks/rsvp-form/fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ function createTextField({
attrs.pattern = pattern || PHONE_PATTERN;
} else if (type === 'email') {
attrs.type = 'email';
} else if (type === 'url') {
attrs.type = 'url';
}
if (limit != null) attrs.maxlength = limit;
if (required === 'x') attrs.required = '';
Expand Down
85 changes: 85 additions & 0 deletions test/unit/blocks/rsvp-form/rsvp-form.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,72 @@ describe('rsvp-form', () => {
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'productsOfInterest').type).to.equal('multi-select');
});

// Substrate/displayAs taxonomy: Type names only the substrate (text/select/
// multi-select); displayAs picks the concrete widget within it.
[
[undefined, 'text'],
['text', 'text'],
['email', 'email'],
['phone', 'phone'],
['number', 'number'],
['date', 'date'],
['url', 'url'],
['text-area', 'text-area'],
['not-a-real-flavor', 'text'],
].forEach(([displayAs, expected]) => {
it(`remaps text substrate + displayAs "${displayAs}" to "${expected}"`, () => {
setRsvpConfigMeta({
rsvpFormFields: [{ Field: 'title', Type: 'text', displayAs }],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'title').type).to.equal(expected);
});
});

it('remaps select+picker (default) displayAs to select', () => {
setRsvpConfigMeta({
rsvpFormFields: [{ Field: 'industry', Type: 'select', displayAs: 'picker', Options: ['Tech'] }],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'industry').type).to.equal('select');
});

it('remaps multi-select+checkbox (default) displayAs to checkbox-group', () => {
setRsvpConfigMeta({
rsvpFormFields: [{ Field: 'interests', Type: 'multi-select', displayAs: 'checkbox', Options: ['A'] }],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'interests').type).to.equal('checkbox-group');
});

it('leaves multi-select with no displayAs as checkbox-group (default)', () => {
setRsvpConfigMeta({
rsvpFormFields: [{ Field: 'interests', Type: 'multi-select', Options: ['A'] }],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'interests').type).to.equal('checkbox-group');
});

it('remaps multi-select+combobox displayAs to the compact multi-select widget', () => {
setRsvpConfigMeta({
rsvpFormFields: [{ Field: 'interests', Type: 'multi-select', displayAs: 'combobox', Options: ['A'] }],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'interests').type).to.equal('multi-select');
});

it('leaves non-taxonomy types (e.g. heading, divider) untouched', () => {
setRsvpConfigMeta({
rsvpFormFields: [
{ Field: 'intro', Type: 'heading', Label: 'Welcome' },
{ Field: 'sep', Type: 'divider' },
],
});
const { fields } = resolveRsvpConfig();
expect(fields.find((f) => f.field === 'intro').type).to.equal('heading');
expect(fields.find((f) => f.field === 'sep').type).to.equal('divider');
});
});

describe('fields.js buildField', () => {
Expand Down Expand Up @@ -173,6 +239,25 @@ describe('rsvp-form', () => {
expect(wrapper.querySelectorAll('sp-checkbox')).to.have.lengthOf(2);
expect(wrapper.querySelector('.rsvp-form-checkbox-group').id).to.equal('contactMethods');
});

it('sets sp-textfield type to email/url for those substrate flavors', () => {
const email = buildField({ field: 'email', type: 'email', label: 'Email' });
expect(email.querySelector('sp-textfield').getAttribute('type')).to.equal('email');
const url = buildField({ field: 'website', type: 'url', label: 'Website' });
expect(url.querySelector('sp-textfield').getAttribute('type')).to.equal('url');
});

// Known pre-existing gap (not introduced by the substrate/displayAs taxonomy):
// FIELD_BUILDERS has no number/date entry, so these fall to createTextField,
// which has no number/date branch either — plain sp-textfield, no type attr.
it('falls back to a plain sp-textfield for number/date (no dedicated control yet)', () => {
const number = buildField({ field: 'years', type: 'number', label: 'Years' });
expect(number.dataset.type).to.equal('number');
expect(number.querySelector('sp-textfield').getAttribute('type')).to.equal(null);
const date = buildField({ field: 'eventDate', type: 'date', label: 'Date' });
expect(date.dataset.type).to.equal('date');
expect(date.querySelector('sp-textfield').getAttribute('type')).to.equal(null);
});
});

describe('payload.js constructPayload', () => {
Expand Down
Loading