import { expect, html, defineCE, unsafeStatic, fixture } from '@open-wc/testing';
import { LitElement } from '@lion/core';
import sinon from 'sinon';
import { FormControlMixin } from '../src/FormControlMixin.js';
import { FormRegistrarMixin } from '../src/registration/FormRegistrarMixin.js';
describe('FormControlMixin', () => {
const inputSlot = '';
// @ts-expect-error base constructor same return type
class FormControlMixinClass extends FormControlMixin(LitElement) {}
const tagString = defineCE(FormControlMixinClass);
const tag = unsafeStatic(tagString);
it('has a label', async () => {
const elAttr = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag} label="Email address">${inputSlot}${tag}>
`));
expect(elAttr.label).to.equal('Email address', 'as an attribute');
const elProp = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}
.label=${'Email address'}
>${inputSlot}
${tag}>`));
expect(elProp.label).to.equal('Email address', 'as a property');
const elElem = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
${inputSlot}
${tag}>`));
expect(elElem.label).to.equal('Email address', 'as an element');
});
it('is hidden when attribute hidden is true', async () => {
const el = await fixture(html`
<${tag} hidden>
${inputSlot}
${tag}>`);
expect(el).not.to.be.displayed;
});
it('has a label that supports inner html', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
${inputSlot}
${tag}>`));
expect(el.label).to.equal('Email address');
});
it('only takes label of direct child', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
<${tag} label="Email address">
${inputSlot}
${tag}>
${tag}>`));
expect(el.label).to.equal('');
});
it('can have a help-text', async () => {
const elAttr = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag} help-text="We will not send you any spam">${inputSlot}${tag}>
`));
expect(elAttr.helpText).to.equal('We will not send you any spam', 'as an attribute');
const elProp = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}
.helpText=${'We will not send you any spam'}
>${inputSlot}
${tag}>`));
expect(elProp.helpText).to.equal('We will not send you any spam', 'as a property');
const elElem = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
We will not send you any spam
${inputSlot}
${tag}>`));
expect(elElem.helpText).to.equal('We will not send you any spam', 'as an element');
});
it('can have a help-text that supports inner html', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
We will not send you any spam
${inputSlot}
${tag}>`));
expect(el.helpText).to.equal('We will not send you any spam');
});
it('only takes help-text of direct child', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
<${tag} help-text="We will not send you any spam">
${inputSlot}
${tag}>
${tag}>`));
expect(el.helpText).to.equal('');
});
it('does not duplicate aria-describedby and aria-labelledby ids', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(`
<${tagString} help-text="This element will be disconnected/reconnected">${inputSlot}${tagString}>
`));
const wrapper = /** @type {LitElement} */ (await fixture(``));
el.parentElement?.appendChild(wrapper);
wrapper.appendChild(el);
await wrapper.updateComplete;
['aria-describedby', 'aria-labelledby'].forEach(ariaAttributeName => {
const ariaAttribute = Array.from(el.children)
.find(child => child.slot === 'input')
?.getAttribute(ariaAttributeName)
?.trim()
.split(' ');
const hasDuplicate = !!ariaAttribute?.find((elm, i) => ariaAttribute.indexOf(elm) !== i);
expect(hasDuplicate).to.be.false;
});
});
// FIXME: Broken test
it.skip('internally sorts aria-describedby and aria-labelledby ids', async () => {
const wrapper = await fixture(html`
should go after input internals
should go after input internals
<${tag}>
Added to description by default
${tag}>
should go after input internals
should go after input internals
`);
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
const { _inputNode } = el;
// 1. addToAriaLabelledBy()
// external inputs should go in order defined by user
const labelA = /** @type {HTMLElement} */ (wrapper.querySelector('#additionalLabelA'));
const labelB = /** @type {HTMLElement} */ (wrapper.querySelector('#additionalLabelB'));
el.addToAriaLabelledBy(labelA);
el.addToAriaLabelledBy(labelB);
const ariaLabelId = /** @type {number} */ (_inputNode
.getAttribute('aria-labelledby')
?.indexOf(`label-${el._inputId}`));
const ariaLabelA = /** @type {number} */ (_inputNode
.getAttribute('aria-labelledby')
?.indexOf('additionalLabelA'));
const ariaLabelB = /** @type {number} */ (_inputNode
.getAttribute('aria-labelledby')
?.indexOf('additionalLabelB'));
expect(ariaLabelId < ariaLabelB && ariaLabelB < ariaLabelA).to.be.true;
// 2. addToAriaDescribedBy()
// Check if the aria attr is filled initially
const descA = /** @type {HTMLElement} */ (wrapper.querySelector('#additionalDescriptionA'));
const descB = /** @type {HTMLElement} */ (wrapper.querySelector('#additionalDescriptionB'));
el.addToAriaDescribedBy(descB);
el.addToAriaDescribedBy(descA);
const ariaDescId = /** @type {number} */ (_inputNode
.getAttribute('aria-describedby')
?.indexOf(`feedback-${el._inputId}`));
const ariaDescA = /** @type {number} */ (_inputNode
.getAttribute('aria-describedby')
?.indexOf('additionalDescriptionA'));
const ariaDescB = /** @type {number} */ (_inputNode
.getAttribute('aria-describedby')
?.indexOf('additionalDescriptionB'));
// Should be placed in the end
expect(ariaDescId < ariaDescB && ariaDescB < ariaDescA).to.be.true;
});
it('adds aria-live="polite" to the feedback slot', async () => {
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
<${tag}>
${inputSlot}