chore: format & changeset

This commit is contained in:
Konstantinos Norgias 2021-05-20 12:25:28 +02:00
parent e17f7bdfa6
commit 72067c0d19
35 changed files with 520 additions and 351 deletions

View file

@ -0,0 +1,40 @@
---
'babel-plugin-extend-docs': minor
'providence-analytics': minor
'publish-docs': minor
'remark-extend': minor
'@lion/accordion': minor
'@lion/button': minor
'@lion/calendar': minor
'@lion/checkbox-group': minor
'@lion/collapsible': minor
'@lion/combobox': minor
'@lion/core': minor
'@lion/dialog': minor
'@lion/form': minor
'@lion/form-core': minor
'@lion/form-integrations': minor
'@lion/helpers': minor
'@lion/icon': minor
'@lion/input': minor
'@lion/input-amount': minor
'@lion/input-datepicker': minor
'@lion/input-iban': minor
'@lion/input-stepper': minor
'@lion/listbox': minor
'@lion/localize': minor
'@lion/overlays': minor
'@lion/pagination': minor
'@lion/progress-indicator': minor
'@lion/radio-group': minor
'@lion/select': minor
'@lion/select-rich': minor
'@lion/steps': minor
'@lion/switch': minor
'@lion/tabs': minor
'@lion/textarea': minor
'@lion/tooltip': minor
'@lion/validate-messages': minor
---
**BREAKING** Upgrade to lit version 2

View file

@ -36,9 +36,8 @@ There are the following methods available to control the pagination.
```js preview-story ```js preview-story
export const methods = ({ shadowRoot }) => { export const methods = ({ shadowRoot }) => {
setTimeout(() => { setTimeout(() => {
shadowRoot.getElementById('pagination-method-demo').innerText = shadowRoot.getElementById( shadowRoot.getElementById('pagination-method-demo').innerText =
'pagination-method', shadowRoot.getElementById('pagination-method').current;
).current;
}); });
return html` return html`
@ -80,9 +79,8 @@ export const methods = ({ shadowRoot }) => {
```js preview-story ```js preview-story
export const event = ({ shadowRoot }) => { export const event = ({ shadowRoot }) => {
setTimeout(() => { setTimeout(() => {
shadowRoot.getElementById('pagination-event-demo-text').innerText = shadowRoot.getElementById( shadowRoot.getElementById('pagination-event-demo-text').innerText =
'pagination-event-demo', shadowRoot.getElementById('pagination-event-demo').current;
).current;
}); });
return html` return html`

View file

@ -18,9 +18,9 @@ import '@lion/button/define';
export class UmbrellaForm extends LitElement { export class UmbrellaForm extends LitElement {
get _lionFormNode() { get _lionFormNode() {
return /** @type {import('@lion/form').LionForm} */ (this.shadowRoot?.querySelector( return /** @type {import('@lion/form').LionForm} */ (
'lion-form', this.shadowRoot?.querySelector('lion-form')
)); );
} }
render() { render() {

View file

@ -111,7 +111,8 @@ class PBoard extends DecorateMixin(LitElement) {
checked checked
@change="${({ target }) => { @change="${({ target }) => {
// TODO: of course, logic depending on dom is never a good idea // TODO: of course, logic depending on dom is never a good idea
const groupBoxes = target.parentElement.nextElementSibling.querySelectorAll( const groupBoxes =
target.parentElement.nextElementSibling.querySelectorAll(
'input[type=checkbox]', 'input[type=checkbox]',
); );
const { checked } = target; const { checked } = target;

View file

@ -25,11 +25,8 @@ const promptAnalyzerModule = require('../../src/cli/prompt-analyzer-menu.js');
const { toPosixPath } = require('../../src/program/utils/to-posix-path.js'); const { toPosixPath } = require('../../src/program/utils/to-posix-path.js');
const { getExtendDocsResults } = require('../../src/cli/launch-providence-with-extend-docs.js'); const { getExtendDocsResults } = require('../../src/cli/launch-providence-with-extend-docs.js');
const { const { pathsArrayFromCs, pathsArrayFromCollectionName, appendProjectDependencyPaths } =
pathsArrayFromCs, cliHelpersModule;
pathsArrayFromCollectionName,
appendProjectDependencyPaths,
} = cliHelpersModule;
const queryResults = []; const queryResults = [];

View file

@ -15,9 +15,8 @@ const {
restoreSuppressNonCriticalLogs, restoreSuppressNonCriticalLogs,
} = require('../../../test-helpers/mock-log-service-helpers.js'); } = require('../../../test-helpers/mock-log-service-helpers.js');
const findCustomelementsQueryConfig = QueryService.getQueryConfigFromAnalyzer( const findCustomelementsQueryConfig =
'find-customelements', QueryService.getQueryConfigFromAnalyzer('find-customelements');
);
const _providenceCfg = { const _providenceCfg = {
targetProjectPaths: ['/fictional/project'], // defined in mockProject targetProjectPaths: ['/fictional/project'], // defined in mockProject
}; };

View file

@ -149,7 +149,8 @@ describe('remarkExtend', () => {
it('throws if an import file does not exist', async () => { it('throws if an import file does not exist', async () => {
await expectThrowsAsync(() => execute("```js ::import('./fixtures/not-available.md')\n```"), { await expectThrowsAsync(() => execute("```js ::import('./fixtures/not-available.md')\n```"), {
errorMatch: /The import "\.\/fixtures\/not-available.md" in "test-file.md" does not exist\. Resolved to ".*"\.$/, errorMatch:
/The import "\.\/fixtures\/not-available.md" in "test-file.md" does not exist\. Resolved to ".*"\.$/,
}); });
}); });
@ -157,7 +158,8 @@ describe('remarkExtend', () => {
const input = const input =
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```"; "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```";
await expectThrowsAsync(() => execute(input), { await expectThrowsAsync(() => execute(input), {
errorMatch: /The start selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\.$/, errorMatch:
/The start selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\.$/,
}); });
}); });
@ -165,7 +167,8 @@ describe('remarkExtend', () => {
const input = const input =
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```"; "```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```";
await expectThrowsAsync(() => execute(input), { await expectThrowsAsync(() => execute(input), {
errorMatch: /The end selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\./, errorMatch:
/The end selector "heading:has\(\[value=Does not exit\]\)" could not find a matching node in ".*"\./,
}); });
}); });

View file

@ -226,12 +226,12 @@ export class LionAccordion extends LitElement {
* @private * @private
*/ */
__setupStore() { __setupStore() {
const invokers = /** @type {HTMLElement[]} */ (Array.from( const invokers = /** @type {HTMLElement[]} */ (
this.querySelectorAll('[slot="invoker"]'), Array.from(this.querySelectorAll('[slot="invoker"]'))
)); );
const contents = /** @type {HTMLElement[]} */ (Array.from( const contents = /** @type {HTMLElement[]} */ (
this.querySelectorAll('[slot="content"]'), Array.from(this.querySelectorAll('[slot="content"]'))
)); );
if (invokers.length !== contents.length) { if (invokers.length !== contents.length) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.warn( console.warn(
@ -356,9 +356,11 @@ export class LionAccordion extends LitElement {
if (!(this.__store && this.__store[this.focusedIndex])) { if (!(this.__store && this.__store[this.focusedIndex])) {
return; return;
} }
const previousInvoker = /** @type {HTMLElement | undefined} */ (Array.from(this.children).find( const previousInvoker = /** @type {HTMLElement | undefined} */ (
Array.from(this.children).find(
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'), child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
)); )
);
if (previousInvoker) { if (previousInvoker) {
unfocusInvoker(previousInvoker); unfocusInvoker(previousInvoker);
} }

View file

@ -162,9 +162,9 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement))
* @protected * @protected
*/ */
get _nativeButtonNode() { get _nativeButtonNode() {
return /** @type {HTMLButtonElement} */ (Array.from(this.children).find( return /** @type {HTMLButtonElement} */ (
child => child.slot === '_button', Array.from(this.children).find(child => child.slot === '_button')
)); );
} }
get slots() { get slots() {

View file

@ -813,12 +813,10 @@ describe('<lion-calendar>', () => {
`); `);
const elObj = new CalendarObject(el); const elObj = new CalendarObject(el);
expect( expect(
elObj.checkForAllDayObjs(/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'), [ elObj.checkForAllDayObjs(
1, /** @param {DayObject} d */ d => d.el.hasAttribute('disabled'),
2, [1, 2, 30, 31],
30, ),
31,
]),
).to.equal(true); ).to.equal(true);
clock.restore(); clock.restore();

View file

@ -11,9 +11,8 @@ function compareMultipleMonth(obj) {
week.days.forEach((day, dayi) => { week.days.forEach((day, dayi) => {
// @ts-expect-error since we are converting Date to ISO string, but that's okay for our test Date comparisons // @ts-expect-error since we are converting Date to ISO string, but that's okay for our test Date comparisons
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
obj.months[monthi].weeks[weeki].days[dayi].date = obj.months[monthi].weeks[weeki].days[ obj.months[monthi].weeks[weeki].days[dayi].date =
dayi obj.months[monthi].weeks[weeki].days[dayi].date.toISOString();
].date.toISOString();
}); });
}); });
}); });

View file

@ -108,9 +108,9 @@ describe('SlotMixin', () => {
const tag = defineCE(SlotPrivateText); const tag = defineCE(SlotPrivateText);
const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));
expect(el.didCreateConditionalSlot()).to.be.true; expect(el.didCreateConditionalSlot()).to.be.true;
const elUserSlot = /** @type {SlotPrivateText} */ (await fixture( const elUserSlot = /** @type {SlotPrivateText} */ (
`<${tag}><p slot="conditional">foo</p><${tag}>`, await fixture(`<${tag}><p slot="conditional">foo</p><${tag}>`)
)); );
expect(elUserSlot.didCreateConditionalSlot()).to.be.false; expect(elUserSlot.didCreateConditionalSlot()).to.be.false;
renderSlot = false; renderSlot = false;
const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`)); const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));

View file

@ -1,6 +1,7 @@
import { dedupeMixin } from '@lion/core'; import { dedupeMixin } from '@lion/core';
const windowWithOptionalPolyfill = /** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window); const windowWithOptionalPolyfill =
/** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
const polyfilledNodes = new WeakMap(); const polyfilledNodes = new WeakMap();
/** /**

View file

@ -31,13 +31,13 @@ const FormRegistrarPortalMixinImplementation = superclass =>
* @type {(FormRegistrarPortalHost & HTMLElement) | undefined} * @type {(FormRegistrarPortalHost & HTMLElement) | undefined}
*/ */
this.registrationTarget = undefined; this.registrationTarget = undefined;
this.__redispatchEventForFormRegistrarPortalMixin = this.__redispatchEventForFormRegistrarPortalMixin.bind( this.__redispatchEventForFormRegistrarPortalMixin =
this, this.__redispatchEventForFormRegistrarPortalMixin.bind(this);
);
this.addEventListener( this.addEventListener(
'form-element-register', 'form-element-register',
/** @type {EventListenerOrEventListenerObject} */ (this /** @type {EventListenerOrEventListenerObject} */ (
.__redispatchEventForFormRegistrarPortalMixin), this.__redispatchEventForFormRegistrarPortalMixin
),
); );
} }

View file

@ -39,8 +39,10 @@ export const ValidateMixinImplementation = superclass =>
SyncUpdatableMixin(DisabledMixin(SlotMixin(ScopedElementsMixin(superclass)))), SyncUpdatableMixin(DisabledMixin(SlotMixin(ScopedElementsMixin(superclass)))),
) { ) {
static get scopedElements() { static get scopedElements() {
const scopedElementsCtor = /** @type {typeof import('@open-wc/scoped-elements/src/types').ScopedElementsHost} */ (super const scopedElementsCtor =
.constructor); /** @type {typeof import('@open-wc/scoped-elements/src/types').ScopedElementsHost} */ (
super.constructor
);
return { return {
...scopedElementsCtor.scopedElements, ...scopedElementsCtor.scopedElements,
'lion-validation-feedback': LionValidationFeedback, 'lion-validation-feedback': LionValidationFeedback,
@ -482,10 +484,12 @@ export const ValidateMixinImplementation = superclass =>
* @private * @private
*/ */
__executeResultValidators(regularValidationResult) { __executeResultValidators(regularValidationResult) {
const resultValidators = /** @type {ResultValidator[]} */ (this._allValidators.filter(v => { const resultValidators = /** @type {ResultValidator[]} */ (
this._allValidators.filter(v => {
const vCtor = /** @type {typeof Validator} */ (v.constructor); const vCtor = /** @type {typeof Validator} */ (v.constructor);
return !vCtor.async && v instanceof ResultValidator; return !vCtor.async && v instanceof ResultValidator;
})); })
);
return resultValidators.filter(v => return resultValidators.filter(v =>
v.executeOnResults({ v.executeOnResults({
@ -511,8 +515,10 @@ export const ValidateMixinImplementation = superclass =>
this.__validationResult = [...resultOutCome, ...syncAndAsyncOutcome]; this.__validationResult = [...resultOutCome, ...syncAndAsyncOutcome];
// this._storeResultsOnInstance(this.__validationResult); // this._storeResultsOnInstance(this.__validationResult);
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this const ctor =
.constructor); /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
this.constructor
);
/** @type {Object.<string, Object.<string, boolean>>} */ /** @type {Object.<string, Object.<string, boolean>>} */
const validationStates = ctor.validationTypes.reduce( const validationStates = ctor.validationTypes.reduce(
@ -582,8 +588,10 @@ export const ValidateMixinImplementation = superclass =>
console.error(errorMessage, this); console.error(errorMessage, this);
throw new Error(errorMessage); throw new Error(errorMessage);
} }
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this const ctor =
.constructor); /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
this.constructor
);
if (ctor.validationTypes.indexOf(v.type) === -1) { if (ctor.validationTypes.indexOf(v.type) === -1) {
const vCtor = /** @type {typeof Validator} */ (v.constructor); const vCtor = /** @type {typeof Validator} */ (v.constructor);
// throws in constructor are not visible to end user so we do both // throws in constructor are not visible to end user so we do both
@ -776,12 +784,16 @@ export const ValidateMixinImplementation = superclass =>
changedProperties.has('shouldShowFeedbackFor') || changedProperties.has('shouldShowFeedbackFor') ||
changedProperties.has('hasFeedbackFor') changedProperties.has('hasFeedbackFor')
) { ) {
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this const ctor =
.constructor); /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
this.constructor
);
// Necessary typecast because types aren't smart enough to understand that we filter out undefined // Necessary typecast because types aren't smart enough to understand that we filter out undefined
this.showsFeedbackFor = /** @type {string[]} */ (ctor.validationTypes this.showsFeedbackFor = /** @type {string[]} */ (
ctor.validationTypes
.map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined)) .map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined))
.filter(Boolean)); .filter(Boolean)
);
this._updateFeedbackComponent(); this._updateFeedbackComponent();
} }
@ -791,9 +803,9 @@ export const ValidateMixinImplementation = superclass =>
} }
if (changedProperties.has('validationStates')) { if (changedProperties.has('validationStates')) {
const prevStates = /** @type {{[key: string]: object;}} */ (changedProperties.get( const prevStates = /** @type {{[key: string]: object;}} */ (
'validationStates', changedProperties.get('validationStates')
)); );
if (prevStates) { if (prevStates) {
Object.entries(this.validationStates).forEach(([type, feedbackObj]) => { Object.entries(this.validationStates).forEach(([type, feedbackObj]) => {
if ( if (
@ -811,11 +823,14 @@ export const ValidateMixinImplementation = superclass =>
* @protected * @protected
*/ */
_updateShouldShowFeedbackFor() { _updateShouldShowFeedbackFor() {
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this const ctor =
.constructor); /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
this.constructor
);
// Necessary typecast because types aren't smart enough to understand that we filter out undefined // Necessary typecast because types aren't smart enough to understand that we filter out undefined
const newShouldShowFeedbackFor = /** @type {string[]} */ (ctor.validationTypes const newShouldShowFeedbackFor = /** @type {string[]} */ (
ctor.validationTypes
.map(type => .map(type =>
this.feedbackCondition( this.feedbackCondition(
type, type,
@ -825,7 +840,8 @@ export const ValidateMixinImplementation = superclass =>
? type ? type
: undefined, : undefined,
) )
.filter(Boolean)); .filter(Boolean)
);
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) { if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
this.shouldShowFeedbackFor = newShouldShowFeedbackFor; this.shouldShowFeedbackFor = newShouldShowFeedbackFor;
@ -841,8 +857,10 @@ export const ValidateMixinImplementation = superclass =>
* @protected * @protected
*/ */
_prioritizeAndFilterFeedback({ validationResult }) { _prioritizeAndFilterFeedback({ validationResult }) {
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this const ctor =
.constructor); /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
this.constructor
);
const types = ctor.validationTypes; const types = ctor.validationTypes;
// Sort all validators based on the type provided. // Sort all validators based on the type provided.
const res = validationResult const res = validationResult

View file

@ -92,7 +92,8 @@ export class MinMaxLength extends Validator {
} }
} }
const isEmailRegex = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; const isEmailRegex =
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export class IsEmail extends Validator { export class IsEmail extends Validator {
static get validatorName() { static get validatorName() {
return 'IsEmail'; return 'IsEmail';

View file

@ -36,11 +36,13 @@ export const runRegistrationSuite = customConfig => {
const { parentTagString, childTagString } = cfg; const { parentTagString, childTagString } = cfg;
it('can register a formElement', async () => { it('can register a formElement', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</${parentTag}> </${parentTag}>
`)); `)
);
expect(el.formElements.length).to.equal(1); expect(el.formElements.length).to.equal(1);
}); });
@ -57,25 +59,29 @@ export const runRegistrationSuite = customConfig => {
}); });
it('can register a formElement with arbitrary dom tree in between registrar and registering', async () => { it('can register a formElement with arbitrary dom tree in between registrar and registering', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<div> <div>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</div> </div>
</${parentTag}> </${parentTag}>
`)); `)
);
expect(el.formElements.length).to.equal(1); expect(el.formElements.length).to.equal(1);
}); });
it('supports nested registration parents', async () => { it('supports nested registration parents', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<${parentTag} class="sub-group"> <${parentTag} class="sub-group">
<${childTag}></${childTag}> <${childTag}></${childTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</${parentTag}> </${parentTag}>
</${parentTag}> </${parentTag}>
`)); `)
);
expect(el.formElements.length).to.equal(1); expect(el.formElements.length).to.equal(1);
const subGroup = /** @type {RegistrarClass} */ (el.querySelector('.sub-group')); const subGroup = /** @type {RegistrarClass} */ (el.querySelector('.sub-group'));
@ -95,20 +101,24 @@ export const runRegistrationSuite = customConfig => {
} }
const tagWrapperString = defineCE(PerformUpdate); const tagWrapperString = defineCE(PerformUpdate);
const tagWrapper = unsafeStatic(tagWrapperString); const tagWrapper = unsafeStatic(tagWrapperString);
const el = /** @type {PerformUpdate} */ (await fixture(html` const el = /** @type {PerformUpdate} */ (
await fixture(html`
<${tagWrapper}> <${tagWrapper}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</${tagWrapper}> </${tagWrapper}>
`)); `)
);
expect(el.formElements.length).to.equal(1); expect(el.formElements.length).to.equal(1);
}); });
it('can dynamically add/remove elements', async () => { it('can dynamically add/remove elements', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</${parentTag}> </${parentTag}>
`)); `)
);
const newField = await fixture(html` const newField = await fixture(html`
<${childTag}></${childTag}> <${childTag}></${childTag}>
`); `);
@ -122,20 +132,24 @@ export const runRegistrationSuite = customConfig => {
}); });
it('adds elements to formElements in the right order (DOM)', async () => { it('adds elements to formElements in the right order (DOM)', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<${childTag} pos="0"></${childTag}> <${childTag} pos="0"></${childTag}>
<${childTag} pos="1"></${childTag}> <${childTag} pos="1"></${childTag}>
<${childTag} pos="2"></${childTag}> <${childTag} pos="2"></${childTag}>
</${parentTag}> </${parentTag}>
`)); `)
);
/** INSERT field before the pos=1 */ /** INSERT field before the pos=1 */
/** /**
* @typedef {Object.<string, string>} prop * @typedef {Object.<string, string>} prop
*/ */
const newField = /** @type {RegisteringClass & prop} */ (await fixture(html` const newField = /** @type {RegisteringClass & prop} */ (
await fixture(html`
<${childTag}></${childTag}> <${childTag}></${childTag}>
`)); `)
);
newField.setAttribute('pos', 'inserted-before-1'); newField.setAttribute('pos', 'inserted-before-1');
el.insertBefore(newField, el.children[1]); el.insertBefore(newField, el.children[1]);
@ -145,9 +159,11 @@ export const runRegistrationSuite = customConfig => {
expect(el.formElements[1].getAttribute('pos')).to.equal('inserted-before-1'); expect(el.formElements[1].getAttribute('pos')).to.equal('inserted-before-1');
/** INSERT field before the pos=0 (e.g. at the top) */ /** INSERT field before the pos=0 (e.g. at the top) */
const topField = /** @type {RegisteringClass & prop} */ (await fixture(html` const topField = /** @type {RegisteringClass & prop} */ (
await fixture(html`
<${childTag}></${childTag}> <${childTag}></${childTag}>
`)); `)
);
topField.setAttribute('pos', 'inserted-before-0'); topField.setAttribute('pos', 'inserted-before-0');
el.insertBefore(topField, el.children[0]); el.insertBefore(topField, el.children[0]);
@ -159,9 +175,9 @@ export const runRegistrationSuite = customConfig => {
describe('FormRegistrarPortalMixin', () => { describe('FormRegistrarPortalMixin', () => {
it('forwards registrations to the .registrationTarget', async () => { it('forwards registrations to the .registrationTarget', async () => {
const el = /** @type {RegistrarClass} */ (await fixture( const el = /** @type {RegistrarClass} */ (
html`<${parentTag}></${parentTag}>`, await fixture(html`<${parentTag}></${parentTag}>`)
)); );
await fixture(html` await fixture(html`
<${portalTag} .registrationTarget=${el}> <${portalTag} .registrationTarget=${el}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
@ -172,9 +188,9 @@ export const runRegistrationSuite = customConfig => {
}); });
it('can dynamically add/remove elements', async () => { it('can dynamically add/remove elements', async () => {
const el = /** @type {RegistrarClass} */ (await fixture( const el = /** @type {RegistrarClass} */ (
html`<${parentTag}></${parentTag}>`, await fixture(html`<${parentTag}></${parentTag}>`)
)); );
const portal = await fixture(html` const portal = await fixture(html`
<${portalTag} .registrationTarget=${el}> <${portalTag} .registrationTarget=${el}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
@ -194,13 +210,15 @@ export const runRegistrationSuite = customConfig => {
}); });
it('adds elements to formElements in the right order', async () => { it('adds elements to formElements in the right order', async () => {
const el = /** @type {RegistrarClass} */ (await fixture(html` const el = /** @type {RegistrarClass} */ (
await fixture(html`
<${parentTag}> <${parentTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
</${parentTag}> </${parentTag}>
`)); `)
);
expect(el.formElements.length).to.equal(3); expect(el.formElements.length).to.equal(3);
@ -232,9 +250,9 @@ export const runRegistrationSuite = customConfig => {
}); });
it('keeps working if moving the portal itself', async () => { it('keeps working if moving the portal itself', async () => {
const el = /** @type {RegistrarClass} */ (await fixture( const el = /** @type {RegistrarClass} */ (
html`<${parentTag}></${parentTag}>`, await fixture(html`<${parentTag}></${parentTag}>`)
)); );
const portal = await fixture(html` const portal = await fixture(html`
<${portalTag} .registrationTarget=${el}> <${portalTag} .registrationTarget=${el}>
<${childTag}></${childTag}> <${childTag}></${childTag}>
@ -270,9 +288,9 @@ export const runRegistrationSuite = customConfig => {
); );
const delayedPortalTag = unsafeStatic(delayedPortalString); const delayedPortalTag = unsafeStatic(delayedPortalString);
const el = /** @type {RegistrarClass} */ (await fixture( const el = /** @type {RegistrarClass} */ (
html`<${parentTag}></${parentTag}>`, await fixture(html`<${parentTag}></${parentTag}>`)
)); );
await fixture(html` await fixture(html`
<${delayedPortalTag} .registrationTarget=${el}> <${delayedPortalTag} .registrationTarget=${el}>
<${childTag}></${childTag}> <${childTag}></${childTag}>

View file

@ -82,9 +82,9 @@ export function runInteractionStateMixinSuite(customConfig) {
}); });
it('sets an attribute "filled" if the input has a non-empty modelValue', async () => { it('sets an attribute "filled" if the input has a non-empty modelValue', async () => {
const el = /** @type {IState} */ (await fixture( const el = /** @type {IState} */ (
html`<${tag} .modelValue=${'hello'}></${tag}>`, await fixture(html`<${tag} .modelValue=${'hello'}></${tag}>`)
)); );
expect(el.hasAttribute('filled')).to.equal(true); expect(el.hasAttribute('filled')).to.equal(true);
el.modelValue = ''; el.modelValue = '';
await el.updateComplete; await el.updateComplete;
@ -97,9 +97,11 @@ export function runInteractionStateMixinSuite(customConfig) {
it('fires "(touched|dirty)-state-changed" event when state changes', async () => { it('fires "(touched|dirty)-state-changed" event when state changes', async () => {
const touchedSpy = sinon.spy(); const touchedSpy = sinon.spy();
const dirtySpy = sinon.spy(); const dirtySpy = sinon.spy();
const el = /** @type {IState} */ (await fixture( const el = /** @type {IState} */ (
await fixture(
html`<${tag} @touched-changed=${touchedSpy} @dirty-changed=${dirtySpy}></${tag}>`, html`<${tag} @touched-changed=${touchedSpy} @dirty-changed=${dirtySpy}></${tag}>`,
)); )
);
el.touched = true; el.touched = true;
expect(touchedSpy.callCount).to.equal(1); expect(touchedSpy.callCount).to.equal(1);
@ -109,14 +111,18 @@ export function runInteractionStateMixinSuite(customConfig) {
}); });
it('sets prefilled once instantiated', async () => { it('sets prefilled once instantiated', async () => {
const el = /** @type {IState} */ (await fixture(html` const el = /** @type {IState} */ (
await fixture(html`
<${tag} .modelValue=${'prefilled'}></${tag}> <${tag} .modelValue=${'prefilled'}></${tag}>
`)); `)
);
expect(el.prefilled).to.be.true; expect(el.prefilled).to.be.true;
const nonPrefilled = /** @type {IState} */ (await fixture(html` const nonPrefilled = /** @type {IState} */ (
await fixture(html`
<${tag} .modelValue=${''}></${tag}> <${tag} .modelValue=${''}></${tag}>
`)); `)
);
expect(nonPrefilled.prefilled).to.be.false; expect(nonPrefilled.prefilled).to.be.false;
}); });
@ -125,9 +131,9 @@ export function runInteractionStateMixinSuite(customConfig) {
(${cfg.allowedModelValueTypes.map(t => t.name).join(', ')})`, async () => { (${cfg.allowedModelValueTypes.map(t => t.name).join(', ')})`, async () => {
/** @typedef {{_inputNode: HTMLElement}} inputNodeInterface */ /** @typedef {{_inputNode: HTMLElement}} inputNodeInterface */
const el = /** @type {IState & inputNodeInterface} */ (await fixture( const el = /** @type {IState & inputNodeInterface} */ (
html`<${tag}></${tag}>`, await fixture(html`<${tag}></${tag}>`)
)); );
/** /**
* @param {*} modelValue * @param {*} modelValue
@ -213,9 +219,11 @@ export function runInteractionStateMixinSuite(customConfig) {
describe('Validation integration with states', () => { describe('Validation integration with states', () => {
it('has .shouldShowFeedbackFor indicating for which type to show messages', async () => { it('has .shouldShowFeedbackFor indicating for which type to show messages', async () => {
const el = /** @type {IState} */ (await fixture(html` const el = /** @type {IState} */ (
await fixture(html`
<${tag}></${tag}> <${tag}></${tag}>
`)); `)
);
// @ts-ignore [allow-private] in test // @ts-ignore [allow-private] in test
expect(el.shouldShowFeedbackFor).to.deep.equal([]); expect(el.shouldShowFeedbackFor).to.deep.equal([]);
el.submitted = true; el.submitted = true;
@ -225,9 +233,11 @@ export function runInteractionStateMixinSuite(customConfig) {
}); });
it('keeps the feedback component in sync', async () => { it('keeps the feedback component in sync', async () => {
const el = /** @type {IState} */ (await fixture(html` const el = /** @type {IState} */ (
await fixture(html`
<${tag} .validators=${[new MinLength(3)]}></${tag}> <${tag} .validators=${[new MinLength(3)]}></${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
await el.updateComplete; await el.updateComplete;
@ -257,9 +267,9 @@ export function runInteractionStateMixinSuite(customConfig) {
} }
const tagLeaveString = defineCE(IStateCustomBlur); const tagLeaveString = defineCE(IStateCustomBlur);
const tagLeave = unsafeStatic(tagLeaveString); const tagLeave = unsafeStatic(tagLeaveString);
const el = /** @type {IStateCustomBlur} */ (await fixture( const el = /** @type {IStateCustomBlur} */ (
html`<${tagLeave}></${tagLeave}>`, await fixture(html`<${tagLeave}></${tagLeave}>`)
)); );
el.dispatchEvent(new Event('custom-blur')); el.dispatchEvent(new Event('custom-blur'));
expect(el.touched).to.be.true; expect(el.touched).to.be.true;
}); });

View file

@ -66,9 +66,11 @@ export function runValidateMixinFeedbackPart() {
}); });
it('has .showsFeedbackFor indicating for which type it actually shows messages', async () => { it('has .showsFeedbackFor indicating for which type it actually shows messages', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} submitted .validators=${[new MinLength(3)]}>${lightDom}</${tag}> <${tag} submitted .validators=${[new MinLength(3)]}>${lightDom}</${tag}>
`)); `)
);
el.modelValue = 'a'; el.modelValue = 'a';
await el.feedbackComplete; await el.feedbackComplete;
@ -87,14 +89,16 @@ export function runValidateMixinFeedbackPart() {
} }
const elTagString = defineCE(ValidateElementCustomTypes); const elTagString = defineCE(ValidateElementCustomTypes);
const elTag = unsafeStatic(elTagString); const elTag = unsafeStatic(elTagString);
const el = /** @type {ValidateElementCustomTypes} */ (await fixture(html` const el = /** @type {ValidateElementCustomTypes} */ (
await fixture(html`
<${elTag} <${elTag}
.submitted=${true} .submitted=${true}
.validators=${[ .validators=${[
new MinLength(2, { type: 'x' }), new MinLength(2, { type: 'x' }),
new MinLength(3, { type: 'error' }), new MinLength(3, { type: 'error' }),
]}>${lightDom}</${elTag}> ]}>${lightDom}</${elTag}>
`)); `)
);
el.modelValue = '1'; el.modelValue = '1';
await el.updateComplete; await el.updateComplete;
@ -116,12 +120,14 @@ export function runValidateMixinFeedbackPart() {
}); });
it('passes a message to the "._feedbackNode"', async () => { it('passes a message to the "._feedbackNode"', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.modelValue=${'cat'} .modelValue=${'cat'}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
expect(_feedbackNode.feedbackData).to.deep.equal([]); expect(_feedbackNode.feedbackData).to.deep.equal([]);
@ -132,13 +138,15 @@ export function runValidateMixinFeedbackPart() {
}); });
it('has configurable feedback visibility hook', async () => { it('has configurable feedback visibility hook', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.modelValue=${'cat'} .modelValue=${'cat'}
.validators=${[new AlwaysInvalid()]} .validators=${[new AlwaysInvalid()]}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
await el.updateComplete; await el.updateComplete;
@ -153,13 +161,15 @@ export function runValidateMixinFeedbackPart() {
}); });
it('writes prioritized result to "._feedbackNode" based on Validator order', async () => { it('writes prioritized result to "._feedbackNode" based on Validator order', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.modelValue=${'cat'} .modelValue=${'cat'}
.validators=${[new AlwaysInvalid(), new MinLength(4)]} .validators=${[new AlwaysInvalid(), new MinLength(4)]}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
await el.updateComplete; await el.updateComplete;
@ -179,13 +189,15 @@ export function runValidateMixinFeedbackPart() {
return 'this ends up in "._feedbackNode"'; return 'this ends up in "._feedbackNode"';
}; };
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.modelValue=${'cat'} .modelValue=${'cat'}
.validators=${[new AlwaysInvalid()]} .validators=${[new AlwaysInvalid()]}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
expect(_feedbackNode.feedbackData).to.be.undefined; expect(_feedbackNode.feedbackData).to.be.undefined;
@ -208,13 +220,15 @@ export function runValidateMixinFeedbackPart() {
return 'this ends up in "._feedbackNode"'; return 'this ends up in "._feedbackNode"';
}; };
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.modelValue=${'cat'} .modelValue=${'cat'}
.validators=${[new AlwaysInvalid()]} .validators=${[new AlwaysInvalid()]}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
expect(_feedbackNode.feedbackData).to.be.undefined; expect(_feedbackNode.feedbackData).to.be.undefined;
@ -248,8 +262,9 @@ export function runValidateMixinFeedbackPart() {
render() { render() {
let name = ''; let name = '';
if (this.feedbackData && this.feedbackData.length > 0) { if (this.feedbackData && this.feedbackData.length > 0) {
const ctor = /** @type {typeof Validator} */ (this.feedbackData[0]?.validator const ctor = /** @type {typeof Validator} */ (
?.constructor); this.feedbackData[0]?.validator?.constructor
);
name = ctor.validatorName; name = ctor.validatorName;
} }
return html`Custom for ${name}`; return html`Custom for ${name}`;
@ -257,13 +272,15 @@ export function runValidateMixinFeedbackPart() {
} }
const customFeedbackTagString = defineCE(ValidateElementCustomRender); const customFeedbackTagString = defineCE(ValidateElementCustomRender);
const customFeedbackTag = unsafeStatic(customFeedbackTagString); const customFeedbackTag = unsafeStatic(customFeedbackTagString);
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[new ContainsLowercaseA(), new AlwaysInvalid()]}> .validators=${[new ContainsLowercaseA(), new AlwaysInvalid()]}>
<${customFeedbackTag} slot="feedback"><${customFeedbackTag}> <${customFeedbackTag} slot="feedback"><${customFeedbackTag}>
</${tag}> </${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
expect(_feedbackNode.localName).to.equal(customFeedbackTagString); expect(_feedbackNode.localName).to.equal(customFeedbackTagString);
@ -282,12 +299,14 @@ export function runValidateMixinFeedbackPart() {
}); });
it('supports custom messages in Validator instance configuration object', async () => { it('supports custom messages in Validator instance configuration object', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[new MinLength(3, { getMessage: () => 'custom via config' })]} .validators=${[new MinLength(3, { getMessage: () => 'custom via config' })]}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
el.modelValue = 'a'; el.modelValue = 'a';
@ -297,13 +316,15 @@ export function runValidateMixinFeedbackPart() {
}); });
it('updates the feedback component when locale changes', async () => { it('updates the feedback component when locale changes', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[new MinLength(3)]} .validators=${[new MinLength(3)]}
.modelValue=${'1'} .modelValue=${'1'}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
await el.feedbackComplete; await el.feedbackComplete;
@ -323,7 +344,8 @@ export function runValidateMixinFeedbackPart() {
} }
const elTagString = defineCE(ValidateElementCustomTypes); const elTagString = defineCE(ValidateElementCustomTypes);
const elTag = unsafeStatic(elTagString); const elTag = unsafeStatic(elTagString);
const el = /** @type {ValidateElementCustomTypes} */ (await fixture(html` const el = /** @type {ValidateElementCustomTypes} */ (
await fixture(html`
<${elTag} <${elTag}
.submitted=${true} .submitted=${true}
.validators=${[ .validators=${[
@ -331,7 +353,8 @@ export function runValidateMixinFeedbackPart() {
new DefaultSuccess(null, { getMessage: () => 'This is a success message' }), new DefaultSuccess(null, { getMessage: () => 'This is a success message' }),
]} ]}
>${lightDom}</${elTag}> >${lightDom}</${elTag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
el.modelValue = 'a'; el.modelValue = 'a';
@ -347,13 +370,15 @@ export function runValidateMixinFeedbackPart() {
describe('Accessibility', () => { describe('Accessibility', () => {
it('sets [aria-invalid="true"] to "._inputNode" when there is an error', async () => { it('sets [aria-invalid="true"] to "._inputNode" when there is an error', async () => {
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
submitted submitted
.validators=${[new Required()]} .validators=${[new Required()]}
.modelValue=${'a'} .modelValue=${'a'}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _inputNode } = getFormControlMembers(el); const { _inputNode } = getFormControlMembers(el);
const inputNode = _inputNode; const inputNode = _inputNode;
@ -386,13 +411,15 @@ export function runValidateMixinFeedbackPart() {
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor); const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
const constructorMessageSpy = sinon.spy(ctorValidator, 'getMessage'); const constructorMessageSpy = sinon.spy(ctorValidator, 'getMessage');
el = /** @type {ValidateElementCustomTypes} */ (await fixture(html` el = /** @type {ValidateElementCustomTypes} */ (
await fixture(html`
<${elTag} <${elTag}
.submitted=${true} .submitted=${true}
.validators=${[constructorValidator]} .validators=${[constructorValidator]}
.modelValue=${'cat'} .modelValue=${'cat'}
>${lightDom}</${elTag}> >${lightDom}</${elTag}>
`)); `)
);
await el.updateComplete; await el.updateComplete;
await el.feedbackComplete; await el.feedbackComplete;
expect(constructorMessageSpy.args[0][0]).to.eql({ expect(constructorMessageSpy.args[0][0]).to.eql({
@ -408,13 +435,15 @@ export function runValidateMixinFeedbackPart() {
const instanceMessageSpy = sinon.spy(); const instanceMessageSpy = sinon.spy();
const instanceValidator = new MinLength(4, { getMessage: instanceMessageSpy }); const instanceValidator = new MinLength(4, { getMessage: instanceMessageSpy });
el = /** @type {ValidateElementCustomTypes} */ (await fixture(html` el = /** @type {ValidateElementCustomTypes} */ (
await fixture(html`
<${elTag} <${elTag}
.submitted=${true} .submitted=${true}
.validators=${[instanceValidator]} .validators=${[instanceValidator]}
.modelValue=${'cat'} .modelValue=${'cat'}
>${lightDom}</${elTag}> >${lightDom}</${elTag}>
`)); `)
);
await el.updateComplete; await el.updateComplete;
await el.feedbackComplete; await el.feedbackComplete;
expect(instanceMessageSpy.args[0][0]).to.eql({ expect(instanceMessageSpy.args[0][0]).to.eql({
@ -435,14 +464,16 @@ export function runValidateMixinFeedbackPart() {
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor); const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
const spy = sinon.spy(ctorValidator, 'getMessage'); const spy = sinon.spy(ctorValidator, 'getMessage');
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[constructorValidator]} .validators=${[constructorValidator]}
.modelValue=${'cat'} .modelValue=${'cat'}
.fieldName=${new Promise(resolve => resolve('myField'))} .fieldName=${new Promise(resolve => resolve('myField'))}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
await el.updateComplete; await el.updateComplete;
await el.feedbackComplete; await el.feedbackComplete;
expect(spy.args[0][0]).to.eql({ expect(spy.args[0][0]).to.eql({
@ -464,14 +495,16 @@ export function runValidateMixinFeedbackPart() {
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor); const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
const spy = sinon.spy(ctorValidator, 'getMessage'); const spy = sinon.spy(ctorValidator, 'getMessage');
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[constructorValidator]} .validators=${[constructorValidator]}
.modelValue=${'cat'} .modelValue=${'cat'}
.fieldName=${new Promise(resolve => resolve('myField'))} .fieldName=${new Promise(resolve => resolve('myField'))}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
await el.updateComplete; await el.updateComplete;
await el.feedbackComplete; await el.feedbackComplete;
@ -500,13 +533,15 @@ export function runValidateMixinFeedbackPart() {
* The Queue system solves this by queueing the updateFeedbackComponent tasks and * The Queue system solves this by queueing the updateFeedbackComponent tasks and
* await them one by one. * await them one by one.
*/ */
const el = /** @type {ValidateElement} */ (await fixture(html` const el = /** @type {ValidateElement} */ (
await fixture(html`
<${tag} <${tag}
.submitted=${true} .submitted=${true}
.validators=${[new MinLength(3)]} .validators=${[new MinLength(3)]}
.modelValue=${'1'} .modelValue=${'1'}
>${lightDom}</${tag}> >${lightDom}</${tag}>
`)); `)
);
const { _feedbackNode } = getFormControlMembers(el); const { _feedbackNode } = getFormControlMembers(el);
el.modelValue = '12345'; el.modelValue = '12345';

View file

@ -22,9 +22,9 @@ import '@lion/input-stepper/define';
export class UmbrellaForm extends LitElement { export class UmbrellaForm extends LitElement {
get _lionFormNode() { get _lionFormNode() {
return /** @type {import('@lion/form').LionForm} */ (this.shadowRoot?.querySelector( return /** @type {import('@lion/form').LionForm} */ (
'lion-form', this.shadowRoot?.querySelector('lion-form')
)); );
} }
/** /**

View file

@ -18,30 +18,34 @@ describe('<lion-input-amount>', () => {
}); });
it('uses formatAmount for formatting', async () => { it('uses formatAmount for formatting', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount></lion-input-amount>`, await fixture(`<lion-input-amount></lion-input-amount>`)
)); );
expect(el.formatter).to.equal(formatAmount); expect(el.formatter).to.equal(formatAmount);
}); });
it('formatAmount uses currency provided on webcomponent', async () => { it('formatAmount uses currency provided on webcomponent', async () => {
// JOD displays 3 fraction digits by default // JOD displays 3 fraction digits by default
localize.locale = 'fr-FR'; localize.locale = 'fr-FR';
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
await fixture(
html`<lion-input-amount currency="JOD" .modelValue="${123}"></lion-input-amount>`, html`<lion-input-amount currency="JOD" .modelValue="${123}"></lion-input-amount>`,
)); )
);
expect(el.formattedValue).to.equal('123,000'); expect(el.formattedValue).to.equal('123,000');
}); });
it('formatAmount uses locale provided in formatOptions', async () => { it('formatAmount uses locale provided in formatOptions', async () => {
let el = /** @type {LionInputAmount} */ (await fixture( let el = /** @type {LionInputAmount} */ (
await fixture(
html` html`
<lion-input-amount <lion-input-amount
.formatOptions="${{ locale: 'en-GB' }}" .formatOptions="${{ locale: 'en-GB' }}"
.modelValue="${123}" .modelValue="${123}"
></lion-input-amount> ></lion-input-amount>
`, `,
)); )
);
expect(el.formattedValue).to.equal('123.00'); expect(el.formattedValue).to.equal('123.00');
el = await fixture( el = await fixture(
html` html`
@ -55,9 +59,11 @@ describe('<lion-input-amount>', () => {
}); });
it('ignores global locale change if property is provided', async () => { it('ignores global locale change if property is provided', async () => {
const el = /** @type {LionInputAmount} */ (await fixture(html` const el = /** @type {LionInputAmount} */ (
await fixture(html`
<lion-input-amount .modelValue=${123456.78} .locale="${'en-GB'}"></lion-input-amount> <lion-input-amount .modelValue=${123456.78} .locale="${'en-GB'}"></lion-input-amount>
`)); `)
);
expect(el.formattedValue).to.equal('123,456.78'); // British expect(el.formattedValue).to.equal('123,456.78'); // British
localize.locale = 'nl-NL'; localize.locale = 'nl-NL';
await aTimeout(0); await aTimeout(0);
@ -65,24 +71,24 @@ describe('<lion-input-amount>', () => {
}); });
it('uses parseAmount for parsing', async () => { it('uses parseAmount for parsing', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount></lion-input-amount>`, await fixture(`<lion-input-amount></lion-input-amount>`)
)); );
expect(el.parser).to.equal(parseAmount); expect(el.parser).to.equal(parseAmount);
}); });
it('sets inputmode attribute to decimal', async () => { it('sets inputmode attribute to decimal', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount></lion-input-amount>`, await fixture(`<lion-input-amount></lion-input-amount>`)
)); );
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el)); const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
expect(_inputNode.getAttribute('inputmode')).to.equal('decimal'); expect(_inputNode.getAttribute('inputmode')).to.equal('decimal');
}); });
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => { it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount></lion-input-amount>`, await fixture(`<lion-input-amount></lion-input-amount>`)
)); );
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el)); const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
expect(_inputNode.type).to.equal('text'); expect(_inputNode.type).to.equal('text');
}); });
@ -93,9 +99,9 @@ describe('<lion-input-amount>', () => {
}); });
it('displays currency if provided', async () => { it('displays currency if provided', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount currency="EUR"></lion-input-amount>`, await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
)); );
expect( expect(
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after') /** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after')
?.innerText, ?.innerText,
@ -104,9 +110,9 @@ describe('<lion-input-amount>', () => {
it('displays correct currency for TRY if locale is tr-TR', async () => { it('displays correct currency for TRY if locale is tr-TR', async () => {
localize.locale = 'tr-TR'; localize.locale = 'tr-TR';
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount currency="TRY"></lion-input-amount>`, await fixture(`<lion-input-amount currency="TRY"></lion-input-amount>`)
)); );
expect( expect(
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after') /** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after')
?.innerText, ?.innerText,
@ -114,9 +120,9 @@ describe('<lion-input-amount>', () => {
}); });
it('can update currency', async () => { it('can update currency', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount currency="EUR"></lion-input-amount>`, await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
)); );
el.currency = 'USD'; el.currency = 'USD';
await el.updateComplete; await el.updateComplete;
expect( expect(
@ -126,9 +132,11 @@ describe('<lion-input-amount>', () => {
}); });
it('ignores currency if a suffix is already present', async () => { it('ignores currency if a suffix is already present', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
await fixture(
`<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`, `<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`,
)); )
);
expect( expect(
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'suffix') /** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'suffix')
?.innerText, ?.innerText,
@ -143,18 +151,18 @@ describe('<lion-input-amount>', () => {
describe('Accessibility', () => { describe('Accessibility', () => {
it('adds currency id to aria-labelledby of input', async () => { it('adds currency id to aria-labelledby of input', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount currency="EUR"></lion-input-amount>`, await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
)); );
expect(el._currencyDisplayNode?.getAttribute('data-label')).to.be.not.null; expect(el._currencyDisplayNode?.getAttribute('data-label')).to.be.not.null;
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el)); const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
expect(_inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode?.id); expect(_inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode?.id);
}); });
it('adds an aria-label to currency slot', async () => { it('adds an aria-label to currency slot', async () => {
const el = /** @type {LionInputAmount} */ (await fixture( const el = /** @type {LionInputAmount} */ (
`<lion-input-amount currency="EUR"></lion-input-amount>`, await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
)); );
expect(el._currencyDisplayNode?.getAttribute('aria-label')).to.equal('euros'); expect(el._currencyDisplayNode?.getAttribute('aria-label')).to.equal('euros');
el.currency = 'USD'; el.currency = 'USD';
await el.updateComplete; await el.updateComplete;

View file

@ -57,13 +57,15 @@ export class DatepickerInputObject {
} }
get overlayHeadingEl() { get overlayHeadingEl() {
return /** @type {HTMLElement} */ (this.overlayEl && return /** @type {HTMLElement} */ (
this.overlayEl.shadowRoot?.querySelector('.calendar-overlay__heading')); this.overlayEl && this.overlayEl.shadowRoot?.querySelector('.calendar-overlay__heading')
);
} }
get overlayCloseButtonEl() { get overlayCloseButtonEl() {
return /** @type {HTMLElement} */ (this.calendarEl && return /** @type {HTMLElement} */ (
this.overlayEl.shadowRoot?.querySelector('#close-button')); this.calendarEl && this.overlayEl.shadowRoot?.querySelector('#close-button')
);
} }
get calendarEl() { get calendarEl() {

View file

@ -235,9 +235,9 @@ export class LocalizeManager {
loadNamespace(namespaceObj, { locale = this.locale } = { locale: this.locale }) { loadNamespace(namespaceObj, { locale = this.locale } = { locale: this.locale }) {
const isDynamicImport = typeof namespaceObj === 'object'; const isDynamicImport = typeof namespaceObj === 'object';
const namespace = /** @type {string} */ (isDynamicImport const namespace = /** @type {string} */ (
? Object.keys(namespaceObj)[0] isDynamicImport ? Object.keys(namespaceObj)[0] : namespaceObj
: namespaceObj); );
if (this._isNamespaceInCache(locale, namespace)) { if (this._isNamespaceInCache(locale, namespace)) {
return Promise.resolve(); return Promise.resolve();

View file

@ -11,12 +11,14 @@ import { forceCurrencyNameForPHPEnGB } from './utils/normalize-get-currency-name
* @returns {string} currency name like 'US dollar' * @returns {string} currency name like 'US dollar'
*/ */
export function getCurrencyName(currencyIso, options) { export function getCurrencyName(currencyIso, options) {
const parts = /** @type {FormatNumberPart[]} */ (formatNumberToParts(1, { const parts = /** @type {FormatNumberPart[]} */ (
formatNumberToParts(1, {
...options, ...options,
style: 'currency', style: 'currency',
currency: currencyIso, currency: currencyIso,
currencyDisplay: 'name', currencyDisplay: 'name',
})); })
);
let currencyName = parts let currencyName = parts
.filter(p => p.type === 'currency') .filter(p => p.type === 'currency')
.map(o => o.value) .map(o => o.value)

View file

@ -9,10 +9,12 @@ import { formatNumberToParts } from './formatNumberToParts.js';
* @returns {number} fraction for the given currency * @returns {number} fraction for the given currency
*/ */
export function getFractionDigits(currency = 'EUR') { export function getFractionDigits(currency = 'EUR') {
const parts = /** @type {FormatNumberPart[]} */ (formatNumberToParts(123, { const parts = /** @type {FormatNumberPart[]} */ (
formatNumberToParts(123, {
style: 'currency', style: 'currency',
currency, currency,
})); })
);
const [fractionPart] = parts.filter(part => part.type === 'fraction'); const [fractionPart] = parts.filter(part => part.type === 'fraction');
return fractionPart ? fractionPart.value.length : 0; return fractionPart ? fractionPart.value.length : 0;
} }

View file

@ -11,10 +11,8 @@ import { LocalizeManager } from '../src/LocalizeManager.js';
*/ */
function getProtectedMembers(localizeManagerEl) { function getProtectedMembers(localizeManagerEl) {
// @ts-ignore // @ts-ignore
const { const { __storage: storage, _supportExternalTranslationTools: supportExternalTranslationTools } =
__storage: storage, localizeManagerEl;
_supportExternalTranslationTools: supportExternalTranslationTools,
} = localizeManagerEl;
return { return {
storage, storage,
supportExternalTranslationTools, supportExternalTranslationTools,

View file

@ -9,10 +9,8 @@ import { localize, setLocalize } from '../src/localize.js';
*/ */
function getProtectedMembers(localizeManagerEl) { function getProtectedMembers(localizeManagerEl) {
// @ts-ignore // @ts-ignore
const { const { _autoLoadOnLocaleChange: autoLoadOnLocaleChange, _fallbackLocale: fallbackLocale } =
_autoLoadOnLocaleChange: autoLoadOnLocaleChange, localizeManagerEl;
_fallbackLocale: fallbackLocale,
} = localizeManagerEl;
return { return {
autoLoadOnLocaleChange, autoLoadOnLocaleChange,
fallbackLocale, fallbackLocale,

View file

@ -244,8 +244,9 @@ export class OverlayController extends EventTargetShim {
* @type {HTMLElement} * @type {HTMLElement}
*/ */
get contentWrapperNode() { get contentWrapperNode() {
return /** @type {HTMLElement} */ (this.__contentWrapperNode || return /** @type {HTMLElement} */ (
this.config?.contentWrapperNode); this.__contentWrapperNode || this.config?.contentWrapperNode
);
} }
/** /**
@ -262,8 +263,9 @@ export class OverlayController extends EventTargetShim {
* @type {HTMLElement} * @type {HTMLElement}
*/ */
get elementToFocusAfterHide() { get elementToFocusAfterHide() {
return /** @type {HTMLElement} */ (this.__elementToFocusAfterHide || return /** @type {HTMLElement} */ (
this.config?.elementToFocusAfterHide); this.__elementToFocusAfterHide || this.config?.elementToFocusAfterHide
);
} }
/** /**
@ -588,8 +590,9 @@ export class OverlayController extends EventTargetShim {
if (this.__isContentNodeProjected && this.contentWrapperNode.isConnected) { if (this.__isContentNodeProjected && this.contentWrapperNode.isConnected) {
// We need to keep track of the original local context. // We need to keep track of the original local context.
/** config [l2], [l4] */ /** config [l2], [l4] */
this.__originalContentParent = /** @type {HTMLElement} */ (this.contentWrapperNode this.__originalContentParent = /** @type {HTMLElement} */ (
.parentNode); this.contentWrapperNode.parentNode
);
} else if (cfgToAdd.contentNode && cfgToAdd.contentNode.isConnected) { } else if (cfgToAdd.contentNode && cfgToAdd.contentNode.isConnected) {
// We need to keep track of the original local context. // We need to keep track of the original local context.
/** config [l1], [l3], [g1] */ /** config [l1], [l3], [g1] */
@ -905,8 +908,9 @@ export class OverlayController extends EventTargetShim {
/** @protected */ /** @protected */
_restoreFocus() { _restoreFocus() {
const { activeElement } = /** @type {* & ShadowRoot} */ (this const { activeElement } = /** @type {* & ShadowRoot} */ (
.__contentWrapperNode).getRootNode(); this.__contentWrapperNode
).getRootNode();
// We only are allowed to move focus if we (still) 'own' it. // We only are allowed to move focus if we (still) 'own' it.
// Otherwise we assume the 'outside world' has, purposefully, taken over // Otherwise we assume the 'outside world' has, purposefully, taken over
if ( if (

View file

@ -49,12 +49,14 @@ describe('ArrowMixin', () => {
}); });
it('shows by default', async () => { it('shows by default', async () => {
const el = /** @type {ArrowTest} */ (await fixture(html` const el = /** @type {ArrowTest} */ (
await fixture(html`
<arrow-test> <arrow-test>
<div slot="content">This is a tooltip</div> <div slot="content">This is a tooltip</div>
<button slot="invoker">Tooltip button</button> <button slot="invoker">Tooltip button</button>
</arrow-test> </arrow-test>
`)); `)
);
expect(el.hasAttribute('has-arrow')).to.be.true; expect(el.hasAttribute('has-arrow')).to.be.true;
const arrowNode = /** @type {Element} */ (el._arrowNode); const arrowNode = /** @type {Element} */ (el._arrowNode);
@ -62,12 +64,14 @@ describe('ArrowMixin', () => {
}); });
it('hides the arrow when has-arrow is false', async () => { it('hides the arrow when has-arrow is false', async () => {
const el = /** @type {ArrowTest} */ (await fixture(html` const el = /** @type {ArrowTest} */ (
await fixture(html`
<arrow-test> <arrow-test>
<div slot="content">This is a tooltip</div> <div slot="content">This is a tooltip</div>
<button slot="invoker">Tooltip button</button> <button slot="invoker">Tooltip button</button>
</arrow-test> </arrow-test>
`)); `)
);
el.hasArrow = false; el.hasArrow = false;
await el.updateComplete; await el.updateComplete;
expect(el.hasAttribute('has-arrow')).to.be.false; expect(el.hasAttribute('has-arrow')).to.be.false;
@ -76,7 +80,8 @@ describe('ArrowMixin', () => {
}); });
it('makes sure positioning of the arrow is correct', async () => { it('makes sure positioning of the arrow is correct', async () => {
const el = /** @type {ArrowTest} */ (await fixture(html` const el = /** @type {ArrowTest} */ (
await fixture(html`
<arrow-test <arrow-test
.config="${ .config="${
/** @type {import('../types/OverlayConfig').OverlayConfig} */ ({ /** @type {import('../types/OverlayConfig').OverlayConfig} */ ({
@ -90,7 +95,8 @@ describe('ArrowMixin', () => {
<div slot="content" style="height: 30px; background-color: red;">Hey there</div> <div slot="content" style="height: 30px; background-color: red;">Hey there</div>
<button slot="invoker" style="height: 30px;">Tooltip button</button> <button slot="invoker" style="height: 30px;">Tooltip button</button>
</arrow-test> </arrow-test>
`)); `)
);
el.opened = true; el.opened = true;

View file

@ -13,12 +13,14 @@ describe('deepContains()', () => {
<input id="el-3"> <input id="el-3">
`; `;
const shadowElementChild = /** @type {HTMLElement} */ (shadowRoot.querySelector('#el-1')); const shadowElementChild = /** @type {HTMLElement} */ (shadowRoot.querySelector('#el-1'));
const element = /** @type {HTMLElement} */ (await fixture(html` const element = /** @type {HTMLElement} */ (
await fixture(html`
<div id="light"> <div id="light">
${shadowElement} ${shadowElement}
<button id="light-el-1"></button> <button id="light-el-1"></button>
</div> </div>
`)); `)
);
const lightChildEl = /** @type {HTMLElement} */ (element.querySelector('#light-el-1')); const lightChildEl = /** @type {HTMLElement} */ (element.querySelector('#light-el-1'));
expect(deepContains(element, element)).to.be.true; expect(deepContains(element, element)).to.be.true;
@ -59,12 +61,14 @@ describe('deepContains()', () => {
const shadowElementChild = /** @type {HTMLElement} */ (shadowRoot.querySelector('#el-2')); const shadowElementChild = /** @type {HTMLElement} */ (shadowRoot.querySelector('#el-2'));
const shadowElementChild2 = /** @type {HTMLElement} */ (shadowRoot2.querySelector('#el-2')); const shadowElementChild2 = /** @type {HTMLElement} */ (shadowRoot2.querySelector('#el-2'));
const element = /** @type {HTMLElement} */ (await fixture(html` const element = /** @type {HTMLElement} */ (
await fixture(html`
<div id="light"> <div id="light">
${shadowElement} ${shadowElement2} ${shadowElement} ${shadowElement2}
<button id="light-el-1"></button> <button id="light-el-1"></button>
</div> </div>
`)); `)
);
expect(deepContains(element, shadowElementChild)).to.be.true; expect(deepContains(element, shadowElementChild)).to.be.true;
expect(deepContains(shadowElement, shadowElementChild)).to.be.true; expect(deepContains(shadowElement, shadowElementChild)).to.be.true;
@ -93,21 +97,26 @@ describe('deepContains()', () => {
`; `;
shadowRoot.insertBefore(shadowNestedElement, shadowRoot.lastElementChild); shadowRoot.insertBefore(shadowNestedElement, shadowRoot.lastElementChild);
const element = /** @type {HTMLElement} */ (await fixture(html` const element = /** @type {HTMLElement} */ (
await fixture(html`
<div id="light"> <div id="light">
${shadowElement} ${shadowElement}
<button id="light-el-1"></button> <button id="light-el-1"></button>
</div> </div>
`)); `)
);
const elementFirstChild = /** @type {HTMLElement} */ (element.firstElementChild); const elementFirstChild = /** @type {HTMLElement} */ (element.firstElementChild);
const elementFirstChildShadow = /** @type {ShadowRoot} */ (elementFirstChild.shadowRoot); const elementFirstChildShadow = /** @type {ShadowRoot} */ (elementFirstChild.shadowRoot);
const elementFirstChildShadowChildren = /** @type {HTMLElement[]} */ (Array.from( const elementFirstChildShadowChildren = /** @type {HTMLElement[]} */ (
elementFirstChildShadow.children, Array.from(elementFirstChildShadow.children)
)); );
const elementFirstChildShadowChildShadow = /** @type {ShadowRoot} */ (elementFirstChildShadowChildren[1] const elementFirstChildShadowChildShadow = /** @type {ShadowRoot} */ (
.shadowRoot); elementFirstChildShadowChildren[1].shadowRoot
const elementFirstChildShadowChildShadowLastChild = /** @type {HTMLElement} */ (elementFirstChildShadowChildShadow.lastElementChild); );
const elementFirstChildShadowChildShadowLastChild = /** @type {HTMLElement} */ (
elementFirstChildShadowChildShadow.lastElementChild
);
expect(deepContains(element, elementFirstChild)).to.be.true; expect(deepContains(element, elementFirstChild)).to.be.true;
expect(deepContains(element, elementFirstChildShadow)).to.be.true; expect(deepContains(element, elementFirstChildShadow)).to.be.true;

View file

@ -4,65 +4,77 @@ import { isVisible } from '../../src/utils/is-visible.js';
describe('isVisible()', () => { describe('isVisible()', () => {
it('returns true for static block elements', async () => { it('returns true for static block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
`<div style="width:10px; height:10px;"></div>`, await fixture(`<div style="width:10px; height:10px;"></div>`)
)); );
expect(isVisible(element)).to.equal(true); expect(isVisible(element)).to.equal(true);
}); });
it('returns false for hidden static block elements', async () => { it('returns false for hidden static block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
`<div style="width:10px; height:10px;" hidden></div>`, await fixture(`<div style="width:10px; height:10px;" hidden></div>`)
)); );
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
it('returns true for relative block elements', async () => { it('returns true for relative block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
await fixture(
`<div style="width:10px; height:10px; position:relative; top:10px; left:10px;"></div>`, `<div style="width:10px; height:10px; position:relative; top:10px; left:10px;"></div>`,
)); )
);
expect(isVisible(element)).to.equal(true); expect(isVisible(element)).to.equal(true);
}); });
it('returns false for hidden relative block elements', async () => { it('returns false for hidden relative block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
await fixture(
`<div style="width:10px; height:10px; position:relative; top:10px; left:10px;" hidden></div>`, `<div style="width:10px; height:10px; position:relative; top:10px; left:10px;" hidden></div>`,
)); )
);
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
it('returns true for absolute block elements', async () => { it('returns true for absolute block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div style="width:10px; height:10px; position:absolute; top:10px; left:10px;"></div> <div style="width:10px; height:10px; position:absolute; top:10px; left:10px;"></div>
`)); `)
);
expect(isVisible(element)).to.equal(true); expect(isVisible(element)).to.equal(true);
}); });
it('returns false for hidden absolute block elements', async () => { it('returns false for hidden absolute block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div style="width:10px; height:10px; position:absolute; top:10px; left:10px;" hidden></div> <div style="width:10px; height:10px; position:absolute; top:10px; left:10px;" hidden></div>
`)); `)
);
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
it('returns true for relative block elements', async () => { it('returns true for relative block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div style="width:10px; height:10px; position:fixed;top:10px; left:10px;"></div> <div style="width:10px; height:10px; position:fixed;top:10px; left:10px;"></div>
`)); `)
);
expect(isVisible(element)).to.equal(true); expect(isVisible(element)).to.equal(true);
}); });
it('returns true for relative block elements', async () => { it('returns true for relative block elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div style="width:10px; height:10px; position:fixed;top:10px; left:10px;" hidden></div> <div style="width:10px; height:10px; position:fixed;top:10px; left:10px;" hidden></div>
`)); `)
);
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
@ -80,48 +92,52 @@ describe('isVisible()', () => {
}); });
it('returns true for static block elements with 0 dimensions', async () => { it('returns true for static block elements with 0 dimensions', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
`<div style="width:0; height:0;"></div>`, await fixture(`<div style="width:0; height:0;"></div>`)
)); );
expect(isVisible(element)).to.equal(true); expect(isVisible(element)).to.equal(true);
}); });
it('returns false for hidden inline elements', async () => { it('returns false for hidden inline elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
`<span hidden>Inline content</span>`, await fixture(`<span hidden>Inline content</span>`)
)); );
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
it('returns false invisible elements', async () => { it('returns false invisible elements', async () => {
const element = /** @type {HTMLElement} */ (await fixture( const element = /** @type {HTMLElement} */ (
`<div style="width:10px; height:10px; visibility: hidden;"></div>`, await fixture(`<div style="width:10px; height:10px; visibility: hidden;"></div>`)
)); );
expect(isVisible(element)).to.equal(false); expect(isVisible(element)).to.equal(false);
}); });
it('returns false when hidden by parent', async () => { it('returns false when hidden by parent', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div hidden> <div hidden>
<div id="target" style="width:10px; height:10px;"></div> <div id="target" style="width:10px; height:10px;"></div>
<div></div> <div></div>
</div> </div>
`)); `)
);
const target = /** @type {HTMLElement} */ (element.querySelector('#target')); const target = /** @type {HTMLElement} */ (element.querySelector('#target'));
expect(isVisible(target)).to.equal(false); expect(isVisible(target)).to.equal(false);
}); });
it('returns false when invisible by parent', async () => { it('returns false when invisible by parent', async () => {
const element = /** @type {HTMLElement} */ (await fixture(` const element = /** @type {HTMLElement} */ (
await fixture(`
<div style="visibility: hidden;"> <div style="visibility: hidden;">
<div id="target" style="width:10px; height:10px;"></div> <div id="target" style="width:10px; height:10px;"></div>
<div></div> <div></div>
</div> </div>
`)); `)
);
const target = /** @type {HTMLElement} */ (element.querySelector('#target')); const target = /** @type {HTMLElement} */ (element.querySelector('#target'));
expect(isVisible(target)).to.equal(false); expect(isVisible(target)).to.equal(false);

View file

@ -21,9 +21,9 @@ class LionFieldWithSelect extends LionField {
* @returns {HTMLSelectElement} * @returns {HTMLSelectElement}
*/ */
get _inputNode() { get _inputNode() {
return /** @type {HTMLSelectElement} */ (Array.from(this.children).find( return /** @type {HTMLSelectElement} */ (
el => el.slot === 'input', Array.from(this.children).find(el => el.slot === 'input')
)); );
} }
} }

View file

@ -100,9 +100,9 @@ export class LionSteps extends LitElement {
} }
get steps() { get steps() {
const defaultSlot = /** @type {HTMLSlotElement} */ (this.shadowRoot?.querySelector( const defaultSlot = /** @type {HTMLSlotElement} */ (
'slot:not([name])', this.shadowRoot?.querySelector('slot:not([name])')
)); );
return /** @type {LionStep[]} */ (defaultSlot.assignedNodes()).filter( return /** @type {LionStep[]} */ (defaultSlot.assignedNodes()).filter(
node => node.nodeType === Node.ELEMENT_NODE, node => node.nodeType === Node.ELEMENT_NODE,
); );

View file

@ -33,9 +33,9 @@ export class LionSwitch extends ScopedElementsMixin(ChoiceInputMixin(LionField))
*/ */
// @ts-ignore [editor]: prevents vscode from complaining // @ts-ignore [editor]: prevents vscode from complaining
get _inputNode() { get _inputNode() {
return /** @type {LionSwitchButton} */ (Array.from(this.children).find( return /** @type {LionSwitchButton} */ (
el => el.slot === 'input', Array.from(this.children).find(el => el.slot === 'input')
)); );
} }
get slots() { get slots() {

View file

@ -323,12 +323,16 @@ export class LionTabs extends LitElement {
) { ) {
return; return;
} }
const previousButton = /** @type {HTMLElement} */ (Array.from(this.children).find( const previousButton = /** @type {HTMLElement} */ (
Array.from(this.children).find(
child => child.slot === 'tab' && child.hasAttribute('selected'), child => child.slot === 'tab' && child.hasAttribute('selected'),
)); )
const previousPanel = /** @type {HTMLElement} */ (Array.from(this.children).find( );
const previousPanel = /** @type {HTMLElement} */ (
Array.from(this.children).find(
child => child.slot === 'panel' && child.hasAttribute('selected'), child => child.slot === 'panel' && child.hasAttribute('selected'),
)); )
);
if (previousButton) { if (previousButton) {
deselectButton(previousButton); deselectButton(previousButton);
} }