// eslint-disable-next-line @typescript-eslint/triple-slash-reference /// /* eslint-disable no-undef */ import { ZuiFormAssociatedElement } from '@zywave/zui-base'; import { assert } from '@esm-bundle/chai'; import { html } from 'lit'; import { buildForm } from '../../../../test/src/util/form-helpers'; import { randString } from '@zywave/../../test/src/util/helpers'; import type { PropertyValues } from 'lit'; import type { FormValueType } from '@zywave/zui-base/dist/zui-form-associated-element'; function getAllKvpFromForm(formOrFormData: HTMLFormElement | FormData) { if (formOrFormData instanceof HTMLFormElement) { formOrFormData = new FormData(formOrFormData); } let result = ''; let i = 1; for (const [k, v] of formOrFormData.entries()) { let val = v; if (Array.isArray(v)) { val = v.join(', '); } result += `#${i++}: ${k}=${val}\n`; } return result; } function createFACEElement(tagName): T & FACETestBase { return document.createElement(tagName) as T & FACETestBase; } abstract class FACETestBase extends ZuiFormAssociatedElement { setFormValue(value: FormValueType) { this._setFormValue(value); } } suite('zui-form-associated-element', () => { let form: HTMLFormElement; let wasSubmitted: boolean; setup(() => { wasSubmitted = false; form = buildForm({ enableSubmit: false, skipAppend: true, onSubmit: () => (wasSubmitted = true), }); document.body.appendChild(form); }); teardown(() => { wasSubmitted = false; document.body.removeChild(form); }); test('Not configuring _focusControlSelector throws error', async () => { const elementTagName = 'test-form-selector-error'; customElements.define( elementTagName, class extends FACETestBase { render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); try { await element.updateComplete; assert.isFalse(true, 'Expected an error'); } catch { // left empty on purpose } }); test('Not configuring formResetCallback throws error', async () => { const elementTagName = 'test-form-reset-callback-error'; customElements.define( elementTagName, class extends FACETestBase { render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); try { /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ (element as any).formResetCallback(); assert.isFalse(true, 'Expected an error'); } catch { // left empty on purpose } }); test('is associated with parent form element', () => { const elementTagName = 'test-face-parent'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); assert.equal(element.form, form); }); test('is associated with form element by attribute', () => { const formId = 'form-id-attr'; form.setAttribute('id', formId); const elementTagName = 'test-face-by-attr'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); element.setAttribute('form', formId); document.body.append(element); assert.equal(element.form, form); document.body.removeChild(element); }); test('form associated element with string value participates form submit', async () => { const elementTagName = 'test-face-basic-submit'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); const name = randString(); const value = randString(); element.setAttribute('name', name); element.setFormValue(value); form.append(element); form.requestSubmit(); assert.isTrue(wasSubmitted); const formData = new FormData(form); assert.equal(formData.get(name), value, getAllKvpFromForm(formData)); }); test('form associated element with array of string values participates form submit', async () => { const elementTagName = 'test-face-array-submit'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); const name = randString(); const numValues = 5; const values = Array.from(Array(numValues)).map(() => randString()); element.setAttribute('name', name); element.setFormValue(values); form.append(element); form.requestSubmit(); assert.isTrue(wasSubmitted); const formData = new FormData(form); const formValues = formData.getAll(name); assert.equal(formValues.length, numValues); for (const v of formValues) { assert.include(formValues, v); } }); test('form associated element with File value participates form submit', async () => { const elementTagName = 'test-face-file-submit'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); const name = randString(); const filename = 'foo.txt'; const value = new File(['foo'], filename, { type: 'text/plain', }); element.setAttribute('name', name); element.setFormValue(value); form.append(element); form.requestSubmit(); assert.isTrue(wasSubmitted); const formData = new FormData(form); const formValue = formData.get(name) as File; assert.exists(formValue); assert.instanceOf(formValue, File); assert.equal(formValue.name, filename); }); test('form associated element participates in form reset', async () => { const elementTagName = 'test-face-reset'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } formResetCallback() { this._setFormValue(null); } } ); const element = createFACEElement(elementTagName); const name = randString(); const value = randString(); element.setAttribute('name', name); element.setFormValue(value); form.append(element); form.reset(); const formData = new FormData(form); assert.notExists(formData.get(name)); }); test('is associated with parent label element', async () => { const elementTagName = 'test-face-label-parent'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); const label = document.createElement('label'); label.innerText = 'label'; label.append(element); form.append(label); let wasClicked = false; element.addEventListener('click', () => (wasClicked = true)); await element.updateComplete; label.click(); assert.isTrue(wasClicked); }); test('is associated with label element by attribute', async () => { const elementTagName = 'test-face-label-by-attr'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const id = 'label-by-attr'; const element = createFACEElement(elementTagName); element.setAttribute('id', id); const label = document.createElement('label'); label.setAttribute('for', id); label.innerText = 'label'; form.append(label); form.append(element); let wasClicked = false; element.addEventListener('click', () => (wasClicked = true)); await element.updateComplete; label.click(); assert.isTrue(wasClicked); }); test('is focused when autofocus applied', async () => { const elementTagName = 'test-face-autofocus'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); element.setAttribute('autofocus', ''); document.body.append(element); await element.updateComplete; assert.equal(document.activeElement, element); document.body.removeChild(element); }); test('ensure form controls with identical names but one with a null value still submit in a form correctly', async () => { const elementTagName = 'test-checkbox'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const checkbox1 = createFACEElement(elementTagName); const checkbox2 = createFACEElement(elementTagName); const name = 'checkbox'; const checkbox1Val = randString(); checkbox1.setAttribute('name', name); checkbox1.setFormValue(checkbox1Val); checkbox2.setAttribute('name', name); checkbox2.setFormValue(null); form.append(checkbox1, checkbox2); form.requestSubmit(); assert.isTrue(wasSubmitted); const formData = new FormData(form); assert.equal(form.querySelectorAll(`[name=${name}]`).length, 2); assert.equal(formData.get(name), checkbox1Val); }); test('setCustomValidity sets proper validity state', async () => { const elementTagName = 'test-face-custom-validity'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); await element.updateComplete; let validationMessage: string, validity: ValidityState; validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, ''); assert.equal(validity.valid, true); assert.equal(validity.customError, false); element.setCustomValidity('foo'); validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, 'foo'); assert.equal(validity.valid, false); assert.equal(validity.customError, true); element.setCustomValidity(''); validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, ''); assert.equal(validity.valid, true); }); test('setCustomValidity preserves other validity issues', async () => { const elementTagName = 'test-face-custom-validity-preserve'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this._setValidity({ valueMissing: true }, 'Please fill out this field.'); } render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); await element.updateComplete; let validationMessage: string, validity: ValidityState; validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, 'Please fill out this field.'); assert.equal(validity.valid, false); assert.equal(validity.customError, false, 'customError should be false'); assert.equal(validity.valueMissing, true, 'valueMissing should be true'); element.setCustomValidity(''); assert.equal(validationMessage, 'Please fill out this field.'); assert.equal(validity.valid, false); assert.equal(validity.customError, false, 'customError should be false'); assert.equal(validity.valueMissing, true, 'valueMissing should be true'); element.setCustomValidity('foo'); validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, 'foo'); assert.equal(validity.valid, false, 'valid should be false'); assert.equal(validity.customError, true, 'customError should be true'); assert.equal(validity.valueMissing, true, 'valueMissing should be true'); element.setCustomValidity('bar'); validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, 'bar'); assert.equal(validity.valid, false, 'valid should be false'); assert.equal(validity.customError, true, 'customError should be true'); assert.equal(validity.valueMissing, true, 'valueMissing should be true'); element.setCustomValidity(''); validationMessage = element.validationMessage; validity = element.validity; assert.equal(validationMessage, 'Please fill out this field.', 'Prior error message should be preserved'); assert.equal(validity.valid, false, 'valid should be false'); assert.equal(validity.customError, false, 'customError should be false'); assert.equal(validity.valueMissing, true, 'valueMissing should be true'); }); test('is disabled when the disabled property is set', async () => { const elementTagName = 'test-face-disabled-property'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); await element.updateComplete; assert.equal(element.hasAttribute('disabled'), false); element.disabled = true; await element.updateComplete; assert.equal(element.hasAttribute('disabled'), true); assert.isTrue(element.matches(':disabled')); }); test('is disabled when the disabled attribute is set', async () => { const elementTagName = 'test-face-disabled-attribute'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); form.append(element); await element.updateComplete; assert.equal(element.hasAttribute('disabled'), false); element.setAttribute('disabled', ''); await element.updateComplete; assert.equal(element.hasAttribute('disabled'), true); assert.isTrue(element.matches(':disabled')); }); test('is disabled when fieldset is disabled', async () => { const elementTagName = 'test-face-disabled-fieldset'; customElements.define( elementTagName, class extends FACETestBase { get _focusControlSelector() { return 'input'; } render() { return html``; } } ); const element = createFACEElement(elementTagName); const fieldset = document.createElement('fieldset'); fieldset.append(element); form.append(fieldset); await element.updateComplete; fieldset.disabled = true; await element.updateComplete; assert.isFalse(element.hasAttribute('disabled'), 'Element should not have disabled attribute'); assert.isFalse(element.disabled, 'Element should have disabled property set to false'); assert.isTrue(element.matches(':disabled')); }); });