diff --git a/custom-elements.json b/custom-elements.json index d3327b8aa..d760d23f4 100644 --- a/custom-elements.json +++ b/custom-elements.json @@ -24242,6 +24242,9 @@ } ], "events": [ + { + "name": "input" + }, { "name": "update", "description": "Instance of `NucleonElement.UpdateEvent`. Dispatched on an element whenever it changes its state." @@ -24607,6 +24610,10 @@ "path": "./src/elements/public/StoreForm/index.ts", "description": "Form element for store settings (`fx:store`).", "attributes": [ + { + "name": "reporting-store-domain-exists", + "description": "URL of the `fx:reporting_store_domain_exists` endpoint." + }, { "name": "customer-password-hash-types", "description": "URL of the `fx:customer_password_hash_types` property helper resource." @@ -24738,6 +24745,11 @@ } ], "properties": [ + { + "name": "reportingStoreDomainExists", + "attribute": "reporting-store-domain-exists", + "description": "URL of the `fx:reporting_store_domain_exists` endpoint." + }, { "name": "customerPasswordHashTypes", "attribute": "customer-password-hash-types", @@ -27275,15 +27287,30 @@ "attributes": [ { "name": "countries", - "description": "URI of the `fx:countries` hAPI resource.", - "type": "string", - "default": "\"\"" + "description": "URI of the `fx:countries` hAPI resource." }, { "name": "regions", - "description": "URI of the `fx:regions` hAPI resource.", + "description": "URI of the `fx:regions` hAPI resource." + }, + { + "name": "store", + "description": "URI of the `fx:store` hAPI resource related to this template config. If this property is `null`, a link relationship will be used when available." + }, + { + "name": "simplify-ns-loading", + "type": "boolean", + "default": "false" + }, + { + "name": "ns", "type": "string", - "default": "\"\"" + "default": "\"defaultNS\"" + }, + { + "name": "status", + "description": "Status message to render at the top of the form. If `null`, the message is hidden.", + "type": "object" }, { "name": "mode", @@ -27317,16 +27344,6 @@ "name": "hiddencontrols", "default": "\"False\"" }, - { - "name": "simplify-ns-loading", - "type": "boolean", - "default": "false" - }, - { - "name": "ns", - "type": "string", - "default": "\"defaultNS\"" - }, { "name": "lang", "description": "Optional ISO 639-1 code describing the language element content is written in.\nChanging the `lang` attribute will update the value of this property.", @@ -27370,16 +27387,71 @@ { "name": "countries", "attribute": "countries", - "description": "URI of the `fx:countries` hAPI resource.", - "type": "string", - "default": "\"\"" + "description": "URI of the `fx:countries` hAPI resource." }, { "name": "regions", "attribute": "regions", - "description": "URI of the `fx:regions` hAPI resource.", + "description": "URI of the `fx:regions` hAPI resource." + }, + { + "name": "store", + "attribute": "store", + "description": "URI of the `fx:store` hAPI resource related to this template config. If this property is `null`, a link relationship will be used when available." + }, + { + "name": "simplifyNsLoading", + "attribute": "simplify-ns-loading", + "type": "boolean", + "default": "false" + }, + { + "name": "ns", + "attribute": "ns", "type": "string", - "default": "\"\"" + "default": "\"defaultNS\"" + }, + { + "name": "t", + "type": "Translator", + "default": "\"(key, options) => {\\n const I18nElement = customElements.get('foxy-i18n') as typeof I18n | undefined;\\n\\n if (!I18nElement) return key;\\n\\n let keys: string[];\\n\\n if (this.simplifyNsLoading) {\\n const namespaces = this.ns.split(' ').filter(v => v.length > 0);\\n const path = [...namespaces.slice(1), key].join('.');\\n keys = namespaces[0] ? [`${namespaces[0]}:${path}`] : [path];\\n } else {\\n keys = this.ns\\n .split(' ')\\n .reverse()\\n .map(v => v.trim())\\n .filter(v => v.length > 0)\\n .reverse()\\n .map((v, i, a) => `${v}:${[...a.slice(i + 1), key].join('.')}`);\\n }\\n\\n keys.push(key);\\n\\n return I18nElement.i18next.t(keys, { lng: this.lang, ...options }).toString();\\n }\"" + }, + { + "name": "generalErrorPrefix", + "description": "Validation errors with this prefix will show up at the top of the form.", + "type": "string", + "default": "\"error:\"" + }, + { + "name": "status", + "attribute": "status", + "description": "Status message to render at the top of the form. If `null`, the message is hidden.", + "type": "object" + }, + { + "name": "headerTitleKey", + "description": "Getter that returns a i18n key for the optional form header title.", + "type": "string" + }, + { + "name": "headerTitleOptions", + "description": "I18next options to pass to the header title translation function.", + "type": "Record" + }, + { + "name": "headerSubtitleKey", + "description": "Getter that returns a i18n key for the optional form header subtitle. Note that subtitle is shown only when data is avaiable.", + "type": "string" + }, + { + "name": "headerSubtitleOptions", + "description": "I18next options to pass to the header subtitle translation function. Note that subtitle is shown only when data is avaiable.", + "type": "Record" + }, + { + "name": "headerCopyIdValue", + "description": "ID that will be written to clipboard when Copy ID button in header is clicked.", + "type": "string | number" }, { "name": "templates", @@ -27436,23 +27508,6 @@ "name": "hiddenSelector", "type": "BooleanSelector" }, - { - "name": "simplifyNsLoading", - "attribute": "simplify-ns-loading", - "type": "boolean", - "default": "false" - }, - { - "name": "ns", - "attribute": "ns", - "type": "string", - "default": "\"defaultNS\"" - }, - { - "name": "t", - "type": "Translator", - "default": "\"(key, options) => {\\n const I18nElement = customElements.get('foxy-i18n') as typeof I18n | undefined;\\n\\n if (!I18nElement) return key;\\n\\n let keys: string[];\\n\\n if (this.simplifyNsLoading) {\\n const namespaces = this.ns.split(' ').filter(v => v.length > 0);\\n const path = [...namespaces.slice(1), key].join('.');\\n keys = namespaces[0] ? [`${namespaces[0]}:${path}`] : [path];\\n } else {\\n keys = this.ns\\n .split(' ')\\n .reverse()\\n .map(v => v.trim())\\n .filter(v => v.length > 0)\\n .reverse()\\n .map((v, i, a) => `${v}:${[...a.slice(i + 1), key].join('.')}`);\\n }\\n\\n keys.push(key);\\n\\n return I18nElement.i18next.t(keys, { lng: this.lang, ...options }).toString();\\n }\"" - }, { "name": "UpdateEvent", "description": "Instances of this event are dispatched on an element whenever it changes its\nstate (e.g. when going from `busy` to `idle` or on `form` data change).\nThis event isn't cancelable, and it does not bubble.", @@ -31247,6 +31302,14 @@ } ], "properties": [ + { + "name": "getStatusPageHref", + "description": "If set, renders Statuses list items as tags." + }, + { + "name": "getLogPageHref", + "description": "If set, renders Logs list items as tags." + }, { "name": "resourceUri", "attribute": "resource-uri", @@ -31461,6 +31524,10 @@ "path": "./src/elements/public/WebhookLogCard/index.ts", "description": "Basic card displaying webhook log (`fx:webhook_log`) info.", "attributes": [ + { + "name": "layout", + "description": "When set to \"resource\", doesn't render resource type and ID." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -31511,6 +31578,11 @@ } ], "properties": [ + { + "name": "layout", + "attribute": "layout", + "description": "When set to \"resource\", doesn't render resource type and ID." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", @@ -31632,6 +31704,10 @@ "path": "./src/elements/public/WebhookStatusCard/index.ts", "description": "Basic card displaying webhook status (`fx:webhook_status`) info.", "attributes": [ + { + "name": "layout", + "description": "When set to \"resource\", doesn't render resource type and ID." + }, { "name": "simplify-ns-loading", "type": "boolean", @@ -31682,6 +31758,11 @@ } ], "properties": [ + { + "name": "layout", + "attribute": "layout", + "description": "When set to \"resource\", doesn't render resource type and ID." + }, { "name": "simplifyNsLoading", "attribute": "simplify-ns-loading", diff --git a/src/elements/internal/InternalEditableControl/InternalEditableControl.test.ts b/src/elements/internal/InternalEditableControl/InternalEditableControl.test.ts index 27d925051..f6e30cdbf 100644 --- a/src/elements/internal/InternalEditableControl/InternalEditableControl.test.ts +++ b/src/elements/internal/InternalEditableControl/InternalEditableControl.test.ts @@ -30,6 +30,13 @@ describe('InternalEditableControl', () => { }); }); + it('has a reactive property "jsonTemplate" (String)', () => { + expect(new InternalEditableControl()).to.have.property('jsonTemplate', null); + expect(InternalEditableControl).to.have.deep.nested.property('properties.jsonTemplate', { + attribute: 'json-template', + }); + }); + it('has a reactive property "placeholder" (String)', () => { expect(InternalEditableControl).to.have.nested.property('properties.placeholder.type', String); }); @@ -42,6 +49,13 @@ describe('InternalEditableControl', () => { expect(InternalEditableControl).to.have.nested.property('properties.v8nPrefix.type', String); }); + it('has a reactive property "jsonPath" (String)', () => { + expect(new InternalEditableControl()).to.have.property('jsonPath', null); + expect(InternalEditableControl).to.have.deep.nested.property('properties.jsonPath', { + attribute: 'json-path', + }); + }); + it('has a reactive property "property" (String)', () => { expect(InternalEditableControl).to.have.nested.property('properties.property.type', String); }); @@ -164,6 +178,36 @@ describe('InternalEditableControl', () => { expect(wrapper.firstElementChild).to.have.property('_value', testData.address_name); }); + it('supports retrieving values from serialized json', async () => { + const testData = await getTestData('./hapi/customer_addresses/0'); + testData.address_name = JSON.stringify({ foo: { bar: 'baz' } }); + + const wrapper = await fixture(html` + + + + + `); + + expect(wrapper.firstElementChild).to.have.property('_value', 'baz'); + }); + + it('supports default values for serialized json', async () => { + const testData = await getTestData('./hapi/customer_addresses/0'); + const wrapper = await fixture(html` + + + + + `); + + expect(wrapper.firstElementChild).to.have.property('_value', 'baz'); + }); + it('sends updates to the parent NucleonElement on value change', async () => { const testData = await getTestData('./hapi/customer_addresses/0'); const wrapper = await fixture(html` @@ -199,6 +243,44 @@ describe('InternalEditableControl', () => { expect(wrapper).to.have.nested.property('form.address_name', testData.address_name); }); + it('supports setting values in serialized json', async () => { + const testData = await getTestData('./hapi/customer_addresses/0'); + testData.address_name = JSON.stringify({ foo: { bar: 'baz' } }); + + const wrapper = await fixture(html` + + + + + `); + + (wrapper.firstElementChild as InternalEditableControl)!.setValue('qux'); + expect(wrapper).to.have.nested.property( + 'form.address_name', + JSON.stringify({ foo: { bar: 'qux' } }) + ); + }); + + it('supports setting values in serialized json with default values', async () => { + const testData = await getTestData('./hapi/customer_addresses/0'); + const wrapper = await fixture(html` + + + + + `); + + (wrapper.firstElementChild as InternalEditableControl)!.setValue('qux'); + expect(wrapper).to.have.nested.property( + 'form.some_unknown_property', + JSON.stringify({ foo: { bar: 'qux' } }) + ); + }); + it("has a protected shortcut for the first v8n error in a a nucleon form it's associated with", async () => { const wrapper = await fixture(html` diff --git a/src/elements/internal/InternalEditableControl/InternalEditableControl.ts b/src/elements/internal/InternalEditableControl/InternalEditableControl.ts index 25de07150..9c570dba8 100644 --- a/src/elements/internal/InternalEditableControl/InternalEditableControl.ts +++ b/src/elements/internal/InternalEditableControl/InternalEditableControl.ts @@ -3,6 +3,7 @@ import type { PropertyDeclarations } from 'lit-element'; import { InternalControl } from '../InternalControl/InternalControl'; import debounce from 'lodash-es/debounce'; +import { get, set } from 'lodash-es'; /** * An internal base class for controls that have editing functionality, e.g. a text field. @@ -18,9 +19,11 @@ export class InternalEditableControl extends InternalControl { return { ...super.properties, checkValidityAsync: { attribute: false }, + jsonTemplate: { attribute: 'json-template' }, placeholder: { type: String, noAccessor: true }, helperText: { type: String, attribute: 'helper-text', noAccessor: true }, v8nPrefix: { type: String, attribute: 'v8n-prefix', noAccessor: true }, + jsonPath: { attribute: 'json-path' }, getValue: { attribute: false }, setValue: { attribute: false }, property: { type: String, noAccessor: true }, @@ -31,9 +34,25 @@ export class InternalEditableControl extends InternalControl { checkValidityAsync: ((value: unknown) => Promise) | null = null; - getValue = (): unknown => this.nucleon?.form[this.property]; + jsonTemplate: string | null = null; - setValue = (newValue: unknown): void => this.nucleon?.edit({ [this.property]: newValue }); + jsonPath: string | null = null; + + getValue = (): unknown => { + const value = get(this.nucleon?.form, this.property); + if (this.jsonPath) return get(JSON.parse(value ?? this.jsonTemplate), this.jsonPath); + return value; + }; + + setValue = (newValue: unknown): void => { + if (this.jsonPath) { + const json = JSON.parse(this.nucleon?.form[this.property] ?? this.jsonTemplate); + set(json, this.jsonPath, newValue); + this.nucleon?.edit({ [this.property]: JSON.stringify(json) }); + } else { + this.nucleon?.edit({ [this.property]: newValue }); + } + }; private __debouncedCheckValidityAsync = debounce(async (newValue: unknown) => { const validOrError = await this.checkValidityAsync?.(newValue); diff --git a/src/elements/internal/InternalEditableListControl/InternalEditableListControl.test.ts b/src/elements/internal/InternalEditableListControl/InternalEditableListControl.test.ts index f7434f32b..5d92645a3 100644 --- a/src/elements/internal/InternalEditableListControl/InternalEditableListControl.test.ts +++ b/src/elements/internal/InternalEditableListControl/InternalEditableListControl.test.ts @@ -24,6 +24,14 @@ describe('InternalEditableListControl', () => { expect(new Control()).to.have.deep.property('inputParams', {}); }); + it('has a reactive property "simpleValue"', () => { + expect(new Control()).to.have.property('simpleValue', false); + expect(Control).to.have.deep.nested.property('properties.simpleValue', { + attribute: 'simple-value', + type: Boolean, + }); + }); + it('has a reactive property "layout"', () => { expect(Control).to.have.deep.nested.property('properties.layout', {}); expect(new Control()).to.have.property('layout', null); @@ -323,4 +331,23 @@ describe('InternalEditableListControl', () => { control = await fixture(html``); expect(control.renderRoot).to.include.text('Test error message'); }); + + it('when simpleValue is true, supports using array of strings as model', async () => { + let value: string[] = ['foo', 'bar']; + + const control = await fixture(html` + value} + .setValue=${(newValue: string[]) => (value = newValue)} + simple-value + > + + `); + + expect(control).to.have.deep.property('_value', [{ value: 'foo' }, { value: 'bar' }]); + + // @ts-expect-error using protected property for testing purposes + control._value = [{ value: 'baz' }]; + expect(value).to.deep.equal(['baz']); + }); }); diff --git a/src/elements/internal/InternalEditableListControl/InternalEditableListControl.ts b/src/elements/internal/InternalEditableListControl/InternalEditableListControl.ts index cb39f31a8..bd864e875 100644 --- a/src/elements/internal/InternalEditableListControl/InternalEditableListControl.ts +++ b/src/elements/internal/InternalEditableListControl/InternalEditableListControl.ts @@ -13,6 +13,7 @@ export class InternalEditableListControl extends InternalEditableControl { return { ...super.properties, inputParams: { attribute: false }, + simpleValue: { type: Boolean, attribute: 'simple-value' }, options: { type: Array }, layout: {}, units: { type: Array }, @@ -48,6 +49,8 @@ export class InternalEditableListControl extends InternalEditableControl { inputParams: Record = {}; + simpleValue = false; + options: Option[] = []; layout: 'standalone' | 'summary-item' | null = null; @@ -335,10 +338,16 @@ export class InternalEditableListControl extends InternalEditableControl { } protected get _value(): Item[] { - return (super._value ?? []) as Item[]; + const value = super._value; + if (this.simpleValue) return ((value as string[]) ?? []).map(value => ({ value })); + return (value ?? []) as Item[]; } protected set _value(newValue: Item[]) { - super._value = newValue; + if (this.simpleValue) { + super._value = newValue.map(({ value }) => value); + } else { + super._value = newValue; + } } } diff --git a/src/elements/internal/InternalSwitchControl/InternalSwitchControl.test.ts b/src/elements/internal/InternalSwitchControl/InternalSwitchControl.test.ts index c226819d8..19635876b 100644 --- a/src/elements/internal/InternalSwitchControl/InternalSwitchControl.test.ts +++ b/src/elements/internal/InternalSwitchControl/InternalSwitchControl.test.ts @@ -33,6 +33,20 @@ describe('InternalSwitchControl', () => { }); }); + it('has a reactive property "falseAlias"', () => { + expect(Control).to.have.deep.nested.property('properties.falseAlias', { + attribute: 'false-alias', + }); + expect(new Control()).to.have.property('falseAlias', null); + }); + + it('has a reactive property "trueAlias"', () => { + expect(Control).to.have.deep.nested.property('properties.trueAlias', { + attribute: 'true-alias', + }); + expect(new Control()).to.have.property('trueAlias', null); + }); + it('has a reactive property "invert"', () => { expect(Control).to.have.deep.nested.property('properties.invert', { type: Boolean }); expect(new Control()).to.have.property('invert', false); @@ -171,4 +185,48 @@ describe('InternalSwitchControl', () => { await control.requestUpdate(); expect(control.renderRoot.querySelector('vcf-tooltip')).to.not.exist; }); + + it('when falseAlias is set, uses the alias instead of false', async () => { + const control = await fixture(html` + + `); + + control.getValue = () => 'no'; + expect(control).to.have.property('_value', true); + + control.falseAlias = 'no'; + expect(control).to.have.property('_value', false); + + control.setValue = stub(); + // @ts-expect-error using protected method for testing purposes + control._value = false; + expect(control.setValue).to.have.been.calledOnceWith('no'); + + control.setValue = stub(); + // @ts-expect-error using protected method for testing purposes + control._value = true; + expect(control.setValue).to.have.been.calledOnceWith(true); + }); + + it('when trueAlias is set, uses the alias instead of true', async () => { + const control = await fixture(html` + + `); + + control.getValue = () => 'yes'; + expect(control).to.have.property('_value', true); + + control.trueAlias = 'yes'; + expect(control).to.have.property('_value', true); + + control.setValue = stub(); + // @ts-expect-error using protected method for testing purposes + control._value = true; + expect(control.setValue).to.have.been.calledOnceWith('yes'); + + control.setValue = stub(); + // @ts-expect-error using protected method for testing purposes + control._value = false; + expect(control.setValue).to.have.been.calledOnceWith(false); + }); }); diff --git a/src/elements/internal/InternalSwitchControl/InternalSwitchControl.ts b/src/elements/internal/InternalSwitchControl/InternalSwitchControl.ts index d69242d99..595bef54b 100644 --- a/src/elements/internal/InternalSwitchControl/InternalSwitchControl.ts +++ b/src/elements/internal/InternalSwitchControl/InternalSwitchControl.ts @@ -7,12 +7,18 @@ export class InternalSwitchControl extends InternalEditableControl { return { ...super.properties, helperTextAsToolip: { type: Boolean, attribute: 'helper-text-as-tooltip' }, + falseAlias: { attribute: 'false-alias' }, + trueAlias: { attribute: 'true-alias' }, invert: { type: Boolean }, }; } helperTextAsToolip = false; + falseAlias: string | null = null; + + trueAlias: string | null = null; + invert = false; renderControl(): TemplateResult { @@ -94,4 +100,17 @@ export class InternalSwitchControl extends InternalEditableControl { `; } + + protected get _value(): boolean { + const originalValue = super._value; + if (this.trueAlias && originalValue === this.trueAlias) return true; + if (this.falseAlias && originalValue === this.falseAlias) return false; + return !!originalValue; + } + + protected set _value(value: boolean) { + if (this.trueAlias && value === true) super._value = this.trueAlias; + else if (this.falseAlias && value === false) super._value = this.falseAlias; + else super._value = value; + } } diff --git a/src/elements/public/SignInForm/SignInForm.ts b/src/elements/public/SignInForm/SignInForm.ts index b1c6a13bc..517df52ef 100644 --- a/src/elements/public/SignInForm/SignInForm.ts +++ b/src/elements/public/SignInForm/SignInForm.ts @@ -4,6 +4,7 @@ import { PropertyDeclarations, TemplateResult, html } from 'lit-element'; import { CheckboxElement } from '@vaadin/vaadin-checkbox'; import { ConfigurableMixin } from '../../../mixins/configurable'; import { EmailFieldElement } from '@vaadin/vaadin-text-field/vaadin-email-field'; +import { TextFieldElement } from '@vaadin/vaadin-text-field/vaadin-text-field'; import { NucleonElement } from '../NucleonElement/NucleonElement'; import { NucleonV8N } from '../NucleonElement/types'; import { PasswordFieldElement } from '@vaadin/vaadin-text-field/vaadin-password-field'; @@ -42,6 +43,8 @@ export class SignInForm extends Base { issuer = 'Unknown'; + private __autofillPoller: number | null = null; + private readonly __emailValidator = () => !this.errors.some(err => err.startsWith('email')); private readonly __passwordValidator = () => !this.errors.some(err => err.startsWith('password')); @@ -388,6 +391,29 @@ export class SignInForm extends Base { `; } + connectedCallback(): void { + super.connectedCallback(); + + if (this.__autofillPoller !== null) window.clearInterval(this.__autofillPoller); + + this.__autofillPoller = window.setInterval(() => { + type Field = EmailFieldElement | PasswordFieldElement | TextFieldElement; + const selector = 'vaadin-text-field, vaadin-email-field, vaadin-password-field'; + const fields = this.renderRoot.querySelectorAll(selector); + + fields.forEach(field => { + const attrValue = field.getAttribute('value') ?? ''; + const propValue = field.value; + if (propValue !== attrValue) field.dispatchEvent(new InputEvent('input')); + }); + }, 250); + } + + disconnectedCallback(): void { + super.disconnectedCallback(); + if (this.__autofillPoller !== null) window.clearInterval(this.__autofillPoller); + } + protected async _sendPost(edits: Partial): Promise { const body = JSON.stringify(edits); const data = await this._fetch(this.parent, { body, method: 'POST' }); diff --git a/src/elements/public/TemplateConfigForm/CountriesList.test.ts b/src/elements/public/TemplateConfigForm/CountriesList.test.ts deleted file mode 100644 index e8583ff9e..000000000 --- a/src/elements/public/TemplateConfigForm/CountriesList.test.ts +++ /dev/null @@ -1,217 +0,0 @@ -import './index'; - -import { expect, fixture } from '@open-wc/testing'; - -import { CountriesList } from './CountriesList'; -import { CountryCard } from './CountryCard'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; -import { getByTestId } from '../../../testgen/getByTestId'; -import { html } from 'lit-html'; - -customElements.define('test-countries-list', CountriesList); - -describe('TemplateConfigForm', () => { - describe('CountriesList', () => { - const sampleData = { - _links: { self: { href: 'test://countries' } }, - message: 'Sample countries', - values: { - US: { default: 'United States' }, - CA: { default: 'Canada' }, - AU: { default: 'Australia' }, - }, - }; - - it('extends NucleonElement', () => { - expect(new CountriesList()).to.be.instanceOf(NucleonElement); - }); - - it('has an empty default i18next namespace', () => { - expect(new CountriesList()).to.have.empty.property('ns'); - }); - - it('has an empty countries map by default', () => { - expect(new CountriesList()).to.have.empty.property('countries'); - }); - - it('has an empty fx:regions URL by default', () => { - expect(new CountriesList()).to.have.empty.property('countries'); - }); - - it('renders countries as CountryCard elements', async () => { - const element = await fixture(html` - - - `); - - const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; - - expect(wrapper.children[0]).to.have.attribute('regions', JSON.stringify(['WA', 'TX'])); - expect(wrapper.children[0]).to.have.attribute('code', 'US'); - expect(wrapper.children[0]).to.have.attribute('href', 'test://regions?country_code=US'); - expect(wrapper.children[0]).to.have.attribute('lang', 'es'); - expect(wrapper.children[0]).to.have.attribute('ns', 'foo'); - - expect(wrapper.children[1]).to.have.attribute('regions', JSON.stringify([])); - expect(wrapper.children[1]).to.have.attribute('code', 'CA'); - expect(wrapper.children[1]).to.have.attribute('href', 'test://regions?country_code=CA'); - expect(wrapper.children[1]).to.have.attribute('lang', 'es'); - expect(wrapper.children[1]).to.have.attribute('ns', 'foo'); - }); - - it('can delete a country', async () => { - const element = await fixture(html` - - `); - - const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; - wrapper.children[1].dispatchEvent(new CustomEvent('delete')); - - expect(element).to.have.deep.property('countries', { US: ['WA', 'TX'] }); - }); - - it('can update country regions', async () => { - const element = await fixture(html` - - `); - - const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; - const card = wrapper.children[1] as CountryCard; - - card.regions = ['QC']; - card.dispatchEvent(new CustomEvent('update:regions')); - - expect(element).to.have.deep.property('countries', { US: ['WA', 'TX'], CA: ['QC'] }); - }); - - it('can add a country with enter-to-submit', async () => { - const element = await fixture(html` - - `); - - const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; - const input = wrapper.querySelector('input') as HTMLInputElement; - - input.value = 'CA'; - input.dispatchEvent(new InputEvent('input')); - input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); - - expect(element).to.have.deep.property('countries', { US: ['WA', 'TX'], CA: '*' }); - }); - - it('can add a country with click-to-submit', async () => { - const element = await fixture(html` - - `); - - const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; - const input = wrapper.querySelector('input') as HTMLInputElement; - const button = wrapper.querySelector('button') as HTMLButtonElement; - - input.value = 'CA'; - input.dispatchEvent(new InputEvent('input')); - button.dispatchEvent(new Event('click')); - - expect(element).to.have.deep.property('countries', { US: ['WA', 'TX'], CA: '*' }); - }); - - it('renders a datalist with suggestions', async () => { - const element = await fixture( - html`` - ); - - const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; - const input = wrapper.querySelector('input') as HTMLInputElement; - const datalist = element.renderRoot.querySelector('datalist') as HTMLDataListElement; - - expect(input.list).to.equal(datalist); - - expect(datalist.options[0]).to.have.property('value', 'US'); - expect(datalist.options[0]).to.include.text('United States'); - - expect(datalist.options[1]).to.have.property('value', 'CA'); - expect(datalist.options[1]).to.include.text('Canada'); - - expect(datalist.options[2]).to.have.property('value', 'AU'); - expect(datalist.options[2]).to.include.text('Australia'); - }); - - it('is enabled by default', async () => { - const element = await fixture( - html`` - ); - - element.querySelectorAll('input, [data-testid="countries"] > *').forEach(control => { - expect(control).to.not.have.attribute('disabled'); - }); - }); - - it('is disabled when element is disabled', async () => { - const element = await fixture( - html`` - ); - - element.querySelectorAll('input, [data-testid="countries"] > *').forEach(control => { - expect(control).to.have.attribute('disabled'); - }); - }); - - it('disables Add Country button when New Country input is empty', async () => { - const element = await fixture( - html`` - ); - - const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; - const input = wrapper.querySelector('input') as HTMLInputElement; - const button = wrapper.querySelector('button') as HTMLButtonElement; - - input.value = ''; - input.dispatchEvent(new InputEvent('input')); - - await element.requestUpdate(); - expect(button).to.have.attribute('disabled'); - }); - - it('enables Add Country button when New Country input has value', async () => { - const element = await fixture( - html`` - ); - - const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; - const input = wrapper.querySelector('input') as HTMLInputElement; - const button = wrapper.querySelector('button') as HTMLButtonElement; - - input.value = 'US'; - input.dispatchEvent(new InputEvent('input')); - - await element.requestUpdate(); - expect(button).to.not.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const element = await fixture( - html`` - ); - - element.querySelectorAll('input, [data-testid="countries"] > *').forEach(input => { - expect(input).to.not.have.attribute('readonly'); - }); - }); - - it('is readonly when element is readonly', async () => { - const element = await fixture( - html`` - ); - - element.querySelectorAll('input, [data-testid="countries"] > *').forEach(input => { - expect(input).to.have.attribute('readonly'); - }); - }); - }); -}); diff --git a/src/elements/public/TemplateConfigForm/CountriesList.ts b/src/elements/public/TemplateConfigForm/CountriesList.ts deleted file mode 100644 index fff018e5a..000000000 --- a/src/elements/public/TemplateConfigForm/CountriesList.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { ScopedElementsMap, ScopedElementsMixin } from '@open-wc/scoped-elements'; -import { TemplateResult, html } from 'lit-html'; - -import { ConfigurableMixin } from '../../../mixins/configurable'; -import { CountryCard } from './CountryCard'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; -import { PropertyDeclarations } from 'lit-element'; -import { Rels } from '@foxy.io/sdk/backend'; -import { Resource } from '@foxy.io/sdk/core'; -import { ThemeableMixin } from '../../../mixins/themeable'; -import { TranslatableMixin } from '../../../mixins/translatable'; -import { classMap } from '../../../utils/class-map'; -import { ifDefined } from 'lit-html/directives/if-defined'; - -const Base = ScopedElementsMixin( - ConfigurableMixin(ThemeableMixin(TranslatableMixin(NucleonElement))) -); - -export class CountriesList extends Base> { - static get scopedElements(): ScopedElementsMap { - return { - 'x-country-card': CountryCard, - 'iron-icon': customElements.get('iron-icon'), - }; - } - - static get properties(): PropertyDeclarations { - return { - ...super.properties, - __newCountry: { attribute: false }, - countries: { type: Object }, - regions: { type: String }, - }; - } - - countries: Record = {}; - - regions = ''; - - private __newCountry = ''; - - render(): TemplateResult { - return html` -
-
- ${Object.entries(this.countries).map(([country, regions]) => { - let regionsLink: string; - - try { - const url = new URL(this.regions); - url.searchParams.set('country_code', country); - regionsLink = url.toString(); - } catch { - regionsLink = ''; - } - - return html` - { - const newCountries = { ...this.countries }; - const newRegions = (evt.currentTarget as CountryCard).regions; - - newCountries[country] = newRegions.length ? newRegions : '*'; - this.countries = newCountries; - - this.dispatchEvent(new CustomEvent('update:countries')); - }} - @delete=${() => { - const newCountries = { ...this.countries }; - delete newCountries[country]; - this.countries = newCountries; - - this.dispatchEvent(new CustomEvent('update:countries')); - }} - > - - `; - })} - -
- { - if (evt.key === 'Enter' && this.__newCountry) this.__addCountry(); - }} - @input=${(evt: InputEvent) => { - const target = evt.currentTarget as HTMLInputElement; - this.__newCountry = target.value; - }} - /> - - -
-
- - - ${Object.entries(this.data?.values ?? {}).map(([code, country]) => { - return html``; - })} - -
- `; - } - - private __addCountry() { - this.countries = { ...this.countries, [this.__newCountry]: '*' }; - this.__newCountry = ''; - this.dispatchEvent(new CustomEvent('update:countries')); - } -} diff --git a/src/elements/public/TemplateConfigForm/TemplateConfigForm.stories.ts b/src/elements/public/TemplateConfigForm/TemplateConfigForm.stories.ts index 6836bcd0f..f29ae8712 100644 --- a/src/elements/public/TemplateConfigForm/TemplateConfigForm.stories.ts +++ b/src/elements/public/TemplateConfigForm/TemplateConfigForm.stories.ts @@ -11,32 +11,126 @@ const summary: Summary = { localName: 'foxy-template-config-form', translatable: true, configurable: { + sections: ['timestamps', 'header'], + buttons: ['create', 'delete', 'undo', 'submit', 'header:copy-id', 'header:copy-json'], inputs: [ - 'cart-type', - 'foxycomplete', - 'locations', - 'hidden-fields', - 'cards', - 'checkout-type', - 'consent', - 'fields', - 'google-analytics', - 'google-tag', - 'troubleshooting', + 'cart-group-one', + 'cart-group-one:cart-type', + 'cart-group-one:cart-display-config-usage', + + 'cart-group-two', + 'cart-group-two:cart-display-config-show-product-weight', + 'cart-group-two:cart-display-config-show-product-category', + 'cart-group-two:cart-display-config-show-product-code', + 'cart-group-two:cart-display-config-show-product-options', + + 'cart-group-three', + 'cart-group-three:cart-display-config-show-sub-frequency', + 'cart-group-three:cart-display-config-show-sub-startdate', + 'cart-group-three:cart-display-config-show-sub-nextdate', + 'cart-group-three:cart-display-config-show-sub-enddate', + + 'cart-group-four', + 'cart-group-four:cart-display-config-hidden-product-options', + + 'country-and-region-group-one', + 'country-and-region-group-one:foxycomplete-usage', + 'country-and-region-group-one:foxycomplete-show-combobox', + 'country-and-region-group-one:foxycomplete-combobox-open', + 'country-and-region-group-one:foxycomplete-combobox-close', + 'country-and-region-group-one:foxycomplete-show-flags', + 'country-and-region-group-one:postal-code-lookup', + 'country-and-region-group-one:location-filtering-usage', + + 'country-and-region-group-two', + 'country-and-region-group-two:location-filtering-filter-type', + 'country-and-region-group-two:location-filtering-filter-values', + + 'country-and-region-group-three', + 'country-and-region-group-three:location-filtering-shipping-filter-type', + 'country-and-region-group-three:location-filtering-shipping-filter-values', + + 'country-and-region-group-four', + 'country-and-region-group-four:location-filtering-billing-filter-type', + 'country-and-region-group-four:location-filtering-billing-filter-values', + + 'customer-accounts', + 'customer-accounts:checkout-type-account', + 'customer-accounts:checkout-type-default', + + 'consent-group-one', + 'consent-group-one:tos-checkbox-settings-usage', + 'consent-group-one:tos-checkbox-settings-url', + 'consent-group-one:tos-checkbox-settings-initial-state', + 'consent-group-one:tos-checkbox-settings-is-hidden', + + 'consent-group-two', + 'consent-group-two:eu-secure-data-transfer-consent-usage', + + 'consent-group-three', + 'consent-group-three:newsletter-subscribe-usage', + + 'supported-cards-group', + 'supported-cards-group:supported-payment-cards', + + 'csc-requirements-group', + 'csc-requirements-group:csc-requirements', + + 'checkout-fields-group-one', + 'checkout-fields-group-one:custom-checkout-field-requirements-cart-controls', + + 'checkout-fields-group-two', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-address-one', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-address-two', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-city', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-region', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-postal-code', + 'checkout-fields-group-two:custom-checkout-field-requirements-billing-country', + + 'checkout-fields-group-three', + 'checkout-fields-group-three:custom-checkout-field-requirements-billing-first-name', + 'checkout-fields-group-three:custom-checkout-field-requirements-billing-last-name', + 'checkout-fields-group-three:custom-checkout-field-requirements-billing-company', + 'checkout-fields-group-three:custom-checkout-field-requirements-billing-tax-id', + 'checkout-fields-group-three:custom-checkout-field-requirements-billing-phone', + 'checkout-fields-group-three:custom-checkout-field-requirements-coupon-entry', + + 'analytics-config', + 'analytics-config:analytics-config-usage', + + 'analytics-config-google-analytics', + 'analytics-config-google-analytics:analytics-config-google-analytics-usage', + 'analytics-config-google-analytics:analytics-config-google-analytics-account-id', + 'analytics-config-google-analytics:analytics-config-google-analytics-include-on-site', + + 'analytics-config-google-tag', + 'analytics-config-google-tag:analytics-config-google-tag-usage', + 'analytics-config-google-tag:analytics-config-google-tag-account-id', + 'analytics-config-google-tag:analytics-config-google-tag-send-to', + + 'debug', + 'debug:debug-usage', + 'custom-config', - 'header', - 'custom-fields', - 'footer', + 'custom-script-values-header', + 'custom-script-values-checkout-fields', + 'custom-script-values-multiship-checkout-fields', + 'custom-script-values-footer', ], }, }; export default getMeta(summary); -export const Playground = getStory({ ...summary, code: true }); -export const Empty = getStory(summary); -export const Error = getStory(summary); -export const Busy = getStory(summary); +const ext = ` + countries="https://demo.api/hapi/property_helpers/3" + regions="https://demo.api/hapi/property_helpers/4" +`; + +export const Playground = getStory({ ...summary, ext, code: true }); +export const Empty = getStory({ ...summary, ext }); +export const Error = getStory({ ...summary, ext }); +export const Busy = getStory({ ...summary, ext }); Empty.args.href = ''; Error.args.href = 'https://demo.api/virtual/empty?status=404'; diff --git a/src/elements/public/TemplateConfigForm/TemplateConfigForm.test.ts b/src/elements/public/TemplateConfigForm/TemplateConfigForm.test.ts index b704c1e87..211980111 100644 --- a/src/elements/public/TemplateConfigForm/TemplateConfigForm.test.ts +++ b/src/elements/public/TemplateConfigForm/TemplateConfigForm.test.ts @@ -1,4093 +1,1304 @@ +import type { InternalTemplateConfigFormFilterValuesControl } from './internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl'; +import type { InternalSourceControl } from '../../internal/InternalSourceControl/InternalSourceControl'; +import type { InternalSelectControl } from '../../internal/InternalSelectControl/InternalSelectControl'; +import type { NucleonElement } from '../NucleonElement/NucleonElement'; +import type { FetchEvent } from '../NucleonElement/FetchEvent'; +import type { Data } from './types'; + import './index'; -import { Checkbox, Choice } from '../../private'; -import { CheckboxChangeEvent, ChoiceChangeEvent } from '../../private/events'; -import { Data, TemplateConfigJSON } from './types'; -import { expect, fixture, html } from '@open-wc/testing'; - -import { CountriesList } from './CountriesList'; -import { I18n } from '../I18n'; -import { InternalSandbox } from '../../internal/InternalSandbox/InternalSandbox'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; -import { TemplateConfigForm } from './TemplateConfigForm'; -import { InternalSourceControl } from '../../internal/InternalSourceControl/InternalSourceControl'; -import { TextFieldElement } from '@vaadin/vaadin-text-field/vaadin-text-field'; -import { getByKey } from '../../../testgen/getByKey'; -import { getByName } from '../../../testgen/getByName'; -import { getByTestId } from '../../../testgen/getByTestId'; +import { expect, fixture, html, waitUntil } from '@open-wc/testing'; +import { TemplateConfigForm as Form } from './TemplateConfigForm'; +import { getDefaultJSON } from './defaults'; +import { createRouter } from '../../../server'; import { getTestData } from '../../../testgen/getTestData'; import { stub } from 'sinon'; -import { unsafeHTML } from 'lit-html/directives/unsafe-html'; describe('TemplateConfigForm', () => { - const OriginalResizeObserver = window.ResizeObserver; - - // @ts-expect-error disabling ResizeObserver because it errors in test env - before(() => (window.ResizeObserver = undefined)); - after(() => (window.ResizeObserver = OriginalResizeObserver)); - - it('extends NucleonElement', () => { - expect(new TemplateConfigForm()).to.be.instanceOf(NucleonElement); + it('imports and defines foxy-internal-editable-list-control element', () => { + expect(customElements.get('foxy-internal-editable-list-control')).to.exist; }); - it('registers as foxy-template-config-form', () => { - expect(customElements.get('foxy-template-config-form')).to.equal(TemplateConfigForm); + it('imports and defines foxy-internal-summary-control element', () => { + expect(customElements.get('foxy-internal-summary-control')).to.exist; }); - it('has a default i18next namespace of "template-config-form"', () => { - expect(new TemplateConfigForm()).to.have.property('ns', 'template-config-form'); + it('imports and defines foxy-internal-switch-control element', () => { + expect(customElements.get('foxy-internal-switch-control')).to.exist; }); - it('has an empty fx:countries URL by default', () => { - expect(new TemplateConfigForm()).to.have.property('countries', ''); + it('imports and defines foxy-internal-select-control element', () => { + expect(customElements.get('foxy-internal-select-control')).to.exist; }); - it('has an empty fx:regions URL by default', () => { - expect(new TemplateConfigForm()).to.have.property('regions', ''); + it('imports and defines foxy-internal-source-control element', () => { + expect(customElements.get('foxy-internal-source-control')).to.exist; }); - describe('cart-type', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'cart-type')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'cart-type')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes cart-type', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'cart-type')).to.not.exist; - }); - - it('renders "cart-type:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'cart-type:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "cart-type:before" slot with template "cart-type:before" if available', async () => { - const type = 'cart-type:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "cart-type:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'cart-type:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "cart-type:after" slot with template "cart-type:after" if available', async () => { - const type = 'cart-type:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a group label with i18n key cart_type', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const label = await getByKey(control, 'cart_type'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('renders a choice of cart types', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.exist; - expect(choice).to.have.deep.property('items', ['default', 'fullpage', 'custom']); - }); - - it('reflects the value of cart_type from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.cart_type = 'fullpage'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.have.property('value', 'fullpage'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes cart-type', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes cart-type', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - expect(choice).to.have.attribute('readonly'); - }); - - it('writes to cart_type property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - - choice.value = 'fullpage'; - choice.dispatchEvent(new ChoiceChangeEvent('fullpage')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.property('cart_type', 'fullpage'); - }); - - ['default', 'fullpage', 'custom'].forEach(type => { - it(`renders i18n label and explainer for ${type} cart type`, async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'cart-type')) as HTMLElement; - const choice = (await getByTestId(control, 'cart-type-choice')) as Choice; - const wrapper = choice.querySelector(`[slot="${type}-label"]`) as HTMLElement; - const label = await getByKey(wrapper, `cart_type_${type}`); - const explainer = await getByKey(wrapper, `cart_type_${type}_explainer`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - }); + it('imports and defines foxy-internal-text-control element', () => { + expect(customElements.get('foxy-internal-text-control')).to.exist; }); - describe('foxycomplete', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'foxycomplete')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'foxycomplete')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes foxycomplete', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'foxycomplete')).to.not.exist; - }); + it('imports and defines foxy-internal-form element', () => { + expect(customElements.get('foxy-internal-form')).to.exist; + }); - it('renders "foxycomplete:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'foxycomplete:before'); + it('imports and defines foxy-nucleon element', () => { + expect(customElements.get('foxy-nucleon')).to.exist; + }); - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); + it('imports and defines foxy-i18n element', () => { + expect(customElements.get('foxy-i18n')).to.exist; + }); - it('replaces "foxycomplete:before" slot with template "foxycomplete:before" if available', async () => { - const type = 'foxycomplete:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); + it('imports and defines foxy-internal-template-config-form-supported-cards-control element', () => { + expect(customElements.get('foxy-internal-template-config-form-supported-cards-control')).to + .exist; + }); - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; + it('imports and defines foxy-internal-template-config-form-filter-values-control element', () => { + expect(customElements.get('foxy-internal-template-config-form-filter-values-control')).to.exist; + }); - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); + it('defines itself as foxy-template-config-form', () => { + expect(customElements.get('foxy-template-config-form')).to.equal(Form); + }); - it('renders "foxycomplete:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'foxycomplete:after'); + it('has a default i18next namespace of "template-config-form"', () => { + expect(Form.defaultNS).to.equal('template-config-form'); + expect(new Form().ns).to.equal('template-config-form'); + }); - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); + it('has a reactive property "countries"', () => { + expect(Form.properties).to.have.deep.property('countries', {}); + expect(new Form().countries).to.be.null; + }); - it('replaces "foxycomplete:after" slot with template "foxycomplete:after" if available', async () => { - const type = 'foxycomplete:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); + it('has a reacitve property "regions"', () => { + expect(Form.properties).to.have.deep.property('regions', {}); + expect(new Form().regions).to.be.null; + }); - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; + it('has a reactive property "store"', () => { + expect(Form.properties).to.have.deep.property('store', {}); + expect(new Form().store).to.be.null; + }); - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); + it('hides cart display settings when customization is turned off', () => { + const form = new Form(); - it('renders a group label with i18n key foxycomplete', async () => { - const layout = html``; - const element = await fixture(layout); + expect(form.hiddenSelector.matches('cart-group-two', true)).to.be.true; + expect(form.hiddenSelector.matches('cart-group-three', true)).to.be.true; + expect(form.hiddenSelector.matches('cart-group-four', true)).to.be.true; - element.lang = 'es'; - element.ns = 'foo'; + const json = getDefaultJSON(); + json.cart_display_config.usage = 'required'; + form.edit({ json: JSON.stringify(json) }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const label = await getByKey(control, 'foxycomplete'); + expect(form.hiddenSelector.matches('cart-group-two', true)).to.be.false; + expect(form.hiddenSelector.matches('cart-group-three', true)).to.be.false; + expect(form.hiddenSelector.matches('cart-group-four', true)).to.be.false; + }); - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); + it('hides foxycomplete settings when it is turned off', () => { + const form = new Form(); + + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-show-combobox', true) + ).to.be.false; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-open', true) + ).to.be.false; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-close', true) + ).to.be.false; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-show-flags', true) + ).to.be.false; + + const json = getDefaultJSON(); + json.foxycomplete.usage = 'none'; + form.edit({ json: JSON.stringify(json) }); + + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-show-combobox', true) + ).to.be.true; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-open', true) + ).to.be.true; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-close', true) + ).to.be.true; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-show-flags', true) + ).to.be.true; + }); - it('renders a choice of foxycomplete configurations', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + it('hides foxycomplete combobox settings when combobox is off', () => { + const form = new Form(); + + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-open', true) + ).to.be.false; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-close', true) + ).to.be.false; + + const json = getDefaultJSON(); + json.foxycomplete.show_combobox = false; + form.edit({ json: JSON.stringify(json) }); + + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-open', true) + ).to.be.true; + expect( + form.hiddenSelector.matches('country-and-region-group-one:foxycomplete-combobox-close', true) + ).to.be.true; + }); - expect(choice).to.exist; - expect(choice).to.have.deep.property('items', ['combobox', 'search', 'disabled']); - }); + it('hides some location filtering controls depending on usage type', () => { + const form = new Form(); - it('selects "disabled" if foxycomplete.usage from parsed form.json is "none"', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; + expect(form.hiddenSelector.matches('country-and-region-group-two', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-three', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-four', true)).to.be.true; - element.data = data; - json.foxycomplete.usage = 'none'; - element.edit({ json: JSON.stringify(json) }); + const json = getDefaultJSON(); + json.location_filtering.usage = 'both'; + form.edit({ json: JSON.stringify(json) }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + expect(form.hiddenSelector.matches('country-and-region-group-two', true)).to.be.false; + expect(form.hiddenSelector.matches('country-and-region-group-three', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-four', true)).to.be.true; - expect(choice).to.have.property('value', 'disabled'); - }); + json.location_filtering.usage = 'shipping'; + form.edit({ json: JSON.stringify(json) }); - it('selects "combobox" if foxycomplete.usage from parsed form.json is "required" and foxycomplete.show_combobox is true', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; + expect(form.hiddenSelector.matches('country-and-region-group-two', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-three', true)).to.be.false; + expect(form.hiddenSelector.matches('country-and-region-group-four', true)).to.be.true; - element.data = data; - json.foxycomplete.usage = 'required'; - json.foxycomplete.show_combobox = true; - element.edit({ json: JSON.stringify(json) }); + json.location_filtering.usage = 'billing'; + form.edit({ json: JSON.stringify(json) }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + expect(form.hiddenSelector.matches('country-and-region-group-two', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-three', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-four', true)).to.be.false; - expect(choice).to.have.property('value', 'combobox'); - }); + json.location_filtering.usage = 'independent'; + form.edit({ json: JSON.stringify(json) }); - it('selects "search" if foxycomplete.usage from parsed form.json is "required" and foxycomplete.show_combobox is false', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; + expect(form.hiddenSelector.matches('country-and-region-group-two', true)).to.be.true; + expect(form.hiddenSelector.matches('country-and-region-group-three', true)).to.be.false; + expect(form.hiddenSelector.matches('country-and-region-group-four', true)).to.be.false; + }); - element.data = data; - json.foxycomplete.usage = 'required'; - json.foxycomplete.show_combobox = false; - element.edit({ json: JSON.stringify(json) }); + it('hides default checkout type setting when only one checkout type is configured', () => { + const form = new Form(); + const selector = 'customer-accounts:checkout-type-default'; + expect(form.hiddenSelector.matches(selector, true)).to.be.false; - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + const json = getDefaultJSON(); + json.checkout_type = 'default_guest'; + form.edit({ json: JSON.stringify(json) }); + expect(form.hiddenSelector.matches(selector, true)).to.be.false; - expect(choice).to.have.property('value', 'search'); - }); + json.checkout_type = 'account_only'; + form.edit({ json: JSON.stringify(json) }); + expect(form.hiddenSelector.matches(selector, true)).to.be.true; - const interactiveControlIds = [ - 'foxycomplete-choice', - 'foxycomplete-open-icon', - 'foxycomplete-close-icon', - 'foxycomplete-lookup-check', - 'foxycomplete-flags-check', - ]; - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.not.have.attribute('disabled'); - } - }); + json.checkout_type = 'guest_only'; + form.edit({ json: JSON.stringify(json) }); + expect(form.hiddenSelector.matches(selector, true)).to.be.true; + }); - it('is disabled when form is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; + it('hides ToS checkbox settings when ToS checkbox is not configured', () => { + const form = new Form(); + const $ = (s: string) => form.hiddenSelector.matches(s, true); - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.have.attribute('disabled'); - } - }); + expect($('consent-group-one:tos-checkbox-settings-url')).to.be.true; + expect($('consent-group-one:tos-checkbox-settings-initial-state')).to.be.true; + expect($('consent-group-one:tos-checkbox-settings-is-hidden')).to.be.true; - it('is disabled when disabledcontrols includes foxycomplete', async () => { - const element = await fixture(html` - - `); + const json = getDefaultJSON(); + json.tos_checkbox_settings.usage = 'required'; + form.edit({ json: JSON.stringify(json) }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; + expect($('consent-group-one:tos-checkbox-settings-url')).to.be.false; + expect($('consent-group-one:tos-checkbox-settings-initial-state')).to.be.false; + expect($('consent-group-one:tos-checkbox-settings-is-hidden')).to.be.false; + }); - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.have.attribute('disabled'); - } - }); + it('hides deprecated Google Analytics and Google Tag Manager settings when 3rd-party analytics are off', async () => { + const form = new Form(); + const $ = (s: string) => form.hiddenSelector.matches(s, true); - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; + expect($('analytics-config-google-analytics')).to.be.true; + expect($('analytics-config-google-tag')).to.be.true; - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.not.have.attribute('readonly'); - } - }); + const json = getDefaultJSON(); + const data = await getTestData('./hapi/template_configs/0'); + json.analytics_config.usage = 'required'; + json.analytics_config.google_analytics.usage = 'required'; + data.json = JSON.stringify(json); + form.data = data; - it('is readonly when form is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; + expect($('analytics-config-google-analytics')).to.be.false; + expect($('analytics-config-google-tag')).to.be.false; + }); - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.have.attribute('readonly'); - } - }); + it('hides deprecated Google Analytics section entirely unless it is already configured', async () => { + const form = new Form(); + const $ = (s: string) => form.hiddenSelector.matches(s, true); + expect($('analytics-config-google-analytics')).to.be.true; + + const json = getDefaultJSON(); + const data = await getTestData('./hapi/template_configs/0'); + json.analytics_config.usage = 'required'; + json.analytics_config.google_analytics.usage = 'required'; + form.edit({ json: JSON.stringify(json) }); + expect($('analytics-config-google-analytics')).to.be.true; + + data.json = JSON.stringify(json); + form.data = data; + expect($('analytics-config-google-analytics')).to.be.false; + }); - it('is readonly when readonlycontrols includes foxycomplete', async () => { - const element = await fixture(html` - - `); + it('hides deprecated Google Analytics settings when it is turned off', async () => { + const form = new Form(); + const $ = (s: string) => form.hiddenSelector.matches(s, true); + + expect($('analytics-config-google-analytics:analytics-config-google-analytics-account-id')).to + .be.true; + expect($('analytics-config-google-analytics:analytics-config-google-analytics-include-on-site')) + .to.be.true; + + const json = getDefaultJSON(); + const data = await getTestData('./hapi/template_configs/0'); + json.analytics_config.usage = 'required'; + json.analytics_config.google_analytics.usage = 'required'; + data.json = JSON.stringify(json); + form.data = data; + + expect($('analytics-config-google-analytics:analytics-config-google-analytics-account-id')).to + .be.false; + expect($('analytics-config-google-analytics:analytics-config-google-analytics-include-on-site')) + .to.be.false; + + json.analytics_config.google_analytics.usage = 'none'; + form.edit({ json: JSON.stringify(json) }); + + expect($('analytics-config-google-analytics:analytics-config-google-analytics-account-id')).to + .be.true; + expect($('analytics-config-google-analytics:analytics-config-google-analytics-include-on-site')) + .to.be.true; + }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; + it('hides Google Tag Manager settings when it is turned off', () => { + const form = new Form(); + const $ = (s: string) => form.hiddenSelector.matches(s, true); - for (const id of interactiveControlIds) { - expect(await getByTestId(control, id)).to.have.attribute('readonly'); - } - }); + expect($('analytics-config-google-tag:analytics-config-google-tag-account-id')).to.be.true; + expect($('analytics-config-google-tag:analytics-config-google-tag-send-to')).to.be.true; - it('writes to foxycomplete property of parsed form.json value on change (combobox)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + const json = getDefaultJSON(); + json.analytics_config.usage = 'required'; + json.analytics_config.google_tag.usage = 'required'; + form.edit({ json: JSON.stringify(json) }); - choice.value = 'combobox'; - choice.dispatchEvent(new ChoiceChangeEvent('combobox')); + expect($('analytics-config-google-tag:analytics-config-google-tag-account-id')).to.be.false; + expect($('analytics-config-google-tag:analytics-config-google-tag-send-to')).to.be.false; + }); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('foxycomplete.usage', 'required'); - expect(json).to.have.nested.property('foxycomplete.show_combobox', true); - }); + it('hides custom multiship settings when multiship is turned off for store', async () => { + const router = createRouter(); + const element = await fixture
(html` + router.handleEvent(evt)}> + + `); + + expect(element.hiddenSelector.matches('custom-script-values-multiship-checkout-fields', true)) + .to.be.true; + + await new Form.API(element).fetch('https://demo.api/hapi/stores/0', { + method: 'PATCH', + body: JSON.stringify({ features_multiship: false }), + }); + + element.store = 'https://demo.api/hapi/stores/0'; + await element.requestUpdate(); + await waitUntil( + () => { + const nucleon = element.renderRoot.querySelector>('#storeLoader'); + return !!nucleon?.data; + }, + undefined, + { timeout: 5000 } + ); + + await element.requestUpdate(); + expect(element.hiddenSelector.matches('custom-script-values-multiship-checkout-fields', true)) + .to.be.true; + + element.store = ''; + await element.requestUpdate(); + await new Form.API(element).fetch('https://demo.api/hapi/stores/0', { + method: 'PATCH', + body: JSON.stringify({ features_multiship: true }), + }); + + element.store = 'https://demo.api/hapi/stores/0'; + await element.requestUpdate(); + await waitUntil( + () => { + const nucleon = element.renderRoot.querySelector>('#storeLoader'); + return !!nucleon?.data; + }, + undefined, + { timeout: 5000 } + ); + + await element.requestUpdate(); + expect(element.hiddenSelector.matches('custom-script-values-multiship-checkout-fields', true)) + .to.be.false; + }); - it('writes to foxycomplete property of parsed form.json value on change (search)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; + it('renders form header', () => { + const form = new Form(); + const renderHeaderMethod = stub(form, 'renderHeader'); + form.render(); + expect(renderHeaderMethod).to.have.been.called; + }); - choice.value = 'search'; - choice.dispatchEvent(new ChoiceChangeEvent('search')); + it('renders four groups of cart settings', async () => { + const layout = html``; + const element = await fixture(layout); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('foxycomplete.usage', 'required'); - expect(json).to.have.nested.property('foxycomplete.show_combobox', false); + [ + 'foxy-internal-summary-control[infer="cart-group-one"]', + 'foxy-internal-summary-control[infer="cart-group-two"]', + 'foxy-internal-summary-control[infer="cart-group-three"]', + 'foxy-internal-summary-control[infer="cart-group-four"]', + ].forEach(selector => { + expect(element.renderRoot.querySelector(selector)).to.exist; }); + }); - it('writes to foxycomplete property of parsed form.json value on change (disabled)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; - - choice.value = 'disabled'; - choice.dispatchEvent(new ChoiceChangeEvent('disabled')); + it('renders select control for cart type in cart settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="cart-group-one"] foxy-internal-select-control[infer="cart-type"]' + ); + + const options = JSON.stringify([ + { label: 'option_default', value: 'default' }, + { label: 'option_fullpage', value: 'fullpage' }, + { label: 'option_custom', value: 'custom' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'cart_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('foxycomplete.usage', 'none'); - }); + it('renders switch control for cart config usage in cart settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="cart-group-one"] foxy-internal-switch-control[infer="cart-display-config-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'cart_display_config.usage'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('invert'); + }); - ['combobox', 'search', 'disabled'].forEach(type => { - it(`renders i18n label and explainer for ${type} foxycomplete config`, async () => { + [ + ['show_product_weight', 'show_product_category', 'show_product_code', 'show_product_options'], + ['show_sub_frequency', 'show_sub_startdate', 'show_sub_nextdate', 'show_sub_enddate'], + ].forEach((group, index) => { + const suffix = index === 0 ? 'two' : 'three'; + group.forEach(prop => { + it(`renders switch control for ${prop} in cart settings group ${suffix}`, async () => { const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const choice = (await getByTestId(control, 'foxycomplete-choice')) as Choice; - const wrapper = choice.querySelector(`[slot="${type}-label"]`) as HTMLElement; - const label = await getByKey(wrapper, `foxycomplete_${type}`); - const explainer = await getByKey(wrapper, `foxycomplete_${type}_explainer`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); + const element = await fixture(layout); + const controlInfer = `cart-display-config-${prop.replace(/_/g, '-')}`; + const control = element.renderRoot.querySelector( + `[infer="cart-group-${suffix}"] foxy-internal-switch-control[infer="${controlInfer}"]` + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', `cart_display_config.${prop}`); + expect(control).to.have.attribute('property', 'json'); }); }); + }); - ['open', 'close'].forEach(action => { - it(`renders a text field with i18n label ${action}_icon`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const field = await getByTestId(control, `foxycomplete-${action}-icon`); - - expect(field).to.have.attribute('label', `${action}_icon`); - }); - - it(`reflects the value of foxycomplete.combobox_${action} of parsed form.json`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); - - element.data = data; - json.foxycomplete[`combobox_${action}`] = 'Test'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const field = await getByTestId(control, `foxycomplete-${action}-icon`); - - expect(field).to.have.property('value', 'Test'); - }); - - it(`writes to foxycomplete.combobox_${action} of parsed form.json on input`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const field = (await getByTestId( - control, - `foxycomplete-${action}-icon` - )) as TextFieldElement; - - field.value = 'Test'; - field.dispatchEvent(new CustomEvent('input')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property(`foxycomplete.combobox_${action}`, 'Test'); - }); - - it(`submits a valid form on enter (${action} icon field)`, async () => { - const layout = html``; - const element = await fixture(layout); - const submit = stub(element, 'submit'); - - element.data = await getTestData('./hapi/template_configs/0'); - - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const field = (await getByTestId( - control, - `foxycomplete-${action}-icon` - )) as TextFieldElement; + it('renders editable list control for hidden product options in cart settings group four', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="cart-group-four"] foxy-internal-editable-list-control[infer="cart-display-config-hidden-product-options"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'cart_display_config.hidden_product_options'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + expect(control).to.have.attribute('simple-value'); + }); - field.value = 'Test'; - field.dispatchEvent(new CustomEvent('input')); - field.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); + it('renders four groups for country and region settings', async () => { + const layout = html``; + const element = await fixture(layout); - expect(submit).to.have.been.called; - }); + [ + 'foxy-internal-summary-control[infer="country-and-region-group-one"]', + 'foxy-internal-summary-control[infer="country-and-region-group-two"]', + 'foxy-internal-summary-control[infer="country-and-region-group-three"]', + 'foxy-internal-summary-control[infer="country-and-region-group-four"]', + ].forEach(selector => { + expect(element.renderRoot.querySelector(selector)).to.exist; }); + }); - it('renders a checkbox with i18n label show_country_flags', async () => { - const layout = html``; - const element = await fixture(layout); + it('renders switch control for foxycomplete usage in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-switch-control[infer="foxycomplete-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'foxycomplete.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - element.lang = 'es'; - element.ns = 'foo'; + it('renders select control for foxycomplete combobox usage in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-select-control[infer="foxycomplete-show-combobox"]' + ); + + const options = JSON.stringify([ + { label: 'option_combobox', value: 'combobox' }, + { label: 'option_search', value: 'search' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + + expect(control?.getValue()).to.equal('combobox'); + control?.setValue('search'); + expect(JSON.parse(element.form.json!).foxycomplete.show_combobox).to.equal(false); + }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = await getByTestId(control, 'foxycomplete-flags-check'); + it('renders text control for foxycomplete combobox open icon in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-text-control[infer="foxycomplete-combobox-open"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'foxycomplete.combobox_open'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - expect(check).to.exist; - expect(check).to.be.instanceOf(Checkbox); + it('renders text control for foxycomplete combobox close icon in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-text-control[infer="foxycomplete-combobox-close"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'foxycomplete.combobox_close'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const label = check?.querySelector('foxy-i18n[key="show_country_flags"]'); + it('renders switch control for foxycomplete show flags option in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-switch-control[infer="foxycomplete-show-flags"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'foxycomplete.show_flags'); + expect(control).to.have.attribute('property', 'json'); + }); - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); + it('renders switch control for postal code lookup feature in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-switch-control[infer="postal-code-lookup"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'enabled'); + expect(control).to.have.attribute('json-path', 'postal_code_lookup'); + expect(control).to.have.attribute('property', 'json'); + }); - [true, false].forEach(state => { - it(`reflects the value of foxycomplete.show_flags (${state}) of parsed form.json`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; + it('renders select control for location filtering usage in country and region settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-one"] foxy-internal-select-control[infer="location-filtering-usage"]' + ); + + const options = JSON.stringify([ + { label: 'option_none', value: 'none' }, + { label: 'option_shipping', value: 'shipping' }, + { label: 'option_billing', value: 'billing' }, + { label: 'option_both', value: 'both' }, + { label: 'option_independent', value: 'independent' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.usage'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + expect(control).to.have.attribute('options', options); + }); - json.foxycomplete.show_flags = state; - element.edit({ json: JSON.stringify(json) }); + it('renders select control for shipping filter type in country and region settings group two', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-two"] foxy-internal-select-control[infer="location-filtering-filter-type"]' + ); + + const options = JSON.stringify([ + { label: 'option_blocklist', value: 'blacklist' }, + { label: 'option_allowlist', value: 'whitelist' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.shipping_filter_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + expect(control).to.have.attribute('options', options); + }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = await getByTestId(control, 'foxycomplete-flags-check'); + it('renders filter values control for shipping and billing filter values in country and region settings group two', async () => { + const layout = html` + + + `; + + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-two"] foxy-internal-template-config-form-filter-values-control[infer="location-filtering-filter-values"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.shipping_filter_values'); + expect(control).to.have.attribute('countries', 'https://demo.api/hapi/property_helpers/3'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('regions', 'https://demo.api/hapi/property_helpers/4'); + + const testValue = { US: ['CA', 'NY'], CA: '*' }; + control?.setValue(testValue); + const config = JSON.parse(element.form.json!).location_filtering; + + expect(config.shipping_filter_values).to.deep.equal(testValue); + expect(config.billing_filter_values).to.deep.equal(testValue); + }); - expect(check).to.have.property('checked', state); - }); + it('renders select control for shipping filter type in country and region settings group three', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-three"] foxy-internal-select-control[infer="location-filtering-shipping-filter-type"]' + ); + + const options = JSON.stringify([ + { label: 'option_blocklist', value: 'blacklist' }, + { label: 'option_allowlist', value: 'whitelist' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.shipping_filter_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + expect(control).to.have.attribute('options', options); + }); - it(`writes to foxycomplete.show_flags of parsed form.json (value: ${state})`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = (await getByTestId(control, 'foxycomplete-flags-check')) as Checkbox; + it('renders filter values control for shipping filter values in country and region settings group three', async () => { + const layout = html` + + + `; + + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-three"] foxy-internal-template-config-form-filter-values-control[infer="location-filtering-shipping-filter-values"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.shipping_filter_values'); + expect(control).to.have.attribute('countries', 'https://demo.api/hapi/property_helpers/3'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('regions', 'https://demo.api/hapi/property_helpers/4'); + }); - check.checked = state; - check.dispatchEvent(new CheckboxChangeEvent(state)); + it('renders select control for billing filter type in country and region settings group four', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-four"] foxy-internal-select-control[infer="location-filtering-billing-filter-type"]' + ); + + const options = JSON.stringify([ + { label: 'option_blocklist', value: 'blacklist' }, + { label: 'option_allowlist', value: 'whitelist' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.billing_filter_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + expect(control).to.have.attribute('options', options); + }); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('foxycomplete.show_flags', state); - }); - }); + it('renders filter values control for billing filter values in country and region settings group four', async () => { + const layout = html` + + + `; + + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="country-and-region-group-four"] foxy-internal-template-config-form-filter-values-control[infer="location-filtering-billing-filter-values"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'location_filtering.billing_filter_values'); + expect(control).to.have.attribute('countries', 'https://demo.api/hapi/property_helpers/3'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('regions', 'https://demo.api/hapi/property_helpers/4'); + }); - it('renders a checkbox with i18n label enable_postcode_lookup', async () => { - const layout = html``; - const element = await fixture(layout); + it('renders group for customer account settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="customer-accounts"]' + ); - element.lang = 'es'; - element.ns = 'foo'; + expect(group).to.exist; + }); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = await getByTestId(control, 'foxycomplete-lookup-check'); + it('renders select control for checkout type in customer account settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="customer-accounts"] foxy-internal-select-control[infer="checkout-type-account"]' + ); + + const options = JSON.stringify([ + { label: 'option_account', value: 'account_only' }, + { label: 'option_guest', value: 'guest_only' }, + { label: 'option_both', value: 'default_guest' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'checkout_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + + const json = getDefaultJSON(); + json.checkout_type = 'account_only'; + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal('account_only'); + + json.checkout_type = 'guest_only'; + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal('guest_only'); + + json.checkout_type = 'default_guest'; + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal('default_guest'); + + control?.setValue('default_account'); + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal('default_guest'); + }); - expect(check).to.exist; - expect(check).to.be.instanceOf(Checkbox); + it('renders select control for default checkout type in customer account settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="customer-accounts"] foxy-internal-select-control[infer="checkout-type-default"]' + ); + + const options = JSON.stringify([ + { label: 'option_default_account', value: 'default_account' }, + { label: 'option_default_guest', value: 'default_guest' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'checkout_type'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const label = check?.querySelector('foxy-i18n[key="enable_postcode_lookup"]'); + it('renders three groups for consent settings', async () => { + const layout = html``; + const element = await fixture(layout); - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); + [ + 'foxy-internal-summary-control[infer="consent-group-one"]', + 'foxy-internal-summary-control[infer="consent-group-two"]', + 'foxy-internal-summary-control[infer="consent-group-three"]', + ].forEach(selector => { + expect(element.renderRoot.querySelector(selector)).to.exist; }); + }); - [true, false].forEach(state => { - it(`reflects the value of postal_code_lookup.usage (${state}) of parsed form.json`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.postal_code_lookup.usage = state ? 'enabled' : 'none'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = await getByTestId(control, 'foxycomplete-lookup-check'); - - expect(check).to.have.property('checked', state); - }); + it('renders select control for ToS checkbox usage in consent settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-one"] foxy-internal-select-control[infer="tos-checkbox-settings-usage"]' + ); + + const options = JSON.stringify([ + { label: 'option_none', value: 'none' }, + { label: 'option_optional', value: 'optional' }, + { label: 'option_required', value: 'required' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'tos_checkbox_settings.usage'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - it(`writes to postal_code_lookup.usage of parsed form.json (value: ${state})`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'foxycomplete')) as HTMLElement; - const check = (await getByTestId(control, 'foxycomplete-lookup-check')) as Checkbox; + it('renders text control for ToS checkbox URL in consent settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-one"] foxy-internal-text-control[infer="tos-checkbox-settings-url"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'tos_checkbox_settings.url'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - check.checked = state; - check.dispatchEvent(new CheckboxChangeEvent(state)); + it('renders select control for ToS checkbox initial state in consent settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-one"] foxy-internal-select-control[infer="tos-checkbox-settings-initial-state"]' + ); + + const options = JSON.stringify([ + { label: 'option_checked', value: 'checked' }, + { label: 'option_unchecked', value: 'unchecked' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'tos_checkbox_settings.initial_state'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - const expectedValue = state ? 'enabled' : 'none'; + it('renders switch control for using hidden item option with ToS checkbox in consent settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-one"] foxy-internal-switch-control[infer="tos-checkbox-settings-is-hidden"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'tos_checkbox_settings.is_hidden'); + expect(control).to.have.attribute('property', 'json'); + }); - expect(json).to.have.nested.property('postal_code_lookup.usage', expectedValue); - }); - }); + it('renders switch control for using hidden item option with ToS checkbox in consent settings group two', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-two"] foxy-internal-switch-control[infer="eu-secure-data-transfer-consent-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'eu_secure_data_transfer_consent.usage'); + expect(control).to.have.attribute('property', 'json'); }); - describe('locations', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'locations')).to.exist; - }); + it('renders switch control for newsletter subscription usage in consent settings group three', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="consent-group-three"] foxy-internal-switch-control[infer="newsletter-subscribe-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'newsletter_subscribe.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'locations')).to.not.exist; - }); + it('renders group for supported cards settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="supported-cards-group"]' + ); - it('is hidden when hiddencontrols includes locations', async () => { - const element = await fixture( - html`` - ); + expect(group).to.exist; + }); - expect(await getByTestId(element, 'locations')).to.not.exist; - }); + it('renders supported cards control in supported cards settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="supported-cards-group"] foxy-internal-template-config-form-supported-cards-control[infer="supported-payment-cards"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'supported_payment_cards'); + expect(control).to.have.attribute('property', 'json'); + }); - it('renders "locations:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'locations:before'); + it('renders disclaimer in supported cards settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const disclaimer = element.renderRoot.querySelector( + '[infer="supported-cards-group"] foxy-i18n[infer=""][key="disclaimer"]' + ); - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); + expect(disclaimer).to.exist; + }); - it('replaces "locations:before" slot with template "locations:before" if available', async () => { - const type = 'locations:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); + it('renders group for CSC requirements settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="csc-requirements-group"]' + ); - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; + expect(group).to.exist; + }); - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); + it('renders select control for CSC requirements in CSC requirements settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="csc-requirements-group"] foxy-internal-select-control[infer="csc-requirements"]' + ); + + const options = JSON.stringify([ + { label: 'option_all_cards', value: 'all_cards' }, + { label: 'option_sso_only', value: 'sso_only' }, + { label: 'option_new_cards_only', value: 'new_cards_only' }, + ]); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'csc_requirements'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', options); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - it('renders "locations:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'locations:after'); + it('renders three groups for checkout fields settings', async () => { + const layout = html``; + const element = await fixture(layout); - expect(slot).to.be.instanceOf(HTMLSlotElement); + [ + 'foxy-internal-summary-control[infer="checkout-fields-group-one"]', + 'foxy-internal-summary-control[infer="checkout-fields-group-two"]', + 'foxy-internal-summary-control[infer="checkout-fields-group-three"]', + ].forEach(selector => { + expect(element.renderRoot.querySelector(selector)).to.exist; }); + }); - it('replaces "locations:after" slot with template "locations:after" if available', async () => { - const type = 'locations:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); + it('renders switch control for toggling cart controls in checkout fields settings group one', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="checkout-fields-group-one"] foxy-internal-switch-control[infer="custom-checkout-field-requirements-cart-controls"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'disabled'); + expect(control).to.have.attribute('true-alias', 'enabled'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute( + 'json-path', + 'custom_checkout_field_requirements.cart_controls' + ); + }); - ['location_plural', 'shipping', 'billing'].forEach(key => { - it(`renders a group label with i18n key ${key}`, async () => { + [ + [ + 'billing_address1', + 'billing_address2', + 'billing_city', + 'billing_region', + 'billing_postal_code', + 'billing_country', + ], + [ + 'billing_first_name', + 'billing_last_name', + 'billing_company', + 'billing_tax_id', + 'billing_phone', + 'coupon_entry', + ], + ].forEach((group, index) => { + const suffix = index === 0 ? 'two' : 'three'; + group.forEach(prop => { + const infer = prop.replace(/_/g, '-').replace('1', '-one').replace('2', '-two'); + const path = `custom_checkout_field_requirements.${prop}`; + + it(`renders select control for ${prop} in checkout fields settings group ${suffix}`, async () => { const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const label = await getByKey(control, key); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + `[infer="checkout-fields-group-${suffix}"] foxy-internal-select-control[infer="custom-checkout-field-requirements-${infer}"]` + ); + + const options = + prop === 'coupon_entry' + ? [ + { label: 'option_enabled', value: 'enabled' }, + { label: 'option_disabled', value: 'disabled' }, + ] + : [ + { label: 'option_default', value: 'default' }, + { label: 'option_optional', value: 'optional' }, + { label: 'option_required', value: 'required' }, + { label: 'option_hidden', value: 'hidden' }, + ]; + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', path); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('options', JSON.stringify(options)); + expect(control).to.have.attribute('layout', 'summary-item'); }); }); + }); - const interactiveControlIds = [ - 'locations-shipping-choice', - 'locations-shipping-list', - 'locations-billing-choice', - 'locations-billing-list', - ]; - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must not be disabled`).to.not.have.attribute('disabled'); - } - }); - - it('is disabled when form is disabled', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must be disabled`).to.have.attribute('disabled'); - } - }); - - it('is disabled when disabledcontrols includes locations', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must be disabled`).to.have.attribute('disabled'); - } - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must not be readonly`).to.not.have.attribute('readonly'); - } - }); - - it('is readonly when form is readonly', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must be readonly`).to.have.attribute('readonly'); - } - }); - - it('is readonly when readonlycontrols includes locations', async () => { - const element = await fixture( - html`` - ); + it('renders group for analytics settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="analytics-config"]' + ); - const control = (await getByTestId(element, 'locations')) as HTMLElement; + expect(group).to.exist; + }); - for (const id of interactiveControlIds) { - const node = await getByTestId(control, id); - expect(node, `${id} must be readonly`).to.have.attribute('readonly'); - } - }); + it('renders switch control for analytics usage in analytics settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config"] foxy-internal-switch-control[infer="analytics-config-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'analytics_config.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - it('renders a choice of shipping filter configurations', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-shipping-choice')) as Choice; + it('renders group for google analytics settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="analytics-config-google-analytics"]' + ); - expect(choice).to.exist; - expect(choice).to.have.deep.property('items', ['allow', 'block']); - }); + expect(group).to.exist; + }); - [ - ['block', 'blacklist', 'blocklist'], - ['allow', 'whitelist', 'allowlist'], - ].forEach(([choiceValue, apiValue, labelKey]) => { - it(`selects "${choiceValue}" shipping filter configuration if location_filtering.shipping_filter_type of parsed form.json is "${apiValue}"`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); + it('renders switch control for google analytics usage in google analytics settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-analytics"] foxy-internal-switch-control[infer="analytics-config-google-analytics-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'analytics_config.google_analytics.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - element.data = data; - json.location_filtering.shipping_filter_type = apiValue; - element.edit({ json: JSON.stringify(json) }); + it('renders text control for google analytics account ID in google analytics settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-analytics"] foxy-internal-text-control[infer="analytics-config-google-analytics-account-id"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'analytics_config.google_analytics.account_id'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-shipping-choice')) as Choice; + it('renders switch control for including google analytics on site in google analytics settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-analytics"] foxy-internal-switch-control[infer="analytics-config-google-analytics-include-on-site"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute( + 'json-path', + 'analytics_config.google_analytics.include_on_site' + ); + }); - expect(choice).to.have.property('value', choiceValue); - }); + it('renders deprecation notice in google analytics settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const notice = element.renderRoot.querySelector( + '[infer="analytics-config-google-analytics"] foxy-i18n[infer=""][key="deprecation_notice"]' + ); - it(`sets location_filtering.shipping_filter_type property of parsed form.json to "${apiValue}" when "${choiceValue}" is selected`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-shipping-choice')) as Choice; + expect(notice).to.exist; + }); - choice.value = choiceValue; - choice.dispatchEvent(new ChoiceChangeEvent(choiceValue)); + it('renders group for google tag settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector( + 'foxy-internal-summary-control[infer="analytics-config-google-tag"]' + ); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('location_filtering.shipping_filter_type', apiValue); - }); + expect(group).to.exist; + }); - it(`renders an i18n label for choice option "${choiceValue}" with the "${labelKey}" key`, async () => { - const layout = html``; - const element = await fixture(layout); + it('renders switch control for google tag usage in google tag settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-tag"] foxy-internal-switch-control[infer="analytics-config-google-tag-usage"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'analytics_config.google_tag.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - element.lang = 'es'; - element.ns = 'foo'; + it('renders text control for google tag account ID in google tag settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-tag"] foxy-internal-text-control[infer="analytics-config-google-tag-account-id"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'analytics_config.google_tag.account_id'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-shipping-choice')) as Choice; - const label = choice.querySelector(`foxy-i18n[slot="${choiceValue}-label"]`); + it('renders text control for google tag send to value in google tag settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + '[infer="analytics-config-google-tag"] foxy-internal-text-control[infer="analytics-config-google-tag-send-to"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'analytics_config.google_tag.send_to'); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute('layout', 'summary-item'); + }); - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('key', labelKey); - expect(label).to.have.attribute('ns', 'foo'); - }); - }); + it('renders usage notice in google tag settings group', async () => { + const layout = html``; + const element = await fixture(layout); + const notice = element.renderRoot.querySelector( + '[infer="analytics-config-google-tag"] foxy-i18n[infer=""][key="usage_notice"]' + ); - it('renders a CountriesList for shipping filter values', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.location_filtering.shipping_filter_values = { US: ['AL', 'TX'], RU: '*' }; - element.edit({ json: JSON.stringify(json) }); - - element.countries = 'test://countries'; - element.regions = 'test://regions'; - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const list = await getByTestId(control, 'locations-shipping-list'); - - expect(list).to.be.instanceOf(CountriesList); - expect(list).to.have.attribute('countries', JSON.stringify({ US: ['AL', 'TX'], RU: '*' })); - expect(list).to.have.attribute('regions', 'test://regions'); - expect(list).to.have.attribute('href', 'test://countries'); - expect(list).to.have.attribute('lang', 'es'); - expect(list).to.have.attribute('ns', 'foo'); - }); + expect(notice).to.exist; + }); - it('sets shipping filter values on change', async () => { - const layout = html``; - const element = await fixture(layout); + it('renders group for debug settings', async () => { + const layout = html``; + const element = await fixture(layout); + const group = element.renderRoot.querySelector('foxy-internal-summary-control[infer="debug"]'); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const list = (await getByTestId(control, 'locations-shipping-list')) as CountriesList; + expect(group).to.exist; + }); - list.countries = { US: ['AL', 'TX'], RU: '*' }; - list.dispatchEvent(new CustomEvent('update:countries')); + it('renders switch control for debug usage in debug settings group', async () => { + const layout = html``; + const element = await fixture(layout); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.deep.nested.property('location_filtering.shipping_filter_values', { - US: ['AL', 'TX'], - RU: '*', - }); - }); + const control = element.renderRoot.querySelector( + '[infer="debug"] foxy-internal-switch-control[infer="debug-usage"]' + ); - it('renders a choice of billing filter configurations', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('false-alias', 'none'); + expect(control).to.have.attribute('true-alias', 'required'); + expect(control).to.have.attribute('json-path', 'debug.usage'); + expect(control).to.have.attribute('property', 'json'); + }); - expect(choice).to.exist; - expect(choice).to.have.deep.property('items', ['allow', 'block', 'copy']); - }); + it('renders source control for custom config', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + 'foxy-internal-source-control[infer="custom-config"]' + ); - it('selects "copy" billing filter configuration if location_filtering.usage of parsed form.json is "both"', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); + expect(control).to.exist; + expect(control?.getValue()).to.equal(''); - element.data = data; - json.location_filtering.usage = 'both'; - element.edit({ json: JSON.stringify(json) }); + const json = getDefaultJSON(); + json.custom_config = { foo: 'bar' }; + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal(JSON.stringify(json.custom_config, null, 2)); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; + json.custom_config = 'test'; + element.edit({ json: JSON.stringify(json) }); + expect(control?.getValue()).to.equal('test'); - expect(choice).to.have.property('value', 'copy'); - }); + control?.setValue(JSON.stringify({ baz: 'qux' }, null, 2)); + expect(JSON.parse(element.form.json!).custom_config).to.deep.equal({ baz: 'qux' }); + }); - ['none', 'shipping', 'billing', 'independent'].forEach(type => { - it(`selects "block" billing filter configuration if location_filtering.usage of parsed form.json is "${type}" and location_filtering.billing_filter_type is "blacklist"`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); + it('renders source control for custom header markup', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + 'foxy-internal-source-control[infer="custom-script-values-header"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'custom_script_values.header'); + expect(control).to.have.attribute('property', 'json'); + }); - json.location_filtering.billing_filter_type = 'blacklist'; - json.location_filtering.usage = type; - element.edit({ json: JSON.stringify(json) }); + it('renders source control for custom checkout fields markup', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + 'foxy-internal-source-control[infer="custom-script-values-checkout-fields"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'custom_script_values.checkout_fields'); + expect(control).to.have.attribute('property', 'json'); + }); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; + it('renders source control for custom multiship checkout fields markup', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + 'foxy-internal-source-control[infer="custom-script-values-multiship-checkout-fields"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('property', 'json'); + expect(control).to.have.attribute( + 'json-path', + 'custom_script_values.multiship_checkout_fields' + ); + }); - expect(choice).to.have.property('value', 'block'); - }); - - it(`selects "allow" billing filter configuration if location_filtering.usage of parsed form.json is "${type}" and location_filtering.billing_filter_type is "whitelist"`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); - - json.location_filtering.billing_filter_type = 'whitelist'; - json.location_filtering.usage = type; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; - - expect(choice).to.have.property('value', 'allow'); - }); - }); - - it('sets location_filtering.billing_filter_type property of parsed form.json to "blacklist" when "block" is selected', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; - - choice.value = 'block'; - choice.dispatchEvent(new ChoiceChangeEvent('block')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('location_filtering.billing_filter_type', 'blacklist'); - }); - - it('sets location_filtering.billing_filter_type property of parsed form.json to "whitelist" when "allow" is selected', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; - - choice.value = 'allow'; - choice.dispatchEvent(new ChoiceChangeEvent('allow')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('location_filtering.billing_filter_type', 'whitelist'); - }); - - it('copies shipping filter config to billing when "copy" is selected', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const choice = (await getByTestId(control, 'locations-billing-choice')) as Choice; - - choice.value = 'copy'; - choice.dispatchEvent(new ChoiceChangeEvent('copy')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.nested.property( - 'location_filtering.billing_filter_type', - json.location_filtering.shipping_filter_type - ); - - expect(json).to.have.nested.property( - 'location_filtering.shipping_filter_values', - json.location_filtering.shipping_filter_values - ); - }); - - it('renders a CountriesList for billing filter values', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.location_filtering.billing_filter_values = { US: ['AL', 'TX'], RU: '*' }; - element.edit({ json: JSON.stringify(json) }); - - element.countries = 'test://countries'; - element.regions = 'test://regions'; - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const list = await getByTestId(control, 'locations-billing-list'); - - expect(list).to.be.instanceOf(CountriesList); - expect(list).to.have.attribute('countries', JSON.stringify({ US: ['AL', 'TX'], RU: '*' })); - expect(list).to.have.attribute('regions', 'test://regions'); - expect(list).to.have.attribute('href', 'test://countries'); - expect(list).to.have.attribute('lang', 'es'); - expect(list).to.have.attribute('ns', 'foo'); - }); - - it('sets billing filter values on change', async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const list = (await getByTestId(control, 'locations-billing-list')) as CountriesList; - - list.countries = { US: ['AL', 'TX'], RU: '*' }; - list.dispatchEvent(new CustomEvent('update:countries')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.deep.nested.property('location_filtering.billing_filter_values', { - US: ['AL', 'TX'], - RU: '*', - }); - }); - - it('switches location_filtering.usage of parsed form.json to "none" on change if there aren\'t any filters', async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const shippingList = (await getByTestId(control, 'locations-shipping-list')) as CountriesList; - const billingList = (await getByTestId(control, 'locations-billing-list')) as CountriesList; - - shippingList.countries = {}; - shippingList.dispatchEvent(new CustomEvent('update:countries')); - billingList.countries = {}; - billingList.dispatchEvent(new CustomEvent('update:countries')); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('location_filtering.usage', 'none'); - }); - - it('switches location_filtering.usage of parsed form.json to "billing" on change if there are only billing filters', async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const shippingList = (await getByTestId(control, 'locations-shipping-list')) as CountriesList; - const billingList = (await getByTestId(control, 'locations-billing-list')) as CountriesList; - - shippingList.countries = {}; - shippingList.dispatchEvent(new CustomEvent('update:countries')); - billingList.countries = { US: '*' }; - billingList.dispatchEvent(new CustomEvent('update:countries')); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('location_filtering.usage', 'billing'); - }); - - it('switches location_filtering.usage of parsed form.json to "shipping" on change if there are only shipping filters', async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const shippingList = (await getByTestId(control, 'locations-shipping-list')) as CountriesList; - const billingList = (await getByTestId(control, 'locations-billing-list')) as CountriesList; - - shippingList.countries = { US: '*' }; - shippingList.dispatchEvent(new CustomEvent('update:countries')); - billingList.countries = {}; - billingList.dispatchEvent(new CustomEvent('update:countries')); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('location_filtering.usage', 'shipping'); - }); - - it('switches location_filtering.usage of parsed form.json to "independent" on change if there are both shipping and billing filters', async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'locations')) as HTMLElement; - const shippingList = (await getByTestId(control, 'locations-shipping-list')) as CountriesList; - const billingList = (await getByTestId(control, 'locations-billing-list')) as CountriesList; - - shippingList.countries = { US: '*' }; - shippingList.dispatchEvent(new CustomEvent('update:countries')); - billingList.countries = { AL: '*' }; - billingList.dispatchEvent(new CustomEvent('update:countries')); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('location_filtering.usage', 'independent'); - }); - }); - - describe('hidden-fields', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'hidden-fields')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'hidden-fields')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes hidden-fields', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'hidden-fields')).to.not.exist; - }); - - it('renders "hidden-fields:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'hidden-fields:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "hidden-fields:before" slot with template "hidden-fields:before" if available', async () => { - const type = 'hidden-fields:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "hidden-fields:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'hidden-fields:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "hidden-fields:after" slot with template "hidden-fields:after" if available', async () => { - const type = 'hidden-fields:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a group label with i18n key hidden_fields', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const label = await getByKey(control, 'hidden_fields'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - const builtIns = [ - 'show_product_weight', - 'show_product_category', - 'show_product_code', - 'show_product_options', - 'show_sub_frequency', - 'show_sub_startdate', - 'show_sub_nextdate', - 'show_sub_enddate', - ] as const; - - builtIns.forEach(name => { - it(`reflects the value of cart_display_config from parsed form.json (built-ins, hidden, ${name})`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.cart_display_config.usage = 'required'; - json.cart_display_config[name] = false; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-list')) as HTMLElement; - const item = Array.from(list.children).find(child => { - return !!child.querySelector(`foxy-i18n[key="${name.substring(5)}"]`); - }); - - expect(item).to.exist; - }); - - it(`reflects the value of cart_display_config from parsed form.json (built-ins, visible, ${name})`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.cart_display_config.usage = 'required'; - json.cart_display_config[name] = true; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-list')) as HTMLElement; - const item = Array.from(list.children).find(child => { - return !!child.querySelector(`foxy-i18n[key="${name.substring(5)}"]`); - }); - - expect(item).to.not.exist; - }); - - it(`writes to cart_display_config property of parsed form.json on delete (built-ins, ${name})`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.cart_display_config.usage = 'required'; - json.cart_display_config[name] = false; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-list')) as HTMLElement; - const item = Array.from(list.children).find(child => { - return !!child.querySelector(`foxy-i18n[key="${name.substring(5)}"]`); - }) as HTMLElement; - - const button = item.querySelector('button[aria-label="delete"]') as HTMLButtonElement; - button.click(); - - const newJSON = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(newJSON).to.have.nested.property(`cart_display_config.${name}`, true); - }); - }); - - it('reflects the value of cart_display_config from parsed form.json (custom)', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.cart_display_config.usage = 'required'; - json.cart_display_config.hidden_product_options = ['foo_test_field', 'bar_test_field']; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-list')) as HTMLElement; - const items = Array.from(list.children); - - json.cart_display_config.hidden_product_options.forEach(name => { - const item = items.find(child => child.textContent?.includes(name)); - expect(item).to.exist; - }); - }); - - it('writes to cart_display_config property of parsed form.json on delete (custom)', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.cart_display_config.usage = 'required'; - json.cart_display_config.hidden_product_options = ['foo_test_field', 'bar_test_field']; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-list')) as HTMLElement; - const items = Array.from(list.children); - - json.cart_display_config.hidden_product_options.forEach(name => { - const item = items.find(child => child.textContent?.includes(name)) as HTMLElement; - const button = item.querySelector('button[aria-label="delete"]') as HTMLButtonElement; - button.click(); - - const newJSON = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(newJSON.cart_display_config.hidden_product_options).to.not.include(name); - }); - }); - - it('writes to cart_display_config property of parsed form.json on add (via button click)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-new')) as HTMLElement; - const input = list.querySelector('input') as HTMLInputElement; - - input.value = 'foo_test_field'; - input.dispatchEvent(new InputEvent('input')); - - await element.requestUpdate(); - list.querySelector('button')!.click(); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.nested.property('cart_display_config.usage', 'required'); - expect(json).to.have.nested.property('cart_display_config.hidden_product_options'); - expect(json.cart_display_config.hidden_product_options).to.include('foo_test_field'); - }); - - it('writes to cart_display_config property of parsed form.json on add (via enter press)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const list = (await getByTestId(control, 'hidden-fields-new')) as HTMLElement; - const input = list.querySelector('input') as HTMLInputElement; - - input.value = 'foo_test_field'; - input.dispatchEvent(new InputEvent('input')); - input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.nested.property('cart_display_config.usage', 'required'); - expect(json).to.have.nested.property('cart_display_config.hidden_product_options'); - expect(json.cart_display_config.hidden_product_options).to.include('foo_test_field'); - }); - - it('is enabled by default', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - - control.querySelectorAll('input').forEach(input => { - input.value = 'Foo'; - input.dispatchEvent(new InputEvent('input')); - }); - - await element.requestUpdate(); - - control.querySelectorAll('button, input').forEach(node => { - expect(node).to.not.have.attribute('disabled'); - }); - }); - - it('disabled Add button when input is empty', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - const newField = (await getByTestId(control, 'hidden-fields-new')) as HTMLElement; - const newFieldButton = newField.querySelector('button'); - - expect(newFieldButton).to.have.attribute('disabled'); - }); - - it('is disabled when the form is disabled', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - - control.querySelectorAll('input').forEach(input => { - input.value = 'Foo'; - input.dispatchEvent(new InputEvent('input')); - }); - - await element.requestUpdate(); - - control.querySelectorAll('button, input').forEach(node => { - expect(node).to.have.attribute('disabled'); - }); - }); - - it('is disabled when disabledcontrols include hidden-fields', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - - control.querySelectorAll('input').forEach(input => { - input.value = 'Foo'; - input.dispatchEvent(new InputEvent('input')); - }); - - await element.requestUpdate(); - - control.querySelectorAll('button, input').forEach(node => { - expect(node).to.have.attribute('disabled'); - }); - }); - - it('is editable by default', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - control.querySelectorAll('input').forEach(node => { - expect(node).to.not.have.attribute('readonly'); - }); - }); - - it('is readonly when the form is readonly', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - control.querySelectorAll('input').forEach(node => { - expect(node).to.have.attribute('readonly'); - }); - }); - - it('is readonly when readonlycontrols include hidden-fields', async () => { - const element = await fixture( - html`` - ); - - const control = (await getByTestId(element, 'hidden-fields')) as HTMLElement; - control.querySelectorAll('input').forEach(node => { - expect(node).to.have.attribute('readonly'); - }); - }); - }); - - describe('cards', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'cards')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'cards')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes cards', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'cards')).to.not.exist; - }); - - it('renders "cards:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'cards:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "cards:before" slot with template "cards:before" if available', async () => { - const type = 'cards:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "cards:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'cards:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "cards:after" slot with template "cards:after" if available', async () => { - const type = 'cards:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a group label with i18n key supported_cards', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const label = await getByKey(control, 'supported_cards'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - const options: Record = { - amex: 'American Express', - diners: 'Diners Club', - discover: 'Discover', - jcb: 'JCB', - maestro: 'Maestro', - mastercard: 'Mastercard', - unionpay: 'UnionPay', - visa: 'Visa', - }; - - Object.entries(options).forEach(([type, name]) => { - it(`renders an option for ${type} (${name})`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const labels = control.querySelectorAll('label'); - const label = Array.from(labels).find(label => label.textContent?.includes(name)); - const input = label?.querySelector('input'); - - expect(label).to.exist; - expect(label).to.include.text(name); - expect(input).to.exist; - expect(input).to.have.attribute('type', 'checkbox'); - }); - - it(`reflects the value of supported_payment_cards from parsed form.json (${type}, excluded)`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.supported_payment_cards = []; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const labels = control.querySelectorAll('label'); - const label = Array.from(labels).find(label => label.textContent?.includes(name)); - const input = label?.querySelector('input'); - - expect(input).to.not.have.attribute('checked'); - }); - - it(`reflects the value of supported_payment_cards from parsed form.json (${type}, included)`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.supported_payment_cards = [type] as TemplateConfigJSON['supported_payment_cards']; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const labels = control.querySelectorAll('label'); - const label = Array.from(labels).find(label => label.textContent?.includes(name)); - const input = label?.querySelector('input'); - - expect(input).to.have.attribute('checked'); - }); - - it(`writes to supported_payment_cards property of parsed form.json value on change (${type}, excluded)`, async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const labels = control.querySelectorAll('label'); - const label = Array.from(labels).find(label => label.textContent?.includes(name))!; - const input = label.querySelector('input') as HTMLInputElement; - - input.checked = false; - input.dispatchEvent(new Event('change')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.property('supported_payment_cards'); - expect(json.supported_payment_cards).to.not.include(type); - }); - - it(`writes to supported_payment_cards property of parsed form.json value on change (${type}, included)`, async () => { - const layout = html``; - const element = await fixture(layout); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const labels = control.querySelectorAll('label'); - const label = Array.from(labels).find(label => label.textContent?.includes(name))!; - const input = label.querySelector('input') as HTMLInputElement; - - input.checked = true; - input.dispatchEvent(new Event('change')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.property('supported_payment_cards'); - expect(json.supported_payment_cards).to.include(type); - }); - }); - - it('reflects the value of csc_requirements (all_cards) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.csc_requirements = 'all_cards'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const savedCheck = await getByTestId(control, 'cards-saved-check'); - const ssoCheck = await getByTestId(control, 'cards-sso-check'); - - expect(savedCheck).to.not.have.attribute('checked'); - expect(ssoCheck).to.not.have.attribute('checked'); - }); - - it('reflects the value of csc_requirements (sso_only) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.csc_requirements = 'sso_only'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const savedCheck = await getByTestId(control, 'cards-saved-check'); - const ssoCheck = await getByTestId(control, 'cards-sso-check'); - - expect(savedCheck).to.have.attribute('checked'); - expect(ssoCheck).to.not.have.attribute('checked'); - }); - - it('reflects the value of csc_requirements (new_cards_only) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.csc_requirements = 'new_cards_only'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const savedCheck = await getByTestId(control, 'cards-saved-check'); - const ssoCheck = await getByTestId(control, 'cards-sso-check'); - - expect(savedCheck).to.have.attribute('disabled'); - expect(savedCheck).to.have.attribute('checked'); - expect(ssoCheck).to.have.attribute('checked'); - }); - - it('writes to csc_requirements property of parsed form.json value on change (cards-saved-check, true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const check = (await getByTestId(control, 'cards-saved-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('csc_requirements', 'sso_only'); - }); - - it('writes to csc_requirements property of parsed form.json value on change (cards-saved-check, false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const check = (await getByTestId(control, 'cards-saved-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('csc_requirements', 'all_cards'); - }); - - it('writes to csc_requirements property of parsed form.json value on change (cards-sso-check, true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const check = (await getByTestId(control, 'cards-sso-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('csc_requirements', 'new_cards_only'); - }); - - it('writes to csc_requirements property of parsed form.json value on change (cards-sso-check, false when cards-saved-check is true)', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.csc_requirements = 'new_cards_only'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const check = (await getByTestId(control, 'cards-sso-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const newJSON = JSON.parse(element.form.json as string); - expect(newJSON).to.have.nested.property('csc_requirements', 'sso_only'); - }); - - it('writes to csc_requirements property of parsed form.json value on change (cards-sso-check, false when cards-saved-check is false)', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.csc_requirements = 'all_cards'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const check = (await getByTestId(control, 'cards-sso-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const newJSON = JSON.parse(element.form.json as string); - expect(newJSON).to.have.nested.property('csc_requirements', 'all_cards'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.not.have.attribute('disabled'); - expect(ssoCheck).to.not.have.attribute('disabled'); - inputs.forEach(input => expect(input).to.not.have.attribute('disabled')); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.have.attribute('disabled'); - expect(ssoCheck).to.have.attribute('disabled'); - inputs.forEach(input => expect(input).to.have.attribute('disabled')); - }); - - it('is disabled when disabledcontrols includes cards', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.have.attribute('disabled'); - expect(ssoCheck).to.have.attribute('disabled'); - inputs.forEach(input => expect(input).to.have.attribute('disabled')); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.not.have.attribute('readonly'); - expect(ssoCheck).to.not.have.attribute('readonly'); - inputs.forEach(input => expect(input).to.not.have.attribute('readonly')); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.have.attribute('readonly'); - expect(ssoCheck).to.have.attribute('readonly'); - inputs.forEach(input => expect(input).to.have.attribute('readonly')); - }); - - it('is readonly when readonlycontrols includes cards', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const inputs = control.querySelectorAll('input'); - const ssoCheck = control.querySelector('[data-testid="cards-sso-check"]'); - const savedCheck = control.querySelector('[data-testid="cards-saved-check"]'); - - expect(savedCheck).to.have.attribute('readonly'); - expect(ssoCheck).to.have.attribute('readonly'); - inputs.forEach(input => expect(input).to.have.attribute('readonly')); - }); - - it('renders a disclaimer with i18n key supported_cards_disclaimer', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'cards')) as HTMLElement; - const label = await getByKey(control, 'supported_cards_disclaimer'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - }); - - describe('checkout-type', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'checkout-type')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'checkout-type')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes checkout-type', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'checkout-type')).to.not.exist; - }); - - it('renders "checkout-type:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'checkout-type:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "checkout-type:before" slot with template "checkout-type:before" if available', async () => { - const type = 'checkout-type:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "checkout-type:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'checkout-type:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "checkout-type:after" slot with template "checkout-type:after" if available', async () => { - const type = 'checkout-type:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a group label with i18n key checkout_type', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const label = await getByKey(control, 'checkout_type'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('renders a helper text with i18n key checkout_type_helper_text', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const text = await getByKey(control, 'checkout_type_helper_text'); - - expect(text).to.exist; - expect(text).to.have.attribute('lang', 'es'); - expect(text).to.have.attribute('ns', 'foo'); - }); - - it('renders a choice of checkout types', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.exist; - expect(choice).to.have.deep.property('items', [ - 'default_account', - 'default_guest', - 'guest_only', - 'account_only', - ]); - }); - - it('reflects the value of checkout_type from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.checkout_type = 'default_guest'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.have.property('value', 'default_guest'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes checkout-type', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes checkout-type', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice).to.have.attribute('readonly'); - }); - - it('renders i18n labels for each choice', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - expect(choice.getText('default_account')).to.equal('checkout_type_default_account'); - expect(choice.getText('default_guest')).to.equal('checkout_type_default_guest'); - expect(choice.getText('account_only')).to.equal('checkout_type_account_only'); - expect(choice.getText('guest_only')).to.equal('checkout_type_guest_only'); - }); - - it('writes to checkout_type property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'checkout-type')) as HTMLElement; - const choice = (await getByTestId(control, 'checkout-type-choice')) as Choice; - - choice.value = 'default_guest'; - choice.dispatchEvent(new ChoiceChangeEvent('default_guest')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.property('checkout_type', 'default_guest'); - }); - }); - - describe('consent', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'consent')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'consent')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes consent', async () => { - const element = await fixture(html` - - `); - - expect(await getByTestId(element, 'consent')).to.not.exist; - }); - - it('renders "consent:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'consent:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "consent:before" slot with template "consent:before" if available', async () => { - const type = 'consent:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "consent:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'consent:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "consent:after" slot with template "consent:after" if available', async () => { - const type = 'consent:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a label with i18n key consent', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const label = await getByKey(control, 'consent'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('renders a TOS URL field with i18n label location_url', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as HTMLElement; - const field = check.querySelector('[data-testid="consent-tos-field"]') as TextFieldElement; - - expect(field).to.have.attribute('label', 'location_url'); - }); - - it('reflects the value of tos_checkbox_settings.usage (required) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const generalCheck = await getByTestId(control, 'consent-tos-check'); - const requireCheck = await getByTestId(control, 'consent-tos-require-check'); - - expect(generalCheck).to.have.attribute('checked'); - expect(requireCheck).to.have.attribute('checked'); - }); - - it('reflects the value of tos_checkbox_settings.usage (optional) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.usage = 'optional'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const generalCheck = await getByTestId(control, 'consent-tos-check'); - const requireCheck = await getByTestId(control, 'consent-tos-require-check'); - - expect(generalCheck).to.have.attribute('checked'); - expect(requireCheck).to.not.have.attribute('checked'); - }); - - it('reflects the value of tos_checkbox_settings.usage (none) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.usage = 'none'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const generalCheck = await getByTestId(control, 'consent-tos-check'); - const requireCheck = await getByTestId(control, 'consent-tos-require-check'); - - expect(generalCheck).to.not.have.attribute('checked'); - expect(requireCheck).to.not.have.attribute('checked'); - }); - - it('reflects the value of tos_checkbox_settings.url from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.url = 'Test'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as HTMLElement; - const field = check.querySelector('[data-testid="consent-tos-field"]') as TextFieldElement; - - expect(field).to.have.property('value', 'Test'); - }); - - it('reflects the value of tos_checkbox_settings.initial_state (checked) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.initial_state = 'checked'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const generalCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const stateCheck = generalCheck.querySelector('[data-testid="consent-tos-state-check"]')!; - - expect(stateCheck).to.have.attribute('checked'); - }); - - it('reflects the value of tos_checkbox_settings.initial_state (unchecked) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.tos_checkbox_settings.initial_state = 'unchecked'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const generalCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const stateCheck = generalCheck.querySelector('[data-testid="consent-tos-state-check"]')!; - - expect(stateCheck).to.not.have.attribute('checked'); - }); - - it('reflects the value of newsletter_subscribe.usage (required) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.newsletter_subscribe.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = await getByTestId(control, 'consent-mail-check'); - - expect(check).to.have.attribute('checked'); - }); - - it('reflects the value of newsletter_subscribe.usage (none) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.newsletter_subscribe.usage = 'none'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = await getByTestId(control, 'consent-mail-check'); - - expect(check).to.not.have.attribute('checked'); - }); - - it('reflects the value of eu_secure_data_transfer_consent.usage (required) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.eu_secure_data_transfer_consent.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = await getByTestId(control, 'consent-sdta-check'); - - expect(check).to.have.attribute('checked'); - }); - - it('reflects the value of eu_secure_data_transfer_consent.usage (none) from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.eu_secure_data_transfer_consent.usage = 'none'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = await getByTestId(control, 'consent-sdta-check'); - - expect(check).to.not.have.attribute('checked'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.not.have.attribute('disabled'); - expect(tosField).to.not.have.attribute('disabled'); - expect(tosRequireCheck).to.not.have.attribute('disabled'); - expect(tosStateCheck).to.not.have.attribute('disabled'); - expect(mailCheck).to.not.have.attribute('disabled'); - expect(sdtaCheck).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.have.attribute('disabled'); - expect(tosField).to.have.attribute('disabled'); - expect(tosRequireCheck).to.have.attribute('disabled'); - expect(tosStateCheck).to.have.attribute('disabled'); - expect(mailCheck).to.have.attribute('disabled'); - expect(sdtaCheck).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes consent', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.have.attribute('disabled'); - expect(tosField).to.have.attribute('disabled'); - expect(tosRequireCheck).to.have.attribute('disabled'); - expect(tosStateCheck).to.have.attribute('disabled'); - expect(mailCheck).to.have.attribute('disabled'); - expect(sdtaCheck).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.not.have.attribute('readonly'); - expect(tosField).to.not.have.attribute('readonly'); - expect(tosRequireCheck).to.not.have.attribute('readonly'); - expect(tosStateCheck).to.not.have.attribute('readonly'); - expect(mailCheck).to.not.have.attribute('readonly'); - expect(sdtaCheck).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.have.attribute('readonly'); - expect(tosField).to.have.attribute('readonly'); - expect(tosRequireCheck).to.have.attribute('readonly'); - expect(tosStateCheck).to.have.attribute('readonly'); - expect(mailCheck).to.have.attribute('readonly'); - expect(sdtaCheck).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes consent', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const tosCheck = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const tosField = tosCheck.querySelector('[data-testid="consent-tos-field"]'); - const tosRequireCheck = tosCheck.querySelector('[data-testid="consent-tos-require-check"]'); - const tosStateCheck = tosCheck.querySelector('[data-testid="consent-tos-state-check"]'); - const mailCheck = await getByTestId(control, 'consent-mail-check'); - const sdtaCheck = await getByTestId(control, 'consent-sdta-check'); - - expect(tosCheck).to.have.attribute('readonly'); - expect(tosField).to.have.attribute('readonly'); - expect(tosRequireCheck).to.have.attribute('readonly'); - expect(tosStateCheck).to.have.attribute('readonly'); - expect(mailCheck).to.have.attribute('readonly'); - expect(sdtaCheck).to.have.attribute('readonly'); - }); - - it('writes to tos_checkbox_settings.usage property of parsed form.json value on change (general, true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.usage', 'required'); - }); - - it('writes to tos_checkbox_settings.usage property of parsed form.json value on change (general, false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.usage', 'none'); - }); - - it('writes to tos_checkbox_settings.usage property of parsed form.json value on change (require, true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-require-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.usage', 'required'); - }); - - it('writes to tos_checkbox_settings.usage property of parsed form.json value on change (require, false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-require-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.usage', 'optional'); - }); - - it('writes to tos_checkbox_settings.url property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const field = check.querySelector('[data-testid="consent-tos-field"]') as TextFieldElement; - - field.value = 'Test'; - field.dispatchEvent(new CustomEvent('input')); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.url', 'Test'); - }); - - it('writes to tos_checkbox_settings.initial_state property of parsed form.json value on change (true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-state-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.initial_state', 'checked'); - }); - - it('writes to tos_checkbox_settings.initial_state property of parsed form.json value on change (false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-state-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('tos_checkbox_settings.initial_state', 'unchecked'); - }); - - it('writes to newsletter_subscribe.usage property of parsed form.json value on change (true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-mail-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('newsletter_subscribe.usage', 'required'); - }); - - it('writes to newsletter_subscribe.usage property of parsed form.json value on change (false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-mail-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('newsletter_subscribe.usage', 'none'); - }); - - it('writes to eu_secure_data_transfer_consent.usage property of parsed form.json value on change (true)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-sdta-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('eu_secure_data_transfer_consent.usage', 'required'); - }); - - it('writes to eu_secure_data_transfer_consent.usage property of parsed form.json value on change (false)', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-sdta-check')) as Checkbox; - - check.checked = false; - check.dispatchEvent(new CheckboxChangeEvent(false)); - - const json = JSON.parse(element.form.json as string); - expect(json).to.have.nested.property('eu_secure_data_transfer_consent.usage', 'none'); - }); - - it('renders translatable tos checkbox label and explainer', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="display_tos_link"]`); - const explainer = check.querySelector(`foxy-i18n[key="display_tos_link_explainer"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - - it('renders translatable "require TOS" checkbox label', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-require-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="require_consent"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('renders translatable "checked by default" checkbox label', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-tos-state-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="checked_by_default"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('renders translatable newsletter checkbox label and explainer', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-mail-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="newsletter_subscribe"]`); - const explainer = check.querySelector(`foxy-i18n[key="newsletter_subscribe_explainer"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - - it('renders translatable EU SDTA checkbox label and explainer', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'consent')) as HTMLElement; - const check = (await getByTestId(control, 'consent-sdta-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="display_sdta"]`); - const explainer = check.querySelector(`foxy-i18n[key="display_sdta_explainer"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - }); - - describe('fields', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'fields')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'fields')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes fields', async () => { - const element = await fixture( - html`` - ); - - expect(await getByTestId(element, 'fields')).to.not.exist; - }); - - it('renders "fields:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'fields:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "fields:before" slot with template "fields:before" if available', async () => { - const type = 'fields:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "fields:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'fields:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "fields:after" slot with template "fields:after" if available', async () => { - const type = 'fields:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a group label with i18n key field_plural', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const label = await getByKey(control, 'field_plural'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - const options = { - cart_controls: ['enabled', 'disabled'], - coupon_entry: ['enabled', 'disabled'], - billing_first_name: ['default', 'optional', 'required', 'hidden'], - billing_last_name: ['default', 'optional', 'required', 'hidden'], - billing_company: ['default', 'optional', 'required', 'hidden'], - billing_tax_id: ['default', 'optional', 'required', 'hidden'], - billing_phone: ['default', 'optional', 'required', 'hidden'], - billing_address1: ['default', 'optional', 'required', 'hidden'], - billing_address2: ['default', 'optional', 'required', 'hidden'], - billing_city: ['default', 'optional', 'required', 'hidden'], - billing_region: ['default', 'optional', 'required', 'hidden'], - billing_postal_code: ['default', 'optional', 'required', 'hidden'], - billing_country: ['default', 'optional', 'required', 'hidden'], - }; - - Object.entries(options).map(([property, values]) => { - const key = property.replace('billing_', ''); - - it(`renders a label with i18n key ${key}`, async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const label = control.querySelector(`label foxy-i18n[key="${key}"]`) as I18n; - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it(`renders a select for ${property} field`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const select = (await getByTestId(control, `fields-${property}`)) as HTMLSelectElement; - const options = Array.from(select.options); - - values.forEach(value => { - const option = options.find(o => o.value === value); - expect(option, `must have an option for "${value}" value`).to.exist; - }); - }); - - values.forEach(value => { - it(`reflects a value of custom_checkout_field_requirements.${property} (${value})`, async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json); - - json.custom_checkout_field_requirements[property] = value; - data.json = JSON.stringify(json); - element.data = data; - - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const select = (await getByTestId(control, `fields-${property}`)) as HTMLSelectElement; - const option = select.options[select.selectedIndex]; - - expect(option).to.have.attribute('value', value); - }); - - it(`writes to custom_checkout_field_requirements.${property} on change (${value})`, async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const select = (await getByTestId(control, `fields-${property}`)) as HTMLSelectElement; - const option = Array.from(select.options).find(o => o.value === value)!; - - option.selected = true; - select.dispatchEvent(new Event('change')); - - const json = JSON.parse(element.form.json as string); - const path = `custom_checkout_field_requirements.${property}`; - - expect(json).to.have.nested.property(path, value); - }); - }); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.not.have.attribute('disabled')); - }); - - it('is disabled when the form is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.have.attribute('disabled')); - }); - - it('is disabled when disabledcontrols include fields', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.have.attribute('disabled')); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.not.have.attribute('readonly')); - }); - - it('is readonly when the form is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.have.attribute('readonly')); - }); - - it('is readonly when readonlycontrols include fields', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'fields')) as HTMLElement; - const selects = control.querySelectorAll('select'); - - selects.forEach(select => expect(select).to.have.attribute('readonly')); - }); - }); - - describe('google-analytics', () => { - it('is hidden by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'google-analytics')).to.not.exist; - }); - - it('is visible when Google Analytics is enabled', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - expect(await getByTestId(element, 'google-analytics')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - expect(await getByTestId(element, 'google-analytics')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes google-analytics', async () => { - const element = await fixture(html` - - `); - - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - expect(await getByTestId(element, 'google-analytics')).to.not.exist; - }); - - it('renders "google-analytics:before" slot when visible', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const slot = await getByName(element, 'google-analytics:before'); - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "google-analytics:before" slot with template "google-analytics:before" if available', async () => { - const type = 'google-analytics:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "google-analytics:after" slot when visible', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const slot = await getByName(element, 'google-analytics:after'); - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "google-analytics:after" slot with template "google-analytics:after" if available', async () => { - const type = 'google-analytics:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a field labelled with i18n key ga_account_id', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - - expect(field).to.have.attribute('label', 'ga_account_id'); - }); - - it('renders a field explained with i18n key ga_account_id_explainer', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - - expect(field).to.have.attribute('helper-text', 'ga_account_id_explainer'); - }); - - it('reflects the value of analytics_config.google_analytics.account_id from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - json.analytics_config.google_analytics.account_id = '123456'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - - expect(field).to.have.property('value', '123456'); - }); - - it('reflects the value of analytics_config.google_analytics.include_on_site from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - json.analytics_config.google_analytics.include_on_site = true; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const check = await getByTestId(control, 'google-analytics-check'); - - expect(check).to.have.attribute('checked'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.not.have.attribute('disabled'); - expect(check).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.have.attribute('disabled'); - expect(check).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes google-analytics', async () => { - const element = await fixture(html` - - `); - - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.have.attribute('disabled'); - expect(check).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.not.have.attribute('readonly'); - expect(check).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.have.attribute('readonly'); - expect(check).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes google-analytics', async () => { - const element = await fixture(html` - - `); - - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = await getByTestId(control, 'google-analytics-field'); - const check = await getByTestId(control, 'google-analytics-check'); - - expect(field).to.have.attribute('readonly'); - expect(check).to.have.attribute('readonly'); - }); - - it('writes to analytics_config.google_analytics.account_id property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = (await getByTestId(control, 'google-analytics-field')) as TextFieldElement; - - field.value = '123456'; - field.dispatchEvent(new CustomEvent('input')); - - const newJSON = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(newJSON).to.have.nested.property( - 'analytics_config.google_analytics.account_id', - '123456' - ); - - expect(newJSON).to.have.nested.property( - 'analytics_config.google_analytics.usage', - 'required' - ); - }); - - it('writes to analytics_config.google_analytics.include_on_site property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const check = (await getByTestId(control, 'google-analytics-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - expect(JSON.parse(element.form.json as string)).to.have.nested.property( - 'analytics_config.google_analytics.include_on_site', - true - ); - }); - - it('switches analytics_config.google_analytics.usage property of parsed form.json to none when empty', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - let json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - json.analytics_config.google_analytics.account_id = '123456'; - json.analytics_config.google_analytics.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const field = (await getByTestId(control, 'google-analytics-field')) as TextFieldElement; - - field.value = ''; - field.dispatchEvent(new CustomEvent('input')); - - json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('analytics_config.google_analytics.account_id', ''); - expect(json).to.have.nested.property('analytics_config.google_analytics.usage', 'none'); - }); - - it('renders translatable checkbox label and explainer', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - element.lang = 'es'; - element.ns = 'foo'; - - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const check = (await getByTestId(control, 'google-analytics-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="ga_include_on_site"]`); - const explainer = check.querySelector(`foxy-i18n[key="ga_include_on_site_explainer"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - - it('renders translatable deprecation notice', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_analytics.usage = 'required'; - data.json = JSON.stringify(json); - element.data = data; - await element.requestUpdate(); - - const control = (await getByTestId(element, 'google-analytics')) as HTMLElement; - const notice = control.querySelector(`foxy-i18n[key="ga_deprecation_notice"]`); - - expect(notice).to.exist; - expect(notice).to.have.attribute('infer', ''); - }); - }); - - describe('google-tag', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'google-tag')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'google-tag')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes google-tag', async () => { - const element = await fixture(html` - - `); - - expect(await getByTestId(element, 'google-tag')).to.not.exist; - }); - - it('renders "google-tag:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'google-tag:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "google-tag:before" slot with template "google-tag:before" if available', async () => { - const type = 'google-tag:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "google-tag:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'google-tag:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "google-tag:after" slot with template "google-tag:after" if available', async () => { - const type = 'google-tag:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a field labelled with i18n key gt_account_id', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - - expect(accountId).to.have.attribute('label', 'gt_account_id'); - }); - - it('renders a field explained with i18n key gt_account_id_explainer', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - - expect(accountId).to.have.attribute('helper-text', 'gt_account_id_explainer'); - }); - - it('renders a field labelled with i18n key gt_send_to', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('label', 'gt_send_to'); - }); - - it('renders a field explained with i18n key gt_send_to_explainer', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('helper-text', 'gt_send_to_explainer'); - }); - - it('reflects the value of analytics_config.google_tag.account_id from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.analytics_config.google_tag.account_id = '123456'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - - expect(accountId).to.have.property('value', '123456'); - }); - - it('reflects the value of analytics_config.google_tag.send_to from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.analytics_config.google_tag.send_to = '123456'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.property('value', '123456'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.not.have.attribute('disabled'); - expect(sendTo).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('disabled'); - expect(sendTo).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes google-tag', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('disabled'); - expect(sendTo).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.not.have.attribute('readonly'); - expect(sendTo).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('readonly'); - expect(sendTo).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes google-tag', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = await getByTestId(control, 'google-tag-account-id'); - const sendTo = await getByTestId(control, 'google-tag-send-to'); - - expect(accountId).to.have.attribute('readonly'); - expect(sendTo).to.have.attribute('readonly'); - }); - - it('writes to analytics_config.google_tag.account_id property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = (await getByTestId(control, 'google-tag-account-id')) as TextFieldElement; - - accountId.value = '123456'; - accountId.dispatchEvent(new CustomEvent('input')); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.nested.property('analytics_config.google_tag.account_id', '123456'); - - expect(json).to.have.nested.property('analytics_config.google_tag.usage', 'required'); - }); - - it('writes to analytics_config.google_tag.send_to property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = (await getByTestId(control, 'google-tag-send-to')) as TextFieldElement; - - accountId.value = '123456'; - accountId.dispatchEvent(new CustomEvent('input')); - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - - expect(json).to.have.nested.property('analytics_config.google_tag.send_to', '123456'); - }); - - it('switches analytics_config.google_tag.usage property of parsed form.json to none when empty', async () => { - const layout = html``; - const element = await fixture(layout); - - const data = await getTestData('./hapi/template_configs/0'); - let json = JSON.parse(data.json) as TemplateConfigJSON; - - json.analytics_config.google_tag.account_id = '123456'; - json.analytics_config.google_tag.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const accountId = (await getByTestId(control, 'google-tag-account-id')) as TextFieldElement; - - accountId.value = ''; - accountId.dispatchEvent(new CustomEvent('input')); - - json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('analytics_config.google_tag.account_id', ''); - expect(json).to.have.nested.property('analytics_config.google_tag.usage', 'none'); - }); - - it('renders translatable usage notice', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const notice = control.querySelector(`foxy-i18n[key="gt_usage_notice"]`); - - expect(notice).to.exist; - expect(notice).to.have.attribute('infer', ''); - }); - - it('renders translatable docs link', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'google-tag')) as HTMLElement; - const linkText = control.querySelector(`foxy-i18n[key="gt_docs_link"]`); - const link = linkText?.closest('a'); - - expect(link).to.exist; - expect(link).to.have.attribute( - 'href', - 'https://wiki.foxycart.com/v/2.0/analytics#google_tag_ga4_google_ads' - ); - - expect(linkText).to.exist; - expect(linkText).to.have.attribute('infer', ''); - }); - }); - - describe('troubleshooting', () => { - it('is visible by default', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'troubleshooting')).to.exist; - }); - - it('is hidden when form is hidden', async () => { - const layout = html``; - const element = await fixture(layout); - expect(await getByTestId(element, 'troubleshooting')).to.not.exist; - }); - - it('is hidden when hiddencontrols includes troubleshooting', async () => { - const element = await fixture(html` - - `); - - expect(await getByTestId(element, 'troubleshooting')).to.not.exist; - }); - - it('renders "troubleshooting:before" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'troubleshooting:before'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "troubleshooting:before" slot with template "troubleshooting:before" if available', async () => { - const type = 'troubleshooting:before'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders "troubleshooting:after" slot by default', async () => { - const layout = html``; - const element = await fixture(layout); - const slot = await getByName(element, 'troubleshooting:after'); - - expect(slot).to.be.instanceOf(HTMLSlotElement); - }); - - it('replaces "troubleshooting:after" slot with template "troubleshooting:after" if available', async () => { - const type = 'troubleshooting:after'; - const value = `

Value of the "${type}" template.

`; - const element = await fixture(html` - - - - `); - - const slot = await getByName(element, type); - const sandbox = (await getByTestId(element, type))!.renderRoot; - - expect(slot).to.not.exist; - expect(sandbox).to.contain.html(value); - }); - - it('renders a label with i18n key troubleshooting', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const label = await getByKey(control, 'troubleshooting'); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - }); - - it('reflects the value of debug.usage from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.debug.usage = 'required'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.have.attribute('checked'); - }); - - it('is enabled by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.not.have.attribute('disabled'); - }); - - it('is disabled when element is disabled', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.have.attribute('disabled'); - }); - - it('is disabled when disabledcontrols includes troubleshooting', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.have.attribute('disabled'); - }); - - it('is editable by default', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.not.have.attribute('readonly'); - }); - - it('is readonly when element is readonly', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.have.attribute('readonly'); - }); - - it('is readonly when readonlycontrols includes troubleshooting', async () => { - const element = await fixture(html` - - `); - - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = await getByTestId(control, 'troubleshooting-check'); - - expect(check).to.have.attribute('readonly'); - }); - - it('writes to debug.usage property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = (await getByTestId(control, 'troubleshooting-check')) as Checkbox; - - check.checked = true; - check.dispatchEvent(new CheckboxChangeEvent(true)); - - expect(JSON.parse(element.form.json as string)).to.have.nested.property( - 'debug.usage', - 'required' - ); - }); - - it('renders translatable checkbox label and explainer', async () => { - const layout = html``; - const element = await fixture(layout); - - element.lang = 'es'; - element.ns = 'foo'; - - const control = (await getByTestId(element, 'troubleshooting')) as HTMLElement; - const check = (await getByTestId(control, 'troubleshooting-check')) as Checkbox; - const label = check.querySelector(`foxy-i18n[key="troubleshooting_debug"]`); - const explainer = check.querySelector(`foxy-i18n[key="troubleshooting_debug_explainer"]`); - - expect(label).to.exist; - expect(label).to.have.attribute('lang', 'es'); - expect(label).to.have.attribute('ns', 'foo'); - - expect(explainer).to.exist; - expect(explainer).to.have.attribute('lang', 'es'); - expect(explainer).to.have.attribute('ns', 'foo'); - }); - }); - - describe('custom-config', () => { - it('renders a source control', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'custom-config')) as InternalSourceControl; - - expect(control).to.be.instanceOf(InternalSourceControl); - expect(control).to.have.attribute('infer', 'custom-config'); - expect(control).to.have.attribute('label', 'custom_config'); - expect(control).to.have.attribute('helper-text', 'custom_config_helper_text'); - }); - - it('reflects the value of custom_config from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.custom_config = { foo: 'bar' }; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'custom-config')) as InternalSourceControl; - expect(control.getValue()).to.equal(JSON.stringify({ foo: 'bar' }, null, 2)); - }); - - it('writes to custom_config property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'custom-config')) as InternalSourceControl; - - control.setValue(JSON.stringify({ foo: 'bar' })); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.deep.property('custom_config', { foo: 'bar' }); - }); - }); - - describe('header', () => { - it('renders a source control', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'header')) as InternalSourceControl; - - expect(control).to.be.instanceOf(InternalSourceControl); - expect(control).to.have.attribute('infer', 'header'); - expect(control).to.have.attribute('label', 'custom_header'); - expect(control).to.have.attribute('helper-text', 'custom_header_helper_text'); - }); - - it('reflects the value of custom_script_values.header from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.custom_script_values.header = 'Test'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'header')) as InternalSourceControl; - expect(control.getValue()).to.equal('Test'); - }); - - it('writes to custom_script_values.header property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'header')) as InternalSourceControl; - - control.setValue('Test'); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('custom_script_values.header', 'Test'); - }); - }); - - describe('custom-fields', () => { - it('renders a source control', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'custom-fields')) as InternalSourceControl; - - expect(control).to.be.instanceOf(InternalSourceControl); - expect(control).to.have.attribute('infer', 'custom-fields'); - expect(control).to.have.attribute('label', 'custom_fields'); - expect(control).to.have.attribute('helper-text', 'custom_fields_helper_text'); - }); - - it('reflects the value of custom_script_values.checkout_fields from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.custom_script_values.checkout_fields = 'Test'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'custom-fields')) as InternalSourceControl; - expect(control.getValue()).to.equal('Test'); - }); - - it('writes to custom_script_values.checkout_fields property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'custom-fields')) as InternalSourceControl; - - control.setValue('Test'); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('custom_script_values.checkout_fields', 'Test'); - }); - }); - - describe('footer', () => { - it('renders a source control', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'footer')) as InternalSourceControl; - - expect(control).to.be.instanceOf(InternalSourceControl); - expect(control).to.have.attribute('infer', 'footer'); - expect(control).to.have.attribute('label', 'custom_footer'); - expect(control).to.have.attribute('helper-text', 'custom_footer_helper_text'); - }); - - it('reflects the value of custom_script_values.footer from parsed form.json', async () => { - const layout = html``; - const element = await fixture(layout); - const data = await getTestData('./hapi/template_configs/0'); - const json = JSON.parse(data.json) as TemplateConfigJSON; - - element.data = data; - json.custom_script_values.footer = 'Test'; - element.edit({ json: JSON.stringify(json) }); - - const control = (await getByTestId(element, 'footer')) as InternalSourceControl; - expect(control.getValue()).to.equal('Test'); - }); - - it('writes to custom_script_values.footer property of parsed form.json value on change', async () => { - const layout = html``; - const element = await fixture(layout); - const control = (await getByTestId(element, 'footer')) as InternalSourceControl; - - control.setValue('Test'); - - const json = JSON.parse(element.form.json as string) as TemplateConfigJSON; - expect(json).to.have.nested.property('custom_script_values.footer', 'Test'); - }); + it('renders source control for custom footer markup', async () => { + const layout = html``; + const element = await fixture(layout); + const control = element.renderRoot.querySelector( + 'foxy-internal-source-control[infer="custom-script-values-footer"]' + ); + + expect(control).to.exist; + expect(control).to.have.attribute('json-template', JSON.stringify(getDefaultJSON())); + expect(control).to.have.attribute('json-path', 'custom_script_values.footer'); + expect(control).to.have.attribute('property', 'json'); }); }); diff --git a/src/elements/public/TemplateConfigForm/TemplateConfigForm.ts b/src/elements/public/TemplateConfigForm/TemplateConfigForm.ts index d1e693160..b983bb4f7 100644 --- a/src/elements/public/TemplateConfigForm/TemplateConfigForm.ts +++ b/src/elements/public/TemplateConfigForm/TemplateConfigForm.ts @@ -1,1349 +1,806 @@ -import * as logos from '../PaymentMethodCard/logos'; - -import { CheckboxChangeEvent, ChoiceChangeEvent } from '../../private/events'; -import { Data, TemplateConfigJSON } from './types'; -import { ScopedElementsMap, ScopedElementsMixin } from '@open-wc/scoped-elements'; -import { TemplateResult, html } from 'lit-html'; - -import { Checkbox } from '../../private/Checkbox/Checkbox'; -import { Choice } from '../../private/Choice/Choice'; -import { ConfigurableMixin } from '../../../mixins/configurable'; -import { CountriesList } from './CountriesList'; -import { Group } from '../../private/Group/Group'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; -import { PropertyDeclarations } from 'lit-element'; -import { ResponsiveMixin } from '../../../mixins/responsive'; -import { TextFieldElement } from '@vaadin/vaadin-text-field/vaadin-text-field'; -import { ThemeableMixin } from '../../../mixins/themeable'; +import type { Data, TemplateConfigJSON } from './types'; +import type { PropertyDeclarations } from 'lit-element'; +import type { TemplateResult } from 'lit-html'; +import type { NucleonElement } from '../NucleonElement/NucleonElement'; +import type { Resource } from '@foxy.io/sdk/core'; +import type { Rels } from '@foxy.io/sdk/backend'; + import { TranslatableMixin } from '../../../mixins/translatable'; -import { classMap } from '../../../utils/class-map'; -import { getDefaultJSON } from './defaults'; -import { live } from 'lit-html/directives/live'; import { BooleanSelector } from '@foxy.io/sdk/core'; +import { ResponsiveMixin } from '../../../mixins/responsive'; +import { getDefaultJSON } from './defaults'; +import { InternalForm } from '../../internal/InternalForm/InternalForm'; +import { ifDefined } from 'lit-html/directives/if-defined'; +import { html, svg } from 'lit-html'; const NS = 'template-config-form'; -const Base = ScopedElementsMixin( - ResponsiveMixin(ConfigurableMixin(ThemeableMixin(TranslatableMixin(NucleonElement, NS)))) -); +const Base = ResponsiveMixin(TranslatableMixin(InternalForm, NS)); /** * Form element for creating or editing template configs (`fx:template_config`). * - * * @element foxy-template-config-form * @since 1.14.0 */ export class TemplateConfigForm extends Base { - static get scopedElements(): ScopedElementsMap { - return { - 'vaadin-text-field': customElements.get('vaadin-text-field'), - 'iron-icon': customElements.get('iron-icon'), - 'foxy-internal-source-control': customElements.get('foxy-internal-source-control'), - 'foxy-internal-sandbox': customElements.get('foxy-internal-sandbox'), - 'foxy-spinner': customElements.get('foxy-spinner'), - 'foxy-i18n': customElements.get('foxy-i18n'), - 'x-countries-list': CountriesList, - 'x-checkbox': Checkbox, - 'x-choice': Choice, - 'x-group': Group, - }; - } - static get properties(): PropertyDeclarations { return { ...super.properties, - __addHiddenFieldInputValue: { attribute: false }, - countries: { type: String }, - regions: { type: String }, + countries: {}, + regions: {}, + store: {}, }; } /** URI of the `fx:countries` hAPI resource. */ - countries = ''; + countries: string | null = null; /** URI of the `fx:regions` hAPI resource. */ - regions = ''; + regions: string | null = null; + + /** URI of the `fx:store` hAPI resource related to this template config. If this property is `null`, a link relationship will be used when available. */ + store: string | null = null; + + private readonly __customCheckoutFieldCouponEntryRequirementsOptions = JSON.stringify([ + { label: 'option_enabled', value: 'enabled' }, + { label: 'option_disabled', value: 'disabled' }, + ]); + + private readonly __customCheckoutFieldRequirementsOptions = JSON.stringify([ + { label: 'option_default', value: 'default' }, + { label: 'option_optional', value: 'optional' }, + { label: 'option_required', value: 'required' }, + { label: 'option_hidden', value: 'hidden' }, + ]); + + private readonly __tosCheckboxSettingsInitialStateOptions = JSON.stringify([ + { label: 'option_checked', value: 'checked' }, + { label: 'option_unchecked', value: 'unchecked' }, + ]); + + private readonly __locationFilteringFilterTypeOptions = JSON.stringify([ + { label: 'option_blocklist', value: 'blacklist' }, + { label: 'option_allowlist', value: 'whitelist' }, + ]); + + private readonly __foxycompleteShowComboboxOptions = JSON.stringify([ + { label: 'option_combobox', value: 'combobox' }, + { label: 'option_search', value: 'search' }, + ]); + + private readonly __tosCheckboxSettingsUsageOptions = JSON.stringify([ + { label: 'option_none', value: 'none' }, + { label: 'option_optional', value: 'optional' }, + { label: 'option_required', value: 'required' }, + ]); + + private readonly __locationFilteringUsageOptions = JSON.stringify([ + { label: 'option_none', value: 'none' }, + { label: 'option_shipping', value: 'shipping' }, + { label: 'option_billing', value: 'billing' }, + { label: 'option_both', value: 'both' }, + { label: 'option_independent', value: 'independent' }, + ]); + + private readonly __cscRequirementsValueOptions = JSON.stringify([ + { label: 'option_all_cards', value: 'all_cards' }, + { label: 'option_sso_only', value: 'sso_only' }, + { label: 'option_new_cards_only', value: 'new_cards_only' }, + ]); + + private readonly __checkoutTypeDefaultOptions = JSON.stringify([ + { label: 'option_default_account', value: 'default_account' }, + { label: 'option_default_guest', value: 'default_guest' }, + ]); + + private readonly __checkoutTypeAccountOptions = JSON.stringify([ + { label: 'option_account', value: 'account_only' }, + { label: 'option_guest', value: 'guest_only' }, + { label: 'option_both', value: 'default_guest' }, + ]); + + private readonly __cartTypeOptions = JSON.stringify([ + { label: 'option_default', value: 'default' }, + { label: 'option_fullpage', value: 'fullpage' }, + { label: 'option_custom', value: 'custom' }, + ]); + + private readonly __locationFilteringFilterValuesSetValue = ( + newValue: Record + ) => { + const formJson = this.__formJson; + const newConfig = { + ...formJson.location_filtering, + shipping_filter_values: newValue, + billing_filter_values: newValue, + }; - private __addHiddenFieldInputValue = ''; + this.edit({ json: JSON.stringify({ ...formJson, location_filtering: newConfig }) }); + }; - render(): TemplateResult { - const hidden = this.hiddenSelector; - const json: TemplateConfigJSON = this.form.json ? JSON.parse(this.form.json) : getDefaultJSON(); + private readonly __locationFilteringFilterTypeSetValue = (value: string) => { + const formJson = this.__formJson; + const newValue = { + ...formJson.location_filtering, + shipping_filter_type: value, + billing_filter_type: value, + }; - return html` -
-
- ${hidden.matches('cart-type', true) ? '' : this.__renderCartType(json)} - ${hidden.matches('foxycomplete', true) ? '' : this.__renderFoxycomplete(json)} - ${hidden.matches('locations', true) ? '' : this.__renderLocations(json)} - ${hidden.matches('hidden-fields', true) ? '' : this.__renderHiddenFields(json)} - ${hidden.matches('cards', true) ? '' : this.__renderCards(json)} - ${hidden.matches('checkout-type', true) ? '' : this.__renderCheckoutType(json)} - ${hidden.matches('consent', true) ? '' : this.__renderConsent(json)} - ${hidden.matches('fields', true) ? '' : this.__renderFields(json)} - ${hidden.matches('google-analytics', true) ? '' : this.__renderGoogleAnalytics(json)} - ${hidden.matches('google-tag', true) ? '' : this.__renderGoogleTag(json)} - ${hidden.matches('troubleshooting', true) ? '' : this.__renderTroubleshooting(json)} - - { - if (typeof json.custom_config === 'string') return json.custom_config; - return JSON.stringify(json.custom_config, null, 2); - }} - .setValue=${(newValue: string) => { - try { - json.custom_config = JSON.parse(newValue); - } catch { - json.custom_config = newValue; - } - - this.edit({ json: JSON.stringify(json) }); - }} - > - - - json.custom_script_values.header} - .setValue=${(newValue: string) => { - json.custom_script_values.header = newValue; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - json.custom_script_values.checkout_fields} - .setValue=${(newValue: string) => { - json.custom_script_values.checkout_fields = newValue; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - json.custom_script_values.footer} - .setValue=${(newValue: string) => { - json.custom_script_values.footer = newValue; - this.edit({ json: JSON.stringify(json) }); - }} - > - -
- -
- - -
-
- `; - } + this.edit({ json: JSON.stringify({ ...formJson, location_filtering: newValue }) }); + }; + + private readonly __foxycompleteShowComboboxGetValue = () => { + return this.__formJson.foxycomplete.show_combobox ? 'combobox' : 'search'; + }; + + private readonly __foxycompleteShowComboboxSetValue = (value: string) => { + const newValue = value === 'combobox'; + const foxycomplete = { ...this.__formJson.foxycomplete, show_combobox: newValue }; + this.edit({ json: JSON.stringify({ ...this.__formJson, foxycomplete }) }); + }; + + private readonly __checkoutTypeAccountGetValue = () => { + const formJson = this.__formJson; + return formJson.checkout_type.includes('default') ? 'default_guest' : formJson.checkout_type; + }; + + private readonly __customConfigGetValue = () => { + const formJson = this.__formJson; + if (typeof formJson.custom_config === 'string') return formJson.custom_config; + return JSON.stringify(formJson.custom_config, null, 2); + }; + + private readonly __customConfigSetValue = (newValue: string) => { + const formJson = this.__formJson; + + try { + formJson.custom_config = JSON.parse(newValue); + } catch { + formJson.custom_config = newValue; + } + + this.edit({ json: JSON.stringify(formJson) }); + }; + + private readonly __defaultJSON = JSON.stringify(getDefaultJSON()); get hiddenSelector(): BooleanSelector { const alwaysHidden = [super.hiddenSelector.toString()]; - const json: TemplateConfigJSON = this.data?.json ? JSON.parse(this.data.json) : null; - if (!json || json.analytics_config.google_analytics.usage === 'none') { - alwaysHidden.push('google-analytics'); - } + const dataJson = this.__dataJson; + const formJson = this.__formJson; - return new BooleanSelector(alwaysHidden.join(' ').trim()); - } + const locationFilteringUsage = formJson.location_filtering.usage; + const checkoutType = formJson.checkout_type; - private __renderCartType(json: TemplateConfigJSON) { - const { lang, ns } = this; - const items = ['default', 'fullpage', 'custom']; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('cart-type', true); + if (formJson.cart_display_config.usage !== 'required') { + alwaysHidden.unshift('cart-group-two', 'cart-group-three', 'cart-group-four'); + } - return html` -
- ${this.renderTemplateOrSlot('cart-type:before')} - - - - - - { - this.edit({ json: JSON.stringify({ ...json, cart_type: evt.detail }) }); - }} - > - ${items.map(item => { - return html` -
- - - -
- `; - })} -
-
- - ${this.renderTemplateOrSlot('cart-type:after')} -
- `; - } + if (formJson.foxycomplete.usage !== 'required') { + alwaysHidden.unshift( + 'country-and-region-group-one:foxycomplete-show-combobox', + 'country-and-region-group-one:foxycomplete-combobox-open', + 'country-and-region-group-one:foxycomplete-combobox-close', + 'country-and-region-group-one:foxycomplete-show-flags' + ); + } - private __renderFoxycomplete(json: TemplateConfigJSON) { - const { lang, ns } = this; - - const isDisabled = !this.in('idle') || this.disabledSelector.matches('foxycomplete', true); - const isReadonly = this.readonlySelector.matches('foxycomplete', true); - const config = json.foxycomplete; - const items = ['combobox', 'search', 'disabled']; - - const value = - config.usage === 'none' ? 'disabled' : config.show_combobox ? 'combobox' : 'search'; - - const flagsCheckbox = html` - { - config.show_flags = evt.detail; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - `; + if (formJson.foxycomplete.show_combobox === false) { + alwaysHidden.unshift( + 'country-and-region-group-one:foxycomplete-combobox-open', + 'country-and-region-group-one:foxycomplete-combobox-close' + ); + } - return html` -
- ${this.renderTemplateOrSlot('foxycomplete:before')} - - - - - - { - if (!(evt instanceof ChoiceChangeEvent)) return; - config.usage = evt.detail === 'disabled' ? 'none' : 'required'; - config.show_combobox = evt.detail === 'combobox'; - this.edit({ json: JSON.stringify(json) }); - }} - > - ${items.map(item => { - return html` -
- - - -
- `; - })} - -
-
- ${['open', 'close'].map(action => { - const field = action === 'open' ? 'combobox_open' : 'combobox_close'; - - return html` - evt.key === 'Enter' && this.submit()} - @input=${(evt: CustomEvent) => { - config[field] = (evt.currentTarget as TextFieldElement).value; - this.edit({ json: JSON.stringify(json) }); - }} - > - - `; - })} -
- - ${flagsCheckbox} -
- -
${flagsCheckbox}
-
- -
- { - json.postal_code_lookup.usage = evt.detail ? 'enabled' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - -
-
+ if (locationFilteringUsage !== 'both') { + alwaysHidden.unshift('country-and-region-group-two'); + } - ${this.renderTemplateOrSlot('foxycomplete:after')} -
- `; - } + if (locationFilteringUsage !== 'shipping' && locationFilteringUsage !== 'independent') { + alwaysHidden.unshift('country-and-region-group-three'); + } - private __renderLocations(json: TemplateConfigJSON) { - const { lang, ns } = this; - const config = json.location_filtering; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('locations', true); - const isReadonly = this.readonlySelector.matches('locations', true); - - const shippingChoice = config.shipping_filter_type === 'blacklist' ? 'block' : 'allow'; - const billingChoice = - config.usage === 'both' - ? 'copy' - : config.billing_filter_type === 'blacklist' - ? 'block' - : 'allow'; - - const normalize = () => { - if (config.usage === 'both') { - config.billing_filter_type = config.shipping_filter_type; - config.billing_filter_values = config.shipping_filter_values; - } else { - const hasBillingFilters = Object.keys(config.billing_filter_values).length > 0; - const hasShippingFilters = Object.keys(config.shipping_filter_values).length > 0; - - if (!hasBillingFilters && !hasShippingFilters) { - config.usage = 'none'; - } else if (hasBillingFilters && !hasShippingFilters) { - config.usage = 'billing'; - } else if (hasShippingFilters && !hasBillingFilters) { - config.usage = 'shipping'; - } else { - config.usage = 'independent'; - } - } - }; + if (locationFilteringUsage !== 'billing' && locationFilteringUsage !== 'independent') { + alwaysHidden.unshift('country-and-region-group-four'); + } - return html` -
- ${this.renderTemplateOrSlot('locations:before')} - - - - - -
- - - + if (checkoutType !== 'default_account' && checkoutType !== 'default_guest') { + alwaysHidden.unshift('customer-accounts:checkout-type-default'); + } - { - if (config.usage !== 'both') config.usage = 'independent'; - config.shipping_filter_type = evt.detail === 'block' ? 'blacklist' : 'whitelist'; - normalize(); - this.edit({ json: JSON.stringify(json) }); - }} - > - - - - { - config.shipping_filter_values = (evt.currentTarget as CountriesList).countries; - normalize(); - this.edit({ json: JSON.stringify(json) }); - }} - > - - - - - - - + if (formJson.tos_checkbox_settings.usage === 'none') { + alwaysHidden.unshift( + 'consent-group-one:tos-checkbox-settings-url', + 'consent-group-one:tos-checkbox-settings-initial-state', + 'consent-group-one:tos-checkbox-settings-is-hidden' + ); + } - { - if (evt.detail === 'copy') { - config.usage = 'both'; - } else { - config.usage = 'independent'; - config.billing_filter_type = evt.detail === 'block' ? 'blacklist' : 'whitelist'; - } - - normalize(); - this.edit({ json: JSON.stringify(json) }); - }} - > - - - - - - { - config.billing_filter_values = (evt.currentTarget as CountriesList).countries; - normalize(); - this.edit({ json: JSON.stringify(json) }); - }} - > - - - -
-
- - ${this.renderTemplateOrSlot('locations:after')} -
- `; - } + if (formJson.analytics_config.usage !== 'required') { + alwaysHidden.unshift('analytics-config-google-analytics', 'analytics-config-google-tag'); + } - private __renderHiddenFields(json: TemplateConfigJSON) { - const { lang, ns } = this; - const suggestions = [] as string[]; - const fields = [] as string[]; - const config = json.cart_display_config; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('hidden-fields', true); - const isReadonly = this.readonlySelector.matches('hidden-fields', true); - - type FieldName = keyof Omit< - TemplateConfigJSON['cart_display_config'], - 'hidden_product_options' | 'usage' - >; - - for (const key in config) { - if (!key.startsWith('show_')) continue; - const field = key.substring(5); - suggestions.push(field); - if (config.usage === 'required' && !config[key as FieldName]) fields.push(field); + if (dataJson?.analytics_config.google_analytics.usage !== 'required') { + alwaysHidden.unshift('analytics-config-google-analytics'); } - if (config.usage === 'required') { - fields.push(...config.hidden_product_options); + if (formJson.analytics_config.google_analytics.usage !== 'required') { + alwaysHidden.unshift( + 'analytics-config-google-analytics:analytics-config-google-analytics-account-id', + 'analytics-config-google-analytics:analytics-config-google-analytics-include-on-site' + ); } - const addField = () => { - config.usage = 'required'; + if (formJson.analytics_config.google_tag.usage !== 'required') { + alwaysHidden.unshift('analytics-config-google-tag:analytics-config-google-tag-account-id'); + alwaysHidden.unshift('analytics-config-google-tag:analytics-config-google-tag-send-to'); + } - if (suggestions.includes(this.__addHiddenFieldInputValue)) { - config[`show_${this.__addHiddenFieldInputValue}` as FieldName] = false; - } else if (!config.hidden_product_options.includes(this.__addHiddenFieldInputValue)) { - config.hidden_product_options.push(this.__addHiddenFieldInputValue); - } + if (this.__storeLoader?.data?.features_multiship !== true) { + alwaysHidden.unshift('custom-script-values-multiship-checkout-fields'); + } - this.edit({ json: JSON.stringify(json) }); - this.__addHiddenFieldInputValue = ''; - }; + return new BooleanSelector(alwaysHidden.join(' ').trim()); + } - const radius = 'calc(var(--lumo-border-radius-l) / 1.2)'; - const inputRadius = fields.length === 0 ? [radius] : ['0', '0', radius, radius]; - const isAddButtonDisabled = isDisabled || !this.__addHiddenFieldInputValue; + renderBody(): TemplateResult { + const customCheckoutFieldRequirementsControls = [ + [ + 'billing_address1', + 'billing_address2', + 'billing_city', + 'billing_region', + 'billing_postal_code', + 'billing_country', + ], + [ + 'billing_first_name', + 'billing_last_name', + 'billing_company', + 'billing_tax_id', + 'billing_phone', + 'coupon_entry', + ], + ]; + + const cartDisplayConfigFields = [ + ['show_product_weight', 'show_product_category', 'show_product_code', 'show_product_options'], + ['show_sub_frequency', 'show_sub_startdate', 'show_sub_nextdate', 'show_sub_enddate'], + ]; return html` -
- ${this.renderTemplateOrSlot('hidden-fields:before')} - - - - - -
- ${fields.map(field => { - return html` -
- ${suggestions.includes(field) - ? html`` - : html`${field}`} - - -
- `; - })} -
- -
0, - 'flex': !isReadonly, - 'hidden': isReadonly, - })} - > - evt.key === 'Enter' && addField()} - @input=${(evt: InputEvent) => { - this.__addHiddenFieldInputValue = (evt.currentTarget as HTMLInputElement).value; - }} - /> - - - ${suggestions - .filter(suggestion => !fields.includes(suggestion)) - .map( - suggestion => html`` - )} - - - -
-
- - ${this.renderTemplateOrSlot('hidden-fields:after')} + + `; + })}
- `; - } - private __renderCards(json: TemplateConfigJSON) { - const { lang, ns } = this; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('cards', true); - const isReadonly = this.readonlySelector.matches('cards', true); - const config = json.supported_payment_cards as string[]; - - let skipForSaved: boolean; - let skipForSSO: boolean; - - if (json.csc_requirements === 'all_cards') { - skipForSaved = false; - skipForSSO = false; - } else if (json.csc_requirements === 'sso_only') { - skipForSaved = true; - skipForSSO = false; - } else { - skipForSaved = true; - skipForSSO = true; - } - - const typeToName: Record = { - amex: 'American Express', - diners: 'Diners Club', - discover: 'Discover', - jcb: 'JCB', - maestro: 'Maestro', - mastercard: 'Mastercard', - unionpay: 'UnionPay', - visa: 'Visa', - }; - - return html` -
- ${this.renderTemplateOrSlot('cards:before')} - -
- - - + + + + + + + + + + + + + + + + + -
- ${Object.entries(logos).map(([type, logo]) => { - if (!typeToName[type]) return; - const isChecked = config.includes(type); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ ${svg``} + +

+
+ + + + + + + + + + + +
{ + return group.every(prop => { + const suffix = index === 0 ? 'two' : 'three'; + const infer = prop.replace(/_/g, '-').replace('1', '-one').replace('2', '-two'); + const sel = `checkout-fields-group-${suffix}:custom-checkout-field-requirements-${infer}`; + return this.hiddenSelector.matches(sel, true); + }); + })} + > + ${customCheckoutFieldRequirementsControls.map((group, index) => { + const suffix = index === 0 ? 'two' : 'three'; + return html` + + ${group.map(prop => { + const infer = prop.replace(/_/g, '-').replace('1', '-one').replace('2', '-two'); + const path = `custom_checkout_field_requirements.${prop}`; return html` -
- -
+ `; })} -
- -
- { - json.csc_requirements = evt.detail ? 'sso_only' : 'all_cards'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - - - { - json.csc_requirements = evt.detail - ? 'new_cards_only' - : skipForSaved - ? 'sso_only' - : 'all_cards'; - - this.edit({ json: JSON.stringify(json) }); - }} - > - - - -
- - - - -
- - ${this.renderTemplateOrSlot('cards:after')} +
+ `; + })}
- `; - } - private __renderCheckoutType(json: TemplateConfigJSON) { - const { lang, ns } = this; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('checkout-type', true); - const isReadonly = this.readonlySelector.matches('checkout-type', true); + + + + + + + + + + + - return html` -
- ${this.renderTemplateOrSlot('checkout-type:before')} - - - - ${this.renderTemplateOrSlot('checkout-type:after')} -
- `; - } - - private __renderConsent(json: TemplateConfigJSON) { - const { lang, ns } = this; - const tosConfig = json.tos_checkbox_settings; - const mailConfig = json.newsletter_subscribe; - const sdtaConfig = json.eu_secure_data_transfer_consent; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('consent', true); - const isReadonly = this.readonlySelector.matches('consent', true); - const dividerStyle = 'margin-left: calc(1.125rem + (var(--lumo-space-m) * 2))'; - - return html` -
- ${this.renderTemplateOrSlot('consent:before')} - - - - - - { - tosConfig.initial_state = evt.detail ? tosConfig.initial_state : 'unchecked'; - tosConfig.is_hidden = false; - tosConfig.usage = evt.detail ? 'required' : 'none'; - tosConfig.url = evt.detail ? tosConfig.url : ''; - - this.edit({ json: JSON.stringify(json) }); - }} - > -
- - - -
- -
- { - tosConfig.url = (evt.currentTarget as TextFieldElement).value; - this.edit({ json: JSON.stringify(json) }); - }} - > - - -
- { - tosConfig.usage = evt.detail ? 'required' : 'optional'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - - - { - tosConfig.initial_state = evt.detail ? 'checked' : 'unchecked'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - -
-
-
- -
- - { - mailConfig.usage = evt.detail ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > -
- - - -
-
- -
- - { - sdtaConfig.usage = evt.detail ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > -
- - + -
-
-
+
+ +

+ + + + + + - ${this.renderTemplateOrSlot('consent:after')} -
- `; - } + + - private __renderFields(json: TemplateConfigJSON) { - const { lang, ns } = this; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('fields', true); - const isReadonly = this.readonlySelector.matches('fields', true); - const config = json.custom_checkout_field_requirements; - const options = { - cart_controls: ['enabled', 'disabled'], - coupon_entry: ['enabled', 'disabled'], - billing_first_name: ['default', 'optional', 'required', 'hidden'], - billing_last_name: ['default', 'optional', 'required', 'hidden'], - billing_company: ['default', 'optional', 'required', 'hidden'], - billing_tax_id: ['default', 'optional', 'required', 'hidden'], - billing_phone: ['default', 'optional', 'required', 'hidden'], - billing_address1: ['default', 'optional', 'required', 'hidden'], - billing_address2: ['default', 'optional', 'required', 'hidden'], - billing_city: ['default', 'optional', 'required', 'hidden'], - billing_region: ['default', 'optional', 'required', 'hidden'], - billing_postal_code: ['default', 'optional', 'required', 'hidden'], - billing_country: ['default', 'optional', 'required', 'hidden'], - }; + + - return html` -
- ${this.renderTemplateOrSlot('fields:before')} - - - - - -
- ${Object.entries(options).map(([property, values]) => { - return html` - - `; - })} - - -
-
- - ${this.renderTemplateOrSlot('fields:after')} -
- `; - } + + - private __renderGoogleAnalytics(json: TemplateConfigJSON) { - const { lang, ns } = this; - const config = json.analytics_config; - const gtConfig = config.google_tag; - const gaConfig = config.google_analytics; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('google-analytics', true); - const isReadonly = this.readonlySelector.matches('google-analytics', true); + + - return html` -
- ${this.renderTemplateOrSlot('google-analytics:before')} - - - Google Analytics - - - - -
- evt.key === 'Enter' && this.submit()} - @input=${(evt: InputEvent) => { - gaConfig.account_id = (evt.currentTarget as TextFieldElement).value; - gaConfig.usage = gaConfig.account_id ? 'required' : 'none'; - config.usage = gaConfig.account_id || gtConfig.account_id ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - { - gaConfig.include_on_site = evt.detail; - this.edit({ json: JSON.stringify(json) }); - }} - > -
- - - -
-
-
-
- - ${this.renderTemplateOrSlot('google-analytics:after')} -
+ ${super.renderBody()} + + `; } - private __renderGoogleTag(json: TemplateConfigJSON) { - const config = json.analytics_config; - const gtConfig = config.google_tag; - const gaConfig = config.google_analytics; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('google-tag', true); - const isReadonly = this.readonlySelector.matches('google-tag', true); - - return html` -
- ${this.renderTemplateOrSlot('google-tag:before')} - - - Google Tag - -
- evt.key === 'Enter' && this.submit()} - @input=${(evt: InputEvent) => { - gtConfig.account_id = (evt.currentTarget as TextFieldElement).value; - gtConfig.usage = gtConfig.account_id ? 'required' : 'none'; - config.usage = gtConfig.account_id || gaConfig.account_id ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - - evt.key === 'Enter' && this.submit()} - @input=${(evt: InputEvent) => { - gtConfig.send_to = (evt.currentTarget as TextFieldElement).value; - gtConfig.usage = gtConfig.account_id ? 'required' : 'none'; - config.usage = gtConfig.account_id || gaConfig.account_id ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > - - -

- - - - - -

-
-
- - ${this.renderTemplateOrSlot('google-tag:after')} -
- `; + private get __storeLoader() { + type Loader = NucleonElement>; + return this.renderRoot.querySelector('#storeLoader'); } - private __renderTroubleshooting(json: TemplateConfigJSON) { - const { lang, ns } = this; - const config = json.debug; - const isDisabled = !this.in('idle') || this.disabledSelector.matches('troubleshooting', true); - const isReadonly = this.readonlySelector.matches('troubleshooting', true); + private get __formJson(): TemplateConfigJSON { + return this.form.json ? JSON.parse(this.form.json) : getDefaultJSON(); + } - return html` -
- ${this.renderTemplateOrSlot('troubleshooting:before')} - - - - - -
- { - config.usage = evt.detail ? 'required' : 'none'; - this.edit({ json: JSON.stringify(json) }); - }} - > -
- - - -
-
-
-
- - ${this.renderTemplateOrSlot('troubleshooting:after')} -
- `; + private get __dataJson(): TemplateConfigJSON | null { + return this.data?.json ? JSON.parse(this.data.json) : null; } } diff --git a/src/elements/public/TemplateConfigForm/index.ts b/src/elements/public/TemplateConfigForm/index.ts index dbc8c6a4b..7468bf39c 100644 --- a/src/elements/public/TemplateConfigForm/index.ts +++ b/src/elements/public/TemplateConfigForm/index.ts @@ -1,11 +1,17 @@ -import '@polymer/iron-icons'; -import '@polymer/iron-icon'; -import '@vaadin/vaadin-text-field/vaadin-text-field'; +import '../../internal/InternalEditableListControl/index'; +import '../../internal/InternalSummaryControl/index'; +import '../../internal/InternalSwitchControl/index'; +import '../../internal/InternalSelectControl/index'; import '../../internal/InternalSourceControl/index'; -import '../../internal/InternalSandbox/index'; -import '../Spinner/index'; +import '../../internal/InternalTextControl/index'; +import '../../internal/InternalForm/index'; + +import '../NucleonElement/index'; import '../I18n/index'; +import './internal/InternalTemplateConfigFormSupportedCardsControl/index'; +import './internal/InternalTemplateConfigFormFilterValuesControl/index'; + import { TemplateConfigForm } from './TemplateConfigForm'; customElements.define('foxy-template-config-form', TemplateConfigForm); diff --git a/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.test.ts b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.test.ts new file mode 100644 index 000000000..b69bd1710 --- /dev/null +++ b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.test.ts @@ -0,0 +1,252 @@ +import type { NucleonElement } from '../../../NucleonElement/NucleonElement'; +import type { FetchEvent } from '../../../NucleonElement/FetchEvent'; + +import './index'; + +import { InternalTemplateConfigFormFilterValuesControlItem as Item } from './InternalTemplateConfigFormFilterValuesControlItem'; +import { InternalTemplateConfigFormFilterValuesControl as Control } from './InternalTemplateConfigFormFilterValuesControl'; +import { expect, fixture, waitUntil } from '@open-wc/testing'; +import { InternalEditableControl } from '../../../../internal/InternalEditableControl/InternalEditableControl'; +import { createRouter } from '../../../../../server'; +import { getByTestId } from '../../../../../testgen/getByTestId'; +import { html } from 'lit-html'; + +describe('TemplateConfigForm', () => { + describe('InternalTemplateConfigFormFilterValuesControl', () => { + it('extends InternalEditableControl', () => { + expect(new Control()).to.be.instanceOf(InternalEditableControl); + }); + + it('has an empty default i18next namespace', () => { + expect(new Control()).to.have.empty.property('ns'); + }); + + it('has an empty countries map by default', () => { + expect(new Control()).to.have.empty.property('countries'); + }); + + it('has an empty regions URL by default', () => { + expect(new Control()).to.have.empty.property('regions'); + }); + + it('renders countries as InternalTemplateConfigFormFilterValuesControlItem elements', async () => { + const element = await fixture(html` + ({ US: ['WA', 'TX'], CA: '*' })} + > + + `); + + const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; + + expect(wrapper.children[0]).to.have.attribute('regions', JSON.stringify(['WA', 'TX'])); + expect(wrapper.children[0]).to.have.attribute('infer', ''); + expect(wrapper.children[0]).to.have.attribute('code', 'US'); + expect(wrapper.children[0]).to.have.attribute( + 'href', + 'https://demo.api/hapi/property_helpers/4?country_code=US' + ); + + expect(wrapper.children[1]).to.have.attribute('regions', JSON.stringify([])); + expect(wrapper.children[1]).to.have.attribute('infer', ''); + expect(wrapper.children[1]).to.have.attribute('code', 'CA'); + expect(wrapper.children[1]).to.have.attribute( + 'href', + 'https://demo.api/hapi/property_helpers/4?country_code=CA' + ); + }); + + it('can delete a country', async () => { + let value: Record = { US: ['WA', 'TX'], CA: '*' }; + + const element = await fixture(html` + value} + .setValue=${(newValue: Record) => (value = newValue)} + > + + `); + + const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; + wrapper.children[1].dispatchEvent(new CustomEvent('delete')); + + expect(value).to.deep.equal({ US: ['WA', 'TX'] }); + }); + + it('can update country regions', async () => { + let value: Record = { US: ['WA', 'TX'], CA: '*' }; + + const element = await fixture(html` + value} + .setValue=${(newValue: Record) => (value = newValue)} + > + + `); + + const wrapper = (await getByTestId(element, 'countries')) as HTMLElement; + const card = wrapper.children[1] as Item; + + card.regions = ['QC']; + card.dispatchEvent(new CustomEvent('update:regions')); + + expect(value).to.deep.equal({ US: ['WA', 'TX'], CA: ['QC'] }); + }); + + it('can add a country with enter-to-submit', async () => { + let value: Record = { US: ['WA', 'TX'] }; + + const element = await fixture(html` + value} + .setValue=${(newValue: Record) => (value = newValue)} + > + + `); + + const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; + const input = wrapper.querySelector('input') as HTMLInputElement; + + input.value = 'CA'; + input.dispatchEvent(new InputEvent('input')); + input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' })); + + expect(value).to.deep.equal({ US: ['WA', 'TX'], CA: '*' }); + }); + + it('can add a country with click-to-submit', async () => { + let value: Record = { US: ['WA', 'TX'] }; + + const element = await fixture(html` + value} + .setValue=${(newValue: Record) => (value = newValue)} + > + + `); + + const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; + const input = wrapper.querySelector('input') as HTMLInputElement; + const button = wrapper.querySelector('button') as HTMLButtonElement; + + input.value = 'CA'; + input.dispatchEvent(new InputEvent('input')); + button.dispatchEvent(new Event('click')); + + expect(value).to.deep.equal({ US: ['WA', 'TX'], CA: '*' }); + }); + + it('renders a datalist with suggestions', async () => { + const router = createRouter(); + const element = await fixture(html` + router.handleEvent(evt)} + > + + `); + + await waitUntil( + () => { + const nucleons = element.renderRoot.querySelectorAll>('foxy-nucleon'); + return [...nucleons].every(nucleon => !!nucleon.data); + }, + undefined, + { timeout: 5000 } + ); + + const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; + const input = wrapper.querySelector('input') as HTMLInputElement; + const datalist = element.renderRoot.querySelector('datalist') as HTMLDataListElement; + + expect(input.list).to.equal(datalist); + + expect(datalist.options[0]).to.have.property('value', 'GB'); + expect(datalist.options[0]).to.include.text('United Kingdom'); + + expect(datalist.options[1]).to.have.property('value', 'US'); + expect(datalist.options[1]).to.include.text('United States'); + + expect(datalist.options[2]).to.have.property('value', 'UM'); + expect(datalist.options[2]).to.include.text('United States Minor Outlying Islands'); + }); + + it('is enabled by default', async () => { + const element = await fixture( + html`` + ); + + element.querySelectorAll('input, [data-testid="countries"] > *').forEach(control => { + expect(control).to.not.have.attribute('disabled'); + }); + }); + + it('is disabled when element is disabled', async () => { + const element = await fixture( + html` + + + ` + ); + + element.querySelectorAll('input, [data-testid="countries"] > *').forEach(control => { + expect(control).to.have.attribute('disabled'); + }); + }); + + it('disables Add Country button when New Country input is empty', async () => { + const element = await fixture( + html`` + ); + + const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; + const input = wrapper.querySelector('input') as HTMLInputElement; + const button = wrapper.querySelector('button') as HTMLButtonElement; + + input.value = ''; + input.dispatchEvent(new InputEvent('input')); + + await element.requestUpdate(); + expect(button).to.have.attribute('disabled'); + }); + + it('enables Add Country button when New Country input has value', async () => { + const element = await fixture( + html`` + ); + + const wrapper = (await getByTestId(element, 'new-country')) as HTMLElement; + const input = wrapper.querySelector('input') as HTMLInputElement; + const button = wrapper.querySelector('button') as HTMLButtonElement; + + input.value = 'US'; + input.dispatchEvent(new InputEvent('input')); + + await element.requestUpdate(); + expect(button).to.not.have.attribute('disabled'); + }); + + it('is editable by default', async () => { + const element = await fixture( + html`` + ); + + element.querySelectorAll('input, [data-testid="countries"] > *').forEach(input => { + expect(input).to.not.have.attribute('readonly'); + }); + }); + + it('is readonly when element is readonly', async () => { + const element = await fixture( + html`` + ); + + element.querySelectorAll('input, [data-testid="countries"] > *').forEach(input => { + expect(input).to.have.attribute('readonly'); + }); + }); + }); +}); diff --git a/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.ts b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.ts new file mode 100644 index 000000000..28037c4f0 --- /dev/null +++ b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControl.ts @@ -0,0 +1,151 @@ +import type { PropertyDeclarations } from 'lit-element'; +import type { NucleonElement } from '../../../NucleonElement/NucleonElement'; +import type { Resource } from '@foxy.io/sdk/core'; +import type { Rels } from '@foxy.io/sdk/backend'; + +import { InternalEditableControl } from '../../../../internal/InternalEditableControl/InternalEditableControl'; +import { TemplateResult, html } from 'lit-html'; +import { ifDefined } from 'lit-html/directives/if-defined'; +import { classMap } from '../../../../../utils/class-map'; +import { InternalTemplateConfigFormFilterValuesControlItem } from './InternalTemplateConfigFormFilterValuesControlItem'; + +export class InternalTemplateConfigFormFilterValuesControl extends InternalEditableControl { + static get properties(): PropertyDeclarations { + return { + ...super.properties, + __newCountry: { attribute: false }, + countries: {}, + regions: {}, + }; + } + + countries: string | null = null; + + regions: string | null = null; + + private __newCountry = ''; + + renderControl(): TemplateResult { + const countries = this.__countriesLoader?.data?.values ?? {}; + + return html` +
+ ${Object.entries(this._value).map(([country, regions]) => { + let regionsLink: string; + + try { + const url = new URL(this.regions ?? ''); + url.searchParams.set('country_code', country); + regionsLink = url.toString(); + } catch { + regionsLink = ''; + } + + return html` + { + const newCountries = { ...this._value }; + const newRegions = ( + evt.currentTarget as InternalTemplateConfigFormFilterValuesControlItem + ).regions; + + newCountries[country] = newRegions.length ? newRegions : '*'; + this._value = newCountries; + }} + @delete=${() => { + const newCountries = { ...this._value }; + delete newCountries[country]; + this._value = newCountries; + }} + > + + `; + })} + +
+ { + if (evt.key === 'Enter' && this.__newCountry) this.__addCountry(); + }} + @input=${(evt: InputEvent) => { + const target = evt.currentTarget as HTMLInputElement; + this.__newCountry = target.value; + }} + /> + + +
+
+ + + ${Object.entries(countries).map(([code, country]) => { + return html``; + })} + + + this.requestUpdate()} + > + + `; + } + + private __addCountry() { + this._value = { ...this._value, [this.__newCountry]: '*' }; + this.__newCountry = ''; + } + + private get __countriesLoader() { + type Loader = NucleonElement>; + return this.renderRoot.querySelector('#countriesLoader'); + } + + protected get _value(): Record { + return (super._value ?? {}) as Record; + } + + protected set _value(value: Record) { + super._value = value; + } +} diff --git a/src/elements/public/TemplateConfigForm/CountryCard.test.ts b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.test.ts similarity index 60% rename from src/elements/public/TemplateConfigForm/CountryCard.test.ts rename to src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.test.ts index 31b7e3c09..603d25a41 100644 --- a/src/elements/public/TemplateConfigForm/CountryCard.test.ts +++ b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.test.ts @@ -1,16 +1,13 @@ import './index'; +import { InternalTemplateConfigFormFilterValuesControlItem as Item } from './InternalTemplateConfigFormFilterValuesControlItem'; import { expect, fixture, oneEvent } from '@open-wc/testing'; - -import { CountryCard } from './CountryCard'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; -import { getByTestId } from '../../../testgen/getByTestId'; +import { NucleonElement } from '../../../NucleonElement/NucleonElement'; +import { getByTestId } from '../../../../../testgen/getByTestId'; import { html } from 'lit-html'; -customElements.define('test-country-card', CountryCard); - describe('TemplateConfigForm', () => { - describe('CountryCard', () => { + describe('InternalTemplateConfigFormFilterValuesControlItem', () => { const sampleData = { _links: { self: { href: 'test://regions' } }, message: 'Sample regions', @@ -22,36 +19,45 @@ describe('TemplateConfigForm', () => { }; it('extends NucleonElement', () => { - expect(new CountryCard()).to.be.instanceOf(NucleonElement); + expect(new Item()).to.be.instanceOf(NucleonElement); }); it('has an empty default i18next namespace', () => { - expect(new CountryCard()).to.have.empty.property('ns'); + expect(new Item()).to.have.empty.property('ns'); }); it('has an empty regions array by default', () => { - expect(new CountryCard()).to.have.empty.property('regions'); + expect(new Item()).to.have.empty.property('regions'); }); it('has an empty country name by default', () => { - expect(new CountryCard()).to.have.empty.property('name'); + expect(new Item()).to.have.empty.property('name'); }); it('has an empty country code by default', () => { - expect(new CountryCard()).to.have.empty.property('code'); + expect(new Item()).to.have.empty.property('code'); }); it('displays country code if available', async () => { - const layout = html``; - const element = await fixture(layout); - const country = await getByTestId(element, 'country'); + const element = await fixture(html` + + + `); + const country = await getByTestId(element, 'country'); expect(country).to.include.text('US'); }); it('displays both country name and code if available', async () => { - const layout = html``; - const element = await fixture(layout); + const layout = html` + + + `; + + const element = await fixture(layout); const country = await getByTestId(element, 'country'); expect(country).to.include.text('United States'); @@ -59,8 +65,8 @@ describe('TemplateConfigForm', () => { }); it('emits "delete" event when Delete Country button is clicked', async () => { - const layout = html``; - const element = await fixture(layout); + const layout = html``; + const element = await fixture(layout); const country = (await getByTestId(element, 'country')) as HTMLElement; const button = country.querySelector('button[aria-label="delete"]') as HTMLButtonElement; const deleteEvent = oneEvent(element, 'delete'); @@ -71,8 +77,14 @@ describe('TemplateConfigForm', () => { }); it('renders regions as codes when available', async () => { - const layout = html``; - const element = await fixture(layout); + const layout = html` + + + `; + + const element = await fixture(layout); const regions = (await getByTestId(element, 'regions')) as HTMLElement; element.regions.forEach((code, index) => { @@ -82,8 +94,14 @@ describe('TemplateConfigForm', () => { it('renders regions as codes + names when available', async () => { const regions = ['AL', 'TX', 'WA'] as const; - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); const wrapper = (await getByTestId(element, 'regions')) as HTMLElement; @@ -94,8 +112,13 @@ describe('TemplateConfigForm', () => { }); it('can delete a region', async () => { - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); const wrapper = (await getByTestId(element, 'regions')) as HTMLElement; @@ -107,8 +130,11 @@ describe('TemplateConfigForm', () => { }); it('can add a region with enter-to-submit', async () => { - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); const wrapper = (await getByTestId(element, 'new-region')) as HTMLElement; @@ -122,8 +148,11 @@ describe('TemplateConfigForm', () => { }); it('can add a region with click-to-submit', async () => { - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); const wrapper = (await getByTestId(element, 'new-region')) as HTMLElement; @@ -139,8 +168,14 @@ describe('TemplateConfigForm', () => { it('renders a datalist with suggestions', async () => { const regions = ['AL', 'TX', 'WA'] as const; - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); const wrapper = (await getByTestId(element, 'new-region')) as HTMLElement; @@ -160,7 +195,9 @@ describe('TemplateConfigForm', () => { }); it('is enabled by default', async () => { - const element = await fixture(html``); + const element = await fixture( + html`` + ); element.querySelectorAll('input, button[aria-label="delete"]').forEach(control => { expect(control).to.not.have.attribute('disabled'); @@ -168,8 +205,11 @@ describe('TemplateConfigForm', () => { }); it('is disabled when element is disabled', async () => { - const element = await fixture( - html`` + const element = await fixture( + html` + + + ` ); element.querySelectorAll('input, button[aria-label="delete"]').forEach(control => { @@ -178,7 +218,9 @@ describe('TemplateConfigForm', () => { }); it('disables Add Region button when New Region input is empty', async () => { - const element = await fixture(html``); + const element = await fixture( + html`` + ); const wrapper = (await getByTestId(element, 'new-region')) as HTMLElement; const input = wrapper.querySelector('input') as HTMLInputElement; @@ -192,7 +234,9 @@ describe('TemplateConfigForm', () => { }); it('enables Add Region button when New Region input has value', async () => { - const element = await fixture(html``); + const element = await fixture( + html`` + ); const wrapper = (await getByTestId(element, 'new-region')) as HTMLElement; const input = wrapper.querySelector('input') as HTMLInputElement; @@ -206,7 +250,9 @@ describe('TemplateConfigForm', () => { }); it('is editable by default', async () => { - const element = await fixture(html``); + const element = await fixture( + html`` + ); element.querySelectorAll('input').forEach(input => { expect(input).to.not.have.attribute('readonly'); @@ -214,7 +260,9 @@ describe('TemplateConfigForm', () => { }); it('is readonly when element is readonly', async () => { - const element = await fixture(html``); + const element = await fixture( + html`` + ); element.querySelectorAll('input').forEach(input => { expect(input).to.have.attribute('readonly'); diff --git a/src/elements/public/TemplateConfigForm/CountryCard.ts b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.ts similarity index 78% rename from src/elements/public/TemplateConfigForm/CountryCard.ts rename to src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.ts index 665df5bc7..cd6051ac7 100644 --- a/src/elements/public/TemplateConfigForm/CountryCard.ts +++ b/src/elements/public/TemplateConfigForm/internal/InternalTemplateConfigFormFilterValuesControl/InternalTemplateConfigFormFilterValuesControlItem.ts @@ -1,17 +1,19 @@ import { TemplateResult, html } from 'lit-html'; -import { ConfigurableMixin } from '../../../mixins/configurable'; -import { NucleonElement } from '../NucleonElement/NucleonElement'; +import { ConfigurableMixin } from '../../../../../mixins/configurable'; +import { NucleonElement } from '../../../NucleonElement/NucleonElement'; import { PropertyDeclarations } from 'lit-element'; import { Rels } from '@foxy.io/sdk/backend'; import { Resource } from '@foxy.io/sdk/core'; -import { ThemeableMixin } from '../../../mixins/themeable'; -import { TranslatableMixin } from '../../../mixins/translatable'; -import { classMap } from '../../../utils/class-map'; +import { ThemeableMixin } from '../../../../../mixins/themeable'; +import { TranslatableMixin } from '../../../../../mixins/translatable'; +import { classMap } from '../../../../../utils/class-map'; const Base = ConfigurableMixin(ThemeableMixin(TranslatableMixin(NucleonElement))); -export class CountryCard extends Base> { +export class InternalTemplateConfigFormFilterValuesControlItem extends Base< + Resource +> { static get properties(): PropertyDeclarations { return { ...super.properties, @@ -34,15 +36,12 @@ export class CountryCard extends Base> { return html`
-
-
+
+
${this.name || this.code} ${this.name ? html`${this.code}` : ''}
@@ -65,16 +64,13 @@ export class CountryCard extends Base> {
-
+
${this.regions.map(region => { const name = this.data?.values[region]?.default; const code = region; return html` -
+
${name || code} ${name ? html`${code}` : ''} @@ -83,9 +79,9 @@ export class CountryCard extends Base> {