// 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'));
});
});