Merge pull request #1370 from ing-bank/feat/lit2Update
feat: update to lit 2
This commit is contained in:
commit
2b73c50f6e
158 changed files with 5448 additions and 4148 deletions
40
.changeset/fuzzy-snails-attack.md
Normal file
40
.changeset/fuzzy-snails-attack.md
Normal 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
|
||||
|
|
@ -16,3 +16,11 @@ trim_trailing_whitespace = false
|
|||
block_comment_start = /**
|
||||
block_comment = *
|
||||
block_comment_end = */
|
||||
|
||||
[*.{d.ts,patch,editorconfig}]
|
||||
charset = unset
|
||||
indent_style = unset
|
||||
indent_size = unset
|
||||
end_of_line = unset
|
||||
insert_final_newline = unset
|
||||
trim_trailing_whitespace = unset
|
||||
|
|
@ -7,3 +7,4 @@ storybook-static/
|
|||
_site-dev
|
||||
_site
|
||||
docs/_merged_*
|
||||
patches/
|
||||
|
|
|
|||
2
.github/workflows/verify.yml
vendored
2
.github/workflows/verify.yml
vendored
|
|
@ -72,7 +72,7 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x]
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ import { lazyRender } from './src/lazyRender.js';
|
|||
export const main = () => html`
|
||||
<lion-combobox name="combo" label="Default">
|
||||
${lazyRender(
|
||||
listboxData.map(entry => html` <lion-option .choiceValue="${entry}">${entry}</lion-option> `),
|
||||
listboxData.map(
|
||||
(entry, i) =>
|
||||
html` <lion-option .checked="${i === 0}" .choiceValue="${entry}">${entry}</lion-option> `,
|
||||
),
|
||||
)}
|
||||
</lion-combobox>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { directive } from '@lion/core';
|
||||
import { directive } from 'lit/directive.js';
|
||||
import { AsyncDirective } from 'lit/async-directive.js';
|
||||
|
||||
/**
|
||||
* In order to speed up the first meaningful paint, use this directive
|
||||
|
|
@ -15,9 +16,14 @@ import { directive } from '@lion/core';
|
|||
* )}
|
||||
* </lion-combobox>
|
||||
*/
|
||||
export const lazyRender = directive(tplResult => part => {
|
||||
setTimeout(() => {
|
||||
part.setValue(tplResult);
|
||||
part.commit();
|
||||
});
|
||||
});
|
||||
export const lazyRender = directive(
|
||||
class extends AsyncDirective {
|
||||
render(tplResult) {
|
||||
setTimeout(() => {
|
||||
this.setValue(tplResult);
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// export const lazyRender = () => {};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable*/
|
||||
/* eslint-disable */
|
||||
// https://github.com/gustf/js-levenshtein/blob/master/index.js
|
||||
|
||||
function _min(d0, d1, d2, bx, ay) {
|
||||
|
|
|
|||
|
|
@ -36,9 +36,8 @@ There are the following methods available to control the pagination.
|
|||
```js preview-story
|
||||
export const methods = ({ shadowRoot }) => {
|
||||
setTimeout(() => {
|
||||
shadowRoot.getElementById('pagination-method-demo').innerText = shadowRoot.getElementById(
|
||||
'pagination-method',
|
||||
).current;
|
||||
shadowRoot.getElementById('pagination-method-demo').innerText =
|
||||
shadowRoot.getElementById('pagination-method').current;
|
||||
});
|
||||
|
||||
return html`
|
||||
|
|
@ -80,9 +79,8 @@ export const methods = ({ shadowRoot }) => {
|
|||
```js preview-story
|
||||
export const event = ({ shadowRoot }) => {
|
||||
setTimeout(() => {
|
||||
shadowRoot.getElementById('pagination-event-demo-text').innerText = shadowRoot.getElementById(
|
||||
'pagination-event-demo',
|
||||
).current;
|
||||
shadowRoot.getElementById('pagination-event-demo-text').innerText =
|
||||
shadowRoot.getElementById('pagination-event-demo').current;
|
||||
});
|
||||
|
||||
return html`
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ import '@lion/button/define';
|
|||
|
||||
export class UmbrellaForm extends LitElement {
|
||||
get _lionFormNode() {
|
||||
return /** @type {import('@lion/form').LionForm} */ (this.shadowRoot?.querySelector(
|
||||
'lion-form',
|
||||
));
|
||||
return /** @type {import('@lion/form').LionForm} */ (
|
||||
this.shadowRoot?.querySelector('lion-form')
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import {
|
|||
import './assets/demo-overlay-system.js';
|
||||
import './assets/demo-overlay-backdrop.js';
|
||||
import './assets/applyDemoOverlayStyles.js';
|
||||
import { ref as r } from './assets/ref.js';
|
||||
import { ref, createRef } from 'lit/directives/ref.js';
|
||||
```
|
||||
|
||||
The overlay system allows to create different types of overlays like dialogs, toasts, tooltips, dropdown, etc.
|
||||
|
|
@ -388,14 +388,21 @@ export const openedState = () => {
|
|||
const appState = {
|
||||
opened: false,
|
||||
};
|
||||
const refs = {};
|
||||
const myRefs = {
|
||||
overlay: createRef(),
|
||||
openedState: createRef(),
|
||||
};
|
||||
function onOpenClosed(ev) {
|
||||
appState.opened = ev.target.opened;
|
||||
refs.openedState.innerText = appState.opened;
|
||||
myRefs.openedState.value.innerText = appState.opened;
|
||||
}
|
||||
return html`
|
||||
appState.opened: <span #openedState=${r(refs)}>${appState.opened}</span>
|
||||
<demo-overlay-system .opened="${appState.opened}" @opened-changed=${onOpenClosed}>
|
||||
appState.opened: <span ${ref(myRefs.openedState)}>${appState.opened}</span>
|
||||
<demo-overlay-system
|
||||
${ref(myRefs.overlay)}
|
||||
.opened="${appState.opened}"
|
||||
@opened-changed=${onOpenClosed}
|
||||
>
|
||||
<button slot="invoker">Overlay</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
|
|
@ -419,7 +426,10 @@ the `before-close` or `before-open` events.
|
|||
export const interceptingOpenClose = () => {
|
||||
// Application code
|
||||
let blockOverlay = true;
|
||||
const refs = {};
|
||||
const myRefs = {
|
||||
statusButton: createRef(),
|
||||
overlay: createRef(),
|
||||
};
|
||||
function intercept(ev) {
|
||||
if (blockOverlay) {
|
||||
ev.preventDefault();
|
||||
|
|
@ -428,28 +438,29 @@ export const interceptingOpenClose = () => {
|
|||
return html`
|
||||
Overlay blocked state:
|
||||
<button
|
||||
#statusButton=${r(refs)}
|
||||
${ref(myRefs.statusButton)}
|
||||
@click="${() => {
|
||||
blockOverlay = !blockOverlay;
|
||||
refs.statusButton.textContent = blockOverlay;
|
||||
myRefs.statusButton.value.textContent = blockOverlay;
|
||||
}}"
|
||||
>
|
||||
${blockOverlay}
|
||||
</button>
|
||||
<demo-overlay-system
|
||||
#overlay=${r(refs)}
|
||||
${ref(myRefs.overlay)}
|
||||
@before-closed=${intercept}
|
||||
@before-opened=${intercept}
|
||||
>
|
||||
<button
|
||||
slot="invoker"
|
||||
@click=${() => console.log('blockOverlay', blockOverlay, 'opened', refs.overlay.opened)}
|
||||
@click=${() =>
|
||||
console.log('blockOverlay', blockOverlay, 'opened', myRefs.overlay.value.opened)}
|
||||
>
|
||||
Overlay
|
||||
</button>
|
||||
<div slot="content" class="demo-overlay">
|
||||
Hello! You can close this notification here:
|
||||
<button @click=${() => (refs.overlay.opened = false)}>⨯</button>
|
||||
<button @click=${() => (myRefs.overlay.value.opened = false)}>⨯</button>
|
||||
</div>
|
||||
</demo-overlay-system>
|
||||
`;
|
||||
|
|
|
|||
20
package.json
20
package.json
|
|
@ -2,10 +2,6 @@
|
|||
"private": true,
|
||||
"name": "@lion/root",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages-node/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rocket build",
|
||||
"build:types": "tsc -p tsconfig.build.types.json",
|
||||
|
|
@ -17,7 +13,7 @@
|
|||
"format": "npm run format:eslint && npm run format:prettier",
|
||||
"format:eslint": "eslint --ext .js,.html . --fix",
|
||||
"format:prettier": "prettier \"**/*.{js,md}\" \"packages/*/package.json\" \"package.json\" --write",
|
||||
"postinstall": "npm run custom-elements-manifest",
|
||||
"postinstall": "npm run custom-elements-manifest && patch-package",
|
||||
"lint": "run-p lint:*",
|
||||
"lint:eclint": "git ls-files | xargs eclint check",
|
||||
"lint:eslint": "eslint --ext .js,.html .",
|
||||
|
|
@ -36,6 +32,10 @@
|
|||
"test:screenshots": "rimraf screenshots/.diff/ && rimraf screenshots/.current/ && mocha --require scripts/screenshots/bootstrap.js --exit --timeout 10000 \"packages/**/test/*.screenshots-test.js\"",
|
||||
"test:screenshots:update": "cross-env UPDATE_SCREENSHOTS=true npm run test:screenshots"
|
||||
},
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"packages-node/*"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.10.1",
|
||||
"@bundled-es-modules/fetch-mock": "^6.5.2",
|
||||
|
|
@ -45,8 +45,8 @@
|
|||
"@custom-elements-manifest/analyzer": "^0.1.8",
|
||||
"@open-wc/building-rollup": "^1.2.1",
|
||||
"@open-wc/eslint-config": "^4.2.0",
|
||||
"@open-wc/testing": "^2.5.18",
|
||||
"@open-wc/testing-helpers": "^1.0.0",
|
||||
"@open-wc/testing": "^3.0.0-next.1",
|
||||
"@open-wc/testing-helpers": "^2.0.0-next.0",
|
||||
"@rocket/blog": "^0.3.0",
|
||||
"@rocket/cli": "^0.6.2",
|
||||
"@rocket/launch": "^0.4.0",
|
||||
|
|
@ -59,9 +59,9 @@
|
|||
"@types/prettier": "^2.2.1",
|
||||
"@web/dev-server": "^0.1.8",
|
||||
"@web/dev-server-legacy": "^0.1.7",
|
||||
"@web/test-runner": "^0.12.15",
|
||||
"@web/test-runner": "^0.13.4",
|
||||
"@web/test-runner-browserstack": "^0.4.2",
|
||||
"@web/test-runner-playwright": "^0.8.4",
|
||||
"@web/test-runner-playwright": "^0.8.6",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"bundlesize": "^1.0.0-beta.2",
|
||||
"chai": "^4.2.0",
|
||||
|
|
@ -87,7 +87,9 @@
|
|||
"mock-fs": "^4.10.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15.0.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"playwright": "^1.7.1",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.0.5",
|
||||
"prettier-package-json": "^2.1.3",
|
||||
"remark-html": "^11.0.1",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
const babelPlugin = require('./src/babelPluginExtendDocs');
|
||||
const babelPlugin = require('./src/babelPluginExtendDocs.js');
|
||||
|
||||
module.exports = babelPlugin;
|
||||
|
|
|
|||
|
|
@ -111,9 +111,10 @@ class PBoard extends DecorateMixin(LitElement) {
|
|||
checked
|
||||
@change="${({ target }) => {
|
||||
// TODO: of course, logic depending on dom is never a good idea
|
||||
const groupBoxes = target.parentElement.nextElementSibling.querySelectorAll(
|
||||
'input[type=checkbox]',
|
||||
);
|
||||
const groupBoxes =
|
||||
target.parentElement.nextElementSibling.querySelectorAll(
|
||||
'input[type=checkbox]',
|
||||
);
|
||||
const { checked } = target;
|
||||
Array.from(groupBoxes).forEach(box => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ async function findMembersPerAstEntry(ast, fullCurrentFilePath, projectPath) {
|
|||
// // Handle methods
|
||||
// const mBlacklistPlatform = ['constructor', 'connectedCallback', 'disconnectedCallback'];
|
||||
// const mBlacklistLitEl = [
|
||||
// 'requestUpdateInternal',
|
||||
// 'requestUpdate',
|
||||
// 'createRenderRoot',
|
||||
// 'render',
|
||||
// 'updated',
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@
|
|||
"accessType": "public"
|
||||
},
|
||||
{
|
||||
"name": "requestUpdateInternal",
|
||||
"name": "requestUpdate",
|
||||
"accessType": "protected"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class ExtendedComp extends MyCompMixin(RefClass) {
|
|||
static get properties() {}
|
||||
static get styles() {}
|
||||
get updateComplete() {}
|
||||
requestUpdateInternal() {}
|
||||
requestUpdate() {}
|
||||
createRenderRoot() {}
|
||||
render() {}
|
||||
updated() {}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,8 @@ const promptAnalyzerModule = require('../../src/cli/prompt-analyzer-menu.js');
|
|||
const { toPosixPath } = require('../../src/program/utils/to-posix-path.js');
|
||||
const { getExtendDocsResults } = require('../../src/cli/launch-providence-with-extend-docs.js');
|
||||
|
||||
const {
|
||||
pathsArrayFromCs,
|
||||
pathsArrayFromCollectionName,
|
||||
appendProjectDependencyPaths,
|
||||
} = cliHelpersModule;
|
||||
const { pathsArrayFromCs, pathsArrayFromCollectionName, appendProjectDependencyPaths } =
|
||||
cliHelpersModule;
|
||||
|
||||
const queryResults = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ describe('Analyzer "find-classes"', () => {
|
|||
static get properties() {}
|
||||
static get styles() {}
|
||||
get updateComplete() {}
|
||||
requestUpdateInternal() {}
|
||||
requestUpdate() {}
|
||||
createRenderRoot() {}
|
||||
render() {}
|
||||
updated() {}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,8 @@ const {
|
|||
restoreSuppressNonCriticalLogs,
|
||||
} = require('../../../test-helpers/mock-log-service-helpers.js');
|
||||
|
||||
const findCustomelementsQueryConfig = QueryService.getQueryConfigFromAnalyzer(
|
||||
'find-customelements',
|
||||
);
|
||||
const findCustomelementsQueryConfig =
|
||||
QueryService.getQueryConfigFromAnalyzer('find-customelements');
|
||||
const _providenceCfg = {
|
||||
targetProjectPaths: ['/fictional/project'], // defined in mockProject
|
||||
};
|
||||
|
|
|
|||
8
packages-node/publish-docs/index.d.ts
vendored
Normal file
8
packages-node/publish-docs/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export { PublishDocs } from "./src/PublishDocs.js";
|
||||
export type PublishDocsOptions = {
|
||||
projectDir: string;
|
||||
gitHubUrl: string;
|
||||
gitRootDir: string;
|
||||
copyDir: string;
|
||||
copyTarget: string;
|
||||
};
|
||||
|
|
@ -149,7 +149,8 @@ describe('remarkExtend', () => {
|
|||
|
||||
it('throws if an import file does not exist', async () => {
|
||||
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 =
|
||||
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=Does not exit])')\n```";
|
||||
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 =
|
||||
"```js ::import('./fixtures/three-sections-red.md', 'heading:has([value=More Red])', 'heading:has([value=Does not exit])')\n```";
|
||||
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 ".*"\./,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -226,12 +226,12 @@ export class LionAccordion extends LitElement {
|
|||
* @private
|
||||
*/
|
||||
__setupStore() {
|
||||
const invokers = /** @type {HTMLElement[]} */ (Array.from(
|
||||
this.querySelectorAll('[slot="invoker"]'),
|
||||
));
|
||||
const contents = /** @type {HTMLElement[]} */ (Array.from(
|
||||
this.querySelectorAll('[slot="content"]'),
|
||||
));
|
||||
const invokers = /** @type {HTMLElement[]} */ (
|
||||
Array.from(this.querySelectorAll('[slot="invoker"]'))
|
||||
);
|
||||
const contents = /** @type {HTMLElement[]} */ (
|
||||
Array.from(this.querySelectorAll('[slot="content"]'))
|
||||
);
|
||||
if (invokers.length !== contents.length) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
|
|
@ -356,9 +356,11 @@ export class LionAccordion extends LitElement {
|
|||
if (!(this.__store && this.__store[this.focusedIndex])) {
|
||||
return;
|
||||
}
|
||||
const previousInvoker = /** @type {HTMLElement | undefined} */ (Array.from(this.children).find(
|
||||
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
|
||||
));
|
||||
const previousInvoker = /** @type {HTMLElement | undefined} */ (
|
||||
Array.from(this.children).find(
|
||||
child => child.slot === 'invoker' && child.firstElementChild?.hasAttribute('focused'),
|
||||
)
|
||||
);
|
||||
if (previousInvoker) {
|
||||
unfocusInvoker(previousInvoker);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
|
||||
import '../lion-accordion.js';
|
||||
|
|
@ -25,14 +26,16 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('can programmatically set expanded', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion .expanded=${[1]}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion .expanded=${[1]}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
expect(el.expanded).to.deep.equal([1]);
|
||||
expect(
|
||||
Array.from(el.children).find(
|
||||
|
|
@ -103,14 +106,16 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('can programmatically set focusedIndex', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
expect(el.focusedIndex).to.equal(1);
|
||||
expect(
|
||||
Array.from(el.children).find(
|
||||
|
|
@ -214,16 +219,18 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('selects previous invoker on [arrow-left] and [arrow-up]', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
const invokers = el.querySelectorAll('[slot=invoker]');
|
||||
el.focusedIndex = 2;
|
||||
invokers[2].firstElementChild?.dispatchEvent(
|
||||
|
|
@ -237,14 +244,16 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('selects first invoker on [home]', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion .focusedIndex=${1}>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
const invokers = el.querySelectorAll('[slot=invoker]');
|
||||
invokers[1].firstElementChild?.dispatchEvent(new KeyboardEvent('keydown', { key: 'Home' }));
|
||||
expect(el.focusedIndex).to.equal(0);
|
||||
|
|
@ -258,16 +267,18 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('stays on last invoker on [arrow-right]', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion focusedIndex="2">
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion focusedIndex="2">
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
const invokers = el.querySelectorAll('[slot=invoker]');
|
||||
invokers[2].firstElementChild?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowRight' }),
|
||||
|
|
@ -276,16 +287,18 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('stays on first invoker on [arrow-left]', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion>
|
||||
<h2 slot="invoker"><button>invoker 1</button></h2>
|
||||
<div slot="content">content 1</div>
|
||||
<h2 slot="invoker"><button>invoker 2</button></h2>
|
||||
<div slot="content">content 2</div>
|
||||
<h2 slot="invoker"><button>invoker 3</button></h2>
|
||||
<div slot="content">content 3</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
const invokers = el.querySelectorAll('[slot=invoker]');
|
||||
invokers[0].firstElementChild?.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowLeft' }),
|
||||
|
|
@ -338,12 +351,12 @@ describe('<lion-accordion>', () => {
|
|||
el.append(content);
|
||||
}
|
||||
await el.updateComplete;
|
||||
const invokers = /** @type {HTMLElement[]} */ (Array.from(
|
||||
el.querySelectorAll('[slot=invoker]'),
|
||||
));
|
||||
const contents = /** @type {HTMLElement[]} */ (Array.from(
|
||||
el.querySelectorAll('[slot=content]'),
|
||||
));
|
||||
const invokers = /** @type {HTMLElement[]} */ (
|
||||
Array.from(el.querySelectorAll('[slot=invoker]'))
|
||||
);
|
||||
const contents = /** @type {HTMLElement[]} */ (
|
||||
Array.from(el.querySelectorAll('[slot=content]'))
|
||||
);
|
||||
invokers.forEach((invoker, index) => {
|
||||
const content = contents[index];
|
||||
expect(invoker.style.getPropertyValue('order')).to.equal(`${index + 1}`);
|
||||
|
|
@ -403,12 +416,14 @@ describe('<lion-accordion>', () => {
|
|||
});
|
||||
|
||||
it('adds aria-expanded="true" to invoker when its content is expanded', async () => {
|
||||
const el = /** @type {LionAccordion} */ (await fixture(html`
|
||||
<lion-accordion>
|
||||
<h2 slot="invoker"><button>invoker</button></h2>
|
||||
<div slot="content">content</div>
|
||||
</lion-accordion>
|
||||
`));
|
||||
const el = /** @type {LionAccordion} */ (
|
||||
await fixture(html`
|
||||
<lion-accordion>
|
||||
<h2 slot="invoker"><button>invoker</button></h2>
|
||||
<div slot="content">content</div>
|
||||
</lion-accordion>
|
||||
`)
|
||||
);
|
||||
el.expanded = [0];
|
||||
expect(
|
||||
Array.from(el.children).find(child => child.slot === 'invoker')?.firstElementChild,
|
||||
|
|
|
|||
|
|
@ -162,9 +162,9 @@ export class LionButton extends DisabledWithTabIndexMixin(SlotMixin(LitElement))
|
|||
* @protected
|
||||
*/
|
||||
get _nativeButtonNode() {
|
||||
return /** @type {HTMLButtonElement} */ (Array.from(this.children).find(
|
||||
child => child.slot === '_button',
|
||||
));
|
||||
return /** @type {HTMLButtonElement} */ (
|
||||
Array.from(this.children).find(child => child.slot === '_button')
|
||||
);
|
||||
}
|
||||
|
||||
get slots() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable lit-a11y/click-events-have-key-events */
|
||||
import { browserDetection } from '@lion/core';
|
||||
import { aTimeout, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing';
|
||||
import { aTimeout, expect, fixture, oneEvent } from '@open-wc/testing';
|
||||
import { unsafeStatic, html } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/core/differentKeyEventNamesShimIE';
|
||||
import '@lion/button/define';
|
||||
|
|
@ -37,9 +38,9 @@ describe('lion-button', () => {
|
|||
});
|
||||
|
||||
it('sync type down to the native button', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button type="button">foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button type="button">foo</lion-button>`)
|
||||
);
|
||||
const { nativeButtonNode } = getProtectedMembers(el);
|
||||
|
||||
expect(el.type).to.equal('button');
|
||||
|
|
@ -175,9 +176,9 @@ describe('lion-button', () => {
|
|||
});
|
||||
|
||||
it('does not override user provided role', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button role="foo">foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button role="foo">foo</lion-button>`)
|
||||
);
|
||||
expect(el.getAttribute('role')).to.equal('foo');
|
||||
});
|
||||
|
||||
|
|
@ -187,9 +188,9 @@ describe('lion-button', () => {
|
|||
});
|
||||
|
||||
it('has a tabindex="-1" when disabled', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button disabled>foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button disabled>foo</lion-button>`)
|
||||
);
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
|
|
@ -200,16 +201,16 @@ describe('lion-button', () => {
|
|||
});
|
||||
|
||||
it('does not override user provided tabindex', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button tabindex="5">foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button tabindex="5">foo</lion-button>`)
|
||||
);
|
||||
expect(el.getAttribute('tabindex')).to.equal('5');
|
||||
});
|
||||
|
||||
it('disabled does not override user provided tabindex', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button tabindex="5" disabled>foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button tabindex="5" disabled>foo</lion-button>`)
|
||||
);
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
|
|
@ -230,9 +231,9 @@ describe('lion-button', () => {
|
|||
|
||||
it('does not override aria-labelledby when provided by user', async () => {
|
||||
const browserDetectionStub = sinon.stub(browserDetection, 'isIE11').value(true);
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button aria-labelledby="some-id another-id">foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button aria-labelledby="some-id another-id">foo</lion-button>`)
|
||||
);
|
||||
expect(el.getAttribute('aria-labelledby')).to.equal('some-id another-id');
|
||||
browserDetectionStub.restore();
|
||||
});
|
||||
|
|
@ -244,15 +245,17 @@ describe('lion-button', () => {
|
|||
expect(nativeButtonNode.getAttribute('aria-hidden')).to.equal('true');
|
||||
});
|
||||
|
||||
it('is accessible', async () => {
|
||||
// TODO: enable when native button is not a child anymore
|
||||
it.skip('is accessible', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(`<lion-button>foo</lion-button>`));
|
||||
await expect(el).to.be.accessible();
|
||||
});
|
||||
|
||||
it('is accessible when disabled', async () => {
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button disabled>foo</lion-button>`,
|
||||
));
|
||||
// TODO: enable when native button is not a child anymore
|
||||
it.skip('is accessible when disabled', async () => {
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button disabled>foo</lion-button>`)
|
||||
);
|
||||
await expect(el).to.be.accessible({ ignoredRules: ['color-contrast'] });
|
||||
});
|
||||
});
|
||||
|
|
@ -266,9 +269,9 @@ describe('lion-button', () => {
|
|||
<lion-button type="submit">foo</lion-button>
|
||||
</form>
|
||||
`);
|
||||
const button /** @type {LionButton} */ = /** @type {LionButton} */ (form.querySelector(
|
||||
'lion-button',
|
||||
));
|
||||
const button /** @type {LionButton} */ = /** @type {LionButton} */ (
|
||||
form.querySelector('lion-button')
|
||||
);
|
||||
button.click();
|
||||
expect(formSubmitSpy).to.have.been.calledOnce;
|
||||
});
|
||||
|
|
@ -280,9 +283,9 @@ describe('lion-button', () => {
|
|||
<lion-button type="submit">foo</lion-button>
|
||||
</form>
|
||||
`);
|
||||
const button /** @type {LionButton} */ = /** @type {LionButton} */ (form.querySelector(
|
||||
'lion-button',
|
||||
));
|
||||
const button /** @type {LionButton} */ = /** @type {LionButton} */ (
|
||||
form.querySelector('lion-button')
|
||||
);
|
||||
button.dispatchEvent(new KeyboardEvent('keyup', { key: ' ' }));
|
||||
await aTimeout(0);
|
||||
await aTimeout(0);
|
||||
|
|
@ -313,15 +316,15 @@ describe('lion-button', () => {
|
|||
<lion-button type="reset">reset</lion-button>
|
||||
</form>
|
||||
`);
|
||||
const btn /** @type {LionButton} */ = /** @type {LionButton} */ (form.querySelector(
|
||||
'lion-button',
|
||||
));
|
||||
const firstName = /** @type {HTMLInputElement} */ (form.querySelector(
|
||||
'input[name=firstName]',
|
||||
));
|
||||
const lastName = /** @type {HTMLInputElement} */ (form.querySelector(
|
||||
'input[name=lastName]',
|
||||
));
|
||||
const btn /** @type {LionButton} */ = /** @type {LionButton} */ (
|
||||
form.querySelector('lion-button')
|
||||
);
|
||||
const firstName = /** @type {HTMLInputElement} */ (
|
||||
form.querySelector('input[name=firstName]')
|
||||
);
|
||||
const lastName = /** @type {HTMLInputElement} */ (
|
||||
form.querySelector('input[name=lastName]')
|
||||
);
|
||||
firstName.value = 'Foo';
|
||||
lastName.value = 'Bar';
|
||||
|
||||
|
|
@ -435,9 +438,9 @@ describe('lion-button', () => {
|
|||
|
||||
it('is fired once', async () => {
|
||||
const clickSpy = /** @type {EventListener} */ (sinon.spy());
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
html` <lion-button @click="${clickSpy}">foo</lion-button> `,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(html` <lion-button @click="${clickSpy}">foo</lion-button> `)
|
||||
);
|
||||
|
||||
el.click();
|
||||
|
||||
|
|
@ -454,17 +457,19 @@ describe('lion-button', () => {
|
|||
const formSpyEarly = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault()));
|
||||
const formSpyLater = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault()));
|
||||
|
||||
const el = /** @type {HTMLDivElement} */ (await fixture(
|
||||
html`
|
||||
<div @click="${outsideSpy}">
|
||||
<form @click="${formSpyEarly}">
|
||||
<div @click="${insideSpy}">
|
||||
<lion-button>foo</lion-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
));
|
||||
const el = /** @type {HTMLDivElement} */ (
|
||||
await fixture(
|
||||
html`
|
||||
<div @click="${outsideSpy}">
|
||||
<form @click="${formSpyEarly}">
|
||||
<div @click="${insideSpy}">
|
||||
<lion-button>foo</lion-button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
);
|
||||
const lionButton = /** @type {LionButton} */ (el.querySelector('lion-button'));
|
||||
const form = /** @type {HTMLFormElement} */ (el.querySelector('form'));
|
||||
form.addEventListener('click', formSpyLater);
|
||||
|
|
@ -482,13 +487,15 @@ describe('lion-button', () => {
|
|||
});
|
||||
|
||||
it('works when connected to different form', async () => {
|
||||
const form1El = /** @type {HTMLFormElement} */ (await fixture(
|
||||
html`
|
||||
<form>
|
||||
<lion-button>foo</lion-button>
|
||||
</form>
|
||||
`,
|
||||
));
|
||||
const form1El = /** @type {HTMLFormElement} */ (
|
||||
await fixture(
|
||||
html`
|
||||
<form>
|
||||
<lion-button>foo</lion-button>
|
||||
</form>
|
||||
`,
|
||||
)
|
||||
);
|
||||
const lionButton = /** @type {LionButton} */ (form1El.querySelector('lion-button'));
|
||||
|
||||
expect(lionButton._form).to.equal(form1El);
|
||||
|
|
@ -500,15 +507,17 @@ describe('lion-button', () => {
|
|||
const formSpyEarly = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault()));
|
||||
const formSpyLater = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault()));
|
||||
|
||||
const form2El = /** @type {HTMLFormElement} */ (await fixture(
|
||||
html`
|
||||
<div @click="${outsideSpy}">
|
||||
<form @click="${formSpyEarly}">
|
||||
<div @click="${insideSpy}">${lionButton}</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
));
|
||||
const form2El = /** @type {HTMLFormElement} */ (
|
||||
await fixture(
|
||||
html`
|
||||
<div @click="${outsideSpy}">
|
||||
<form @click="${formSpyEarly}">
|
||||
<div @click="${insideSpy}">${lionButton}</div>
|
||||
</form>
|
||||
</div>
|
||||
`,
|
||||
)
|
||||
);
|
||||
const form2Node = /** @type {HTMLFormElement} */ (form2El.querySelector('form'));
|
||||
|
||||
expect(lionButton._form).to.equal(form2Node);
|
||||
|
|
@ -534,9 +543,9 @@ describe('lion-button', () => {
|
|||
|
||||
before(async () => {
|
||||
const nativeButtonEl = /** @type {LionButton} */ (await fixture('<button>foo</button>'));
|
||||
const lionButtonEl = /** @type {LionButton} */ (await fixture(
|
||||
'<lion-button>foo</lion-button>',
|
||||
));
|
||||
const lionButtonEl = /** @type {LionButton} */ (
|
||||
await fixture('<lion-button>foo</lion-button>')
|
||||
);
|
||||
nativeButtonEvent = await prepareClickEvent(nativeButtonEl);
|
||||
lionButtonEvent = await prepareClickEvent(lionButtonEl);
|
||||
});
|
||||
|
|
@ -578,9 +587,9 @@ describe('lion-button', () => {
|
|||
const targetName = 'host';
|
||||
it(`is ${targetName} with type ${type} and it is inside a ${container}`, async () => {
|
||||
const clickSpy = /** @type {EventListener} */ (sinon.spy(e => e.preventDefault()));
|
||||
const el = /** @type {LionButton} */ (await fixture(
|
||||
`<lion-button type="${type}">foo</lion-button>`,
|
||||
));
|
||||
const el = /** @type {LionButton} */ (
|
||||
await fixture(`<lion-button type="${type}">foo</lion-button>`)
|
||||
);
|
||||
const tag = unsafeStatic(container);
|
||||
await fixture(html`<${tag} @click="${clickSpy}">${el}</${tag}>`);
|
||||
const event = await prepareClickEvent(el);
|
||||
|
|
|
|||
2
packages/calendar/index.d.ts
vendored
Normal file
2
packages/calendar/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { isSameDate } from "./src/utils/isSameDate.js";
|
||||
export { LionCalendar } from "./src/LionCalendar.js";
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { html, LitElement } from '@lion/core';
|
||||
import {
|
||||
getMonthNames,
|
||||
|
|
@ -224,9 +225,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
}
|
||||
|
||||
focusCentralDate() {
|
||||
const button = /** @type {HTMLElement} */ (this.shadowRoot?.querySelector(
|
||||
'button[tabindex="0"]',
|
||||
));
|
||||
const button = /** @type {HTMLElement} */ (
|
||||
this.shadowRoot?.querySelector('button[tabindex="0"]')
|
||||
);
|
||||
button.focus();
|
||||
this.__focusedDate = this.centralDate;
|
||||
}
|
||||
|
|
@ -267,9 +268,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* we can guard against adding events twice
|
||||
*/
|
||||
if (!this.__eventsAdded) {
|
||||
this.__contentWrapperElement = /** @type {HTMLButtonElement} */ (this.shadowRoot?.getElementById(
|
||||
'js-content-wrapper',
|
||||
));
|
||||
this.__contentWrapperElement = /** @type {HTMLButtonElement} */ (
|
||||
this.shadowRoot?.getElementById('js-content-wrapper')
|
||||
);
|
||||
this.__contentWrapperElement.addEventListener('click', this.__boundClickDateDelegation);
|
||||
this.__contentWrapperElement.addEventListener('focus', this.__boundFocusDateDelegation);
|
||||
this.__contentWrapperElement.addEventListener('blur', this.__boundBlurDateDelegation);
|
||||
|
|
@ -305,8 +306,8 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
* @param {string} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
||||
const map = {
|
||||
disableDates: () => this.__disableDatesChanged(),
|
||||
|
|
@ -740,8 +741,9 @@ export class LionCalendar extends LocalizeMixin(LitElement) {
|
|||
!this.__focusedDate &&
|
||||
isDayButton(/** @type {HTMLElement} el */ (this.shadowRoot?.activeElement))
|
||||
) {
|
||||
this.__focusedDate = /** @type {HTMLButtonElement & { date: Date }} */ (this.shadowRoot
|
||||
?.activeElement).date;
|
||||
this.__focusedDate = /** @type {HTMLButtonElement & { date: Date }} */ (
|
||||
this.shadowRoot?.activeElement
|
||||
).date;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,10 +59,16 @@ export function dayTemplate(day, { weekdays, monthsLabels = defaultMonthLabels }
|
|||
<button
|
||||
.date=${day.date}
|
||||
class="calendar__day-button"
|
||||
tabindex=${ifDefined(day.tabindex)}
|
||||
tabindex=${ifDefined(Number(day.tabindex))}
|
||||
aria-label=${`${dayNumber} ${monthName} ${year} ${weekdayName}`}
|
||||
aria-pressed=${ifDefined(day.ariaPressed)}
|
||||
aria-current=${ifDefined(day.ariaCurrent)}
|
||||
aria-pressed=${
|
||||
/** @type {'true'|'false'|'mixed'|'undefined'} */ (ifDefined(day.ariaPressed))
|
||||
}
|
||||
aria-current=${
|
||||
/** @type {'page'|'step'|'location'|'date'|'time'|'true'|'false'} */ (
|
||||
ifDefined(day.ariaCurrent)
|
||||
)
|
||||
}
|
||||
?disabled=${day.disabled}
|
||||
?selected=${day.selected}
|
||||
?past=${day.past}
|
||||
|
|
|
|||
|
|
@ -33,15 +33,15 @@ export class CalendarObject {
|
|||
}
|
||||
|
||||
get nextYearButtonEl() {
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__next-button',
|
||||
)[0]);
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (
|
||||
this.el.shadowRoot?.querySelectorAll('.calendar__next-button')[0]
|
||||
);
|
||||
}
|
||||
|
||||
get previousYearButtonEl() {
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__previous-button',
|
||||
)[0]);
|
||||
return /** @type {HTMLElement & { ariaLabel: string }} */ (
|
||||
this.el.shadowRoot?.querySelectorAll('.calendar__previous-button')[0]
|
||||
);
|
||||
}
|
||||
|
||||
get nextMonthButtonEl() {
|
||||
|
|
@ -57,33 +57,43 @@ export class CalendarObject {
|
|||
}
|
||||
|
||||
get weekdayHeaderEls() {
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll('.calendar__weekday-header'),
|
||||
));
|
||||
return /** @type {HTMLElement[]} */ (
|
||||
Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__weekday-header',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get dayEls() {
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[current-month]',
|
||||
),
|
||||
));
|
||||
return /** @type {HTMLElement[]} */ (
|
||||
Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[current-month]',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get previousMonthDayEls() {
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[previous-month]',
|
||||
),
|
||||
));
|
||||
return /** @type {HTMLElement[]} */ (
|
||||
Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[previous-month]',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get nextMonthDayEls() {
|
||||
return /** @type {HTMLElement[]} */ (Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[next-month]',
|
||||
),
|
||||
));
|
||||
return /** @type {HTMLElement[]} */ (
|
||||
Array.from(
|
||||
/** @type {ShadowRoot} */ (this.el.shadowRoot).querySelectorAll(
|
||||
'.calendar__day-button[next-month]',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
get dayObjs() {
|
||||
|
|
@ -103,9 +113,11 @@ export class CalendarObject {
|
|||
*/
|
||||
getDayEl(monthDayNumber) {
|
||||
// Relies on the fact that empty cells don't have .calendar__day-button[current-month]
|
||||
return /** @type {HTMLElement} */ (this.el.shadowRoot?.querySelectorAll(
|
||||
'.calendar__day-button[current-month]',
|
||||
)[monthDayNumber - 1]);
|
||||
return /** @type {HTMLElement} */ (
|
||||
this.el.shadowRoot?.querySelectorAll('.calendar__day-button[current-month]')[
|
||||
monthDayNumber - 1
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -813,12 +813,10 @@ describe('<lion-calendar>', () => {
|
|||
`);
|
||||
const elObj = new CalendarObject(el);
|
||||
expect(
|
||||
elObj.checkForAllDayObjs(/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'), [
|
||||
1,
|
||||
2,
|
||||
30,
|
||||
31,
|
||||
]),
|
||||
elObj.checkForAllDayObjs(
|
||||
/** @param {DayObject} d */ d => d.el.hasAttribute('disabled'),
|
||||
[1, 2, 30, 31],
|
||||
),
|
||||
).to.equal(true);
|
||||
|
||||
clock.restore();
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ function compareMultipleMonth(obj) {
|
|||
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
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
obj.months[monthi].weeks[weeki].days[dayi].date = obj.months[monthi].weeks[weeki].days[
|
||||
dayi
|
||||
].date.toISOString();
|
||||
obj.months[monthi].weeks[weeki].days[dayi].date =
|
||||
obj.months[monthi].weeks[weeki].days[dayi].date.toISOString();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
3
packages/checkbox-group/index.d.ts
vendored
Normal file
3
packages/checkbox-group/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { LionCheckboxGroup } from "./src/LionCheckboxGroup.js";
|
||||
export { LionCheckboxIndeterminate } from "./src/LionCheckboxIndeterminate.js";
|
||||
export { LionCheckbox } from "./src/LionCheckbox.js";
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { expect, fixture as _fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import '@lion/checkbox-group/define';
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import '@lion/checkbox-group/define';
|
||||
|
||||
|
|
@ -46,9 +47,9 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
|
||||
|
|
@ -65,9 +66,9 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
|
||||
|
|
@ -75,18 +76,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should be checked if all children are checked', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie" checked></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie" checked></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
|
||||
|
|
@ -95,18 +98,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should become indeterminate if one child is checked', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
|
|
@ -120,18 +125,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should become checked if all children are checked', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
// Act
|
||||
|
|
@ -147,18 +154,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should sync all children when parent is checked (from indeterminate to checked)', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
// Act
|
||||
|
|
@ -174,18 +183,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should sync all children when parent is checked (from unchecked to checked)', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
// Act
|
||||
|
|
@ -201,18 +212,20 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should sync all children when parent is checked (from checked to unchecked)', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie" checked></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<lion-checkbox slot="checkbox" label="Archimedes" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon" checked></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie" checked></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
// Act
|
||||
|
|
@ -228,45 +241,50 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should work as expected with siblings checkbox-indeterminate', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||
<lion-checkbox-indeterminate label="Old Greek scientists" id="first-checkbox-indeterminate">
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Archimedes"
|
||||
.choiceValue=${'Archimedes'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Plato" .choiceValue=${'Plato'}></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Pythagoras"
|
||||
.choiceValue=${'Pythagoras'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
<lion-checkbox-indeterminate
|
||||
label="17th Century scientists"
|
||||
id="second-checkbox-indeterminate"
|
||||
>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Isaac Newton"
|
||||
.choiceValue=${'Isaac Newton'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Galileo Galilei"
|
||||
.choiceValue=${'Galileo Galilei'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'#first-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||
<lion-checkbox-indeterminate
|
||||
label="Old Greek scientists"
|
||||
id="first-checkbox-indeterminate"
|
||||
>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Archimedes"
|
||||
.choiceValue=${'Archimedes'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Plato" .choiceValue=${'Plato'}></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Pythagoras"
|
||||
.choiceValue=${'Pythagoras'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
<lion-checkbox-indeterminate
|
||||
label="17th Century scientists"
|
||||
id="second-checkbox-indeterminate"
|
||||
>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Isaac Newton"
|
||||
.choiceValue=${'Isaac Newton'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Galileo Galilei"
|
||||
.choiceValue=${'Galileo Galilei'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elFirstIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('#first-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'#second-checkbox-indeterminate',
|
||||
));
|
||||
const elSecondIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('#second-checkbox-indeterminate')
|
||||
);
|
||||
|
||||
const elFirstSubCheckboxes = getCheckboxIndeterminateMembers(elFirstIndeterminate);
|
||||
const elSecondSubCheckboxes = getCheckboxIndeterminateMembers(elSecondIndeterminate);
|
||||
|
|
@ -289,45 +307,47 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should work as expected with nested indeterminate checkboxes', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||
<lion-checkbox-indeterminate label="Scientists" id="parent-checkbox-indeterminate">
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Isaac Newton"
|
||||
.choiceValue=${'Isaac Newton'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Galileo Galilei"
|
||||
.choiceValue=${'Galileo Galilei'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox-indeterminate
|
||||
slot="checkbox"
|
||||
label="Old Greek scientists"
|
||||
id="nested-checkbox-indeterminate"
|
||||
>
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||
<lion-checkbox-indeterminate label="Scientists" id="parent-checkbox-indeterminate">
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Archimedes"
|
||||
.choiceValue=${'Archimedes'}
|
||||
label="Isaac Newton"
|
||||
.choiceValue=${'Isaac Newton'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Plato" .choiceValue=${'Plato'}></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Pythagoras"
|
||||
.choiceValue=${'Pythagoras'}
|
||||
label="Galileo Galilei"
|
||||
.choiceValue=${'Galileo Galilei'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox-indeterminate
|
||||
slot="checkbox"
|
||||
label="Old Greek scientists"
|
||||
id="nested-checkbox-indeterminate"
|
||||
>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Archimedes"
|
||||
.choiceValue=${'Archimedes'}
|
||||
></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Plato" .choiceValue=${'Plato'}></lion-checkbox>
|
||||
<lion-checkbox
|
||||
slot="checkbox"
|
||||
label="Pythagoras"
|
||||
.choiceValue=${'Pythagoras'}
|
||||
></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-indeterminate>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elNestedIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'#nested-checkbox-indeterminate',
|
||||
));
|
||||
const elParentIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'#parent-checkbox-indeterminate',
|
||||
));
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elNestedIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('#nested-checkbox-indeterminate')
|
||||
);
|
||||
const elParentIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('#parent-checkbox-indeterminate')
|
||||
);
|
||||
const elNestedSubCheckboxes = getCheckboxIndeterminateMembers(elNestedIndeterminate);
|
||||
const elParentSubCheckboxes = getCheckboxIndeterminateMembers(elParentIndeterminate);
|
||||
|
||||
|
|
@ -375,25 +395,27 @@ describe('<lion-checkbox-indeterminate>', () => {
|
|||
|
||||
it('should work as expected if extra html', async () => {
|
||||
// Arrange
|
||||
const el = /** @type {LionCheckboxGroup} */ (await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<div>
|
||||
Let's have some fun
|
||||
<div>Hello I'm a div</div>
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<div>useless div</div>
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<div>absolutely useless</div>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</div>
|
||||
<div>Too much fun, stop it !</div>
|
||||
</lion-checkbox-group>
|
||||
`));
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (el.querySelector(
|
||||
'lion-checkbox-indeterminate',
|
||||
));
|
||||
const el = /** @type {LionCheckboxGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox-group name="scientists[]">
|
||||
<div>
|
||||
Let's have some fun
|
||||
<div>Hello I'm a div</div>
|
||||
<lion-checkbox-indeterminate label="Favorite scientists">
|
||||
<div>useless div</div>
|
||||
<lion-checkbox slot="checkbox" label="Archimedes"></lion-checkbox>
|
||||
<lion-checkbox slot="checkbox" label="Francis Bacon"></lion-checkbox>
|
||||
<div>absolutely useless</div>
|
||||
<lion-checkbox slot="checkbox" label="Marie Curie"></lion-checkbox>
|
||||
</lion-checkbox-indeterminate>
|
||||
</div>
|
||||
<div>Too much fun, stop it !</div>
|
||||
</lion-checkbox-group>
|
||||
`)
|
||||
);
|
||||
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
||||
el.querySelector('lion-checkbox-indeterminate')
|
||||
);
|
||||
const { _subCheckboxes } = getCheckboxIndeterminateMembers(elIndeterminate);
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import '@lion/checkbox-group/define-checkbox';
|
||||
|
||||
/**
|
||||
|
|
@ -14,9 +15,9 @@ describe('<lion-checkbox>', () => {
|
|||
});
|
||||
|
||||
it('can be reset when unchecked by default', async () => {
|
||||
const el = /** @type {LionCheckbox} */ (await fixture(html`
|
||||
<lion-checkbox name="checkbox" .choiceValue=${'male'}></lion-checkbox>
|
||||
`));
|
||||
const el = /** @type {LionCheckbox} */ (
|
||||
await fixture(html` <lion-checkbox name="checkbox" .choiceValue=${'male'}></lion-checkbox> `)
|
||||
);
|
||||
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: false });
|
||||
el.checked = true;
|
||||
expect(el.modelValue).to.deep.equal({ value: 'male', checked: true });
|
||||
|
|
@ -26,9 +27,11 @@ describe('<lion-checkbox>', () => {
|
|||
});
|
||||
|
||||
it('can be reset when checked by default', async () => {
|
||||
const el = /** @type {LionCheckbox} */ (await fixture(html`
|
||||
<lion-checkbox name="checkbox" .choiceValue=${'male'} checked></lion-checkbox>
|
||||
`));
|
||||
const el = /** @type {LionCheckbox} */ (
|
||||
await fixture(html`
|
||||
<lion-checkbox name="checkbox" .choiceValue=${'male'} checked></lion-checkbox>
|
||||
`)
|
||||
);
|
||||
expect(el._initialModelValue).to.deep.equal({ value: 'male', checked: true });
|
||||
el.checked = false;
|
||||
expect(el.modelValue).to.deep.equal({ value: 'male', checked: false });
|
||||
|
|
|
|||
1
packages/collapsible/index.d.ts
vendored
Normal file
1
packages/collapsible/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { LionCollapsible } from "./src/LionCollapsible.js";
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture as _fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
|
||||
import '@lion/collapsible/define';
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ describe('<lion-collapsible>', () => {
|
|||
it('has [opened] on current expanded invoker which serves as styling hook', async () => {
|
||||
const collapsible = await fixture(defaultCollapsible);
|
||||
collapsible.opened = true;
|
||||
await collapsible.requestUpdate();
|
||||
await collapsible.updateComplete;
|
||||
expect(collapsible).to.have.attribute('opened');
|
||||
});
|
||||
|
||||
|
|
@ -62,7 +63,7 @@ describe('<lion-collapsible>', () => {
|
|||
const collHeight1 = getProtectedMembers(collapsible);
|
||||
expect(collHeight1.contentHeight).to.equal('0px');
|
||||
collapsible.show();
|
||||
await collapsible.requestUpdate();
|
||||
await collapsible.updateComplete;
|
||||
const collHeight2 = getProtectedMembers(collapsible);
|
||||
expect(collHeight2.contentHeight).to.equal('32px');
|
||||
});
|
||||
|
|
@ -93,10 +94,10 @@ describe('<lion-collapsible>', () => {
|
|||
it('should listen to the open and close state change', async () => {
|
||||
const collapsible = await fixture(collapsibleWithEvents);
|
||||
collapsible.show();
|
||||
await collapsible.requestUpdate();
|
||||
await collapsible.updateComplete;
|
||||
expect(isCollapsibleOpen).to.equal(true);
|
||||
collapsible.hide();
|
||||
await collapsible.requestUpdate();
|
||||
await collapsible.updateComplete;
|
||||
expect(isCollapsibleOpen).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -131,7 +132,7 @@ describe('<lion-collapsible>', () => {
|
|||
const collapsibleElement = await fixture(defaultCollapsible);
|
||||
const invoker = collapsibleElement.querySelector('[slot=invoker]');
|
||||
collapsibleElement.opened = true;
|
||||
await collapsibleElement.requestUpdate();
|
||||
await collapsibleElement.updateComplete;
|
||||
expect(invoker).to.have.attribute('aria-expanded', 'true');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
1
packages/combobox/index.d.ts
vendored
Normal file
1
packages/combobox/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { LionCombobox } from "./src/LionCombobox.js";
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
import { LionCombobox } from './src/LionCombobox.js';
|
||||
import { /** @type{HTMLElement} */ LionCombobox } from './src/LionCombobox.js';
|
||||
|
||||
customElements.define('lion-combobox', LionCombobox);
|
||||
|
|
|
|||
|
|
@ -218,8 +218,10 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
* @protected
|
||||
*/
|
||||
get _listboxNode() {
|
||||
return /** @type {LionOptions} */ ((this._overlayCtrl && this._overlayCtrl.contentNode) ||
|
||||
Array.from(this.children).find(child => child.slot === 'listbox'));
|
||||
return /** @type {LionOptions} */ (
|
||||
(this._overlayCtrl && this._overlayCtrl.contentNode) ||
|
||||
Array.from(this.children).find(child => child.slot === 'listbox')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -310,8 +312,8 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
* @param {'disabled'|'modelValue'|'readOnly'|'focused'} name
|
||||
* @param {unknown} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
if (name === 'disabled' || name === 'readOnly') {
|
||||
this.__setComboboxDisabledAndReadOnly();
|
||||
}
|
||||
|
|
@ -514,9 +516,8 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
phase: 'overlay-close',
|
||||
})
|
||||
) {
|
||||
this._inputNode.value = this.formElements[
|
||||
/** @type {number} */ (this.checkedIndex)
|
||||
].choiceValue;
|
||||
this._inputNode.value =
|
||||
this.formElements[/** @type {number} */ (this.checkedIndex)].choiceValue;
|
||||
}
|
||||
} else {
|
||||
this._syncToTextboxMultiple(this.modelValue, this._oldModelValue);
|
||||
|
|
@ -703,7 +704,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
});
|
||||
|
||||
// [7]. If no autofill took place, we are left with the previously matched option; correct this
|
||||
if (!hasAutoFilled && autoselect && !this.multipleChoice) {
|
||||
if (autoselect && !hasAutoFilled && !this.multipleChoice) {
|
||||
// This means there is no match for checkedIndex
|
||||
this.checkedIndex = -1;
|
||||
}
|
||||
|
|
@ -771,7 +772,7 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
*/
|
||||
_setupOverlayCtrl() {
|
||||
super._setupOverlayCtrl();
|
||||
this.__initFilterListbox();
|
||||
this.__shouldAutocompleteNextUpdate = true;
|
||||
this.__setupCombobox();
|
||||
}
|
||||
|
||||
|
|
@ -863,13 +864,6 @@ export class LionCombobox extends OverlayMixin(LionListbox) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
__initFilterListbox() {
|
||||
this._handleAutocompletion();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
124
packages/core/index.d.ts
vendored
124
packages/core/index.d.ts
vendored
|
|
@ -1,16 +1,82 @@
|
|||
export { asyncAppend } from 'lit-html/directives/async-append.js';
|
||||
export { asyncReplace } from 'lit-html/directives/async-replace.js';
|
||||
export { cache } from 'lit-html/directives/cache.js';
|
||||
export { classMap } from 'lit-html/directives/class-map.js';
|
||||
export { guard } from 'lit-html/directives/guard.js';
|
||||
export { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
export { repeat } from 'lit-html/directives/repeat.js';
|
||||
export { styleMap } from 'lit-html/directives/style-map.js';
|
||||
export { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
export { until } from 'lit-html/directives/until.js';
|
||||
export { render as renderShady } from 'lit-html/lib/shady-render.js';
|
||||
export {
|
||||
html,
|
||||
CSSResult,
|
||||
adoptStyles,
|
||||
css,
|
||||
getCompatibleStyle,
|
||||
supportsAdoptingStyleSheets,
|
||||
unsafeCSS,
|
||||
UpdatingElement,
|
||||
notEqual,
|
||||
ReactiveElement,
|
||||
svg,
|
||||
noChange,
|
||||
nothing,
|
||||
render,
|
||||
RenderOptions,
|
||||
LitElement,
|
||||
defaultConverter,
|
||||
CSSResultArray,
|
||||
PropertyValues,
|
||||
TemplateResult,
|
||||
} from 'lit';
|
||||
|
||||
export {
|
||||
customElement,
|
||||
property,
|
||||
state,
|
||||
eventOptions,
|
||||
query,
|
||||
queryAll,
|
||||
queryAsync,
|
||||
queryAssignedNodes,
|
||||
} from 'lit/decorators.js';
|
||||
|
||||
export {
|
||||
AttributePart,
|
||||
BooleanAttributePart,
|
||||
ChildPart,
|
||||
ElementPart,
|
||||
EventPart,
|
||||
Part,
|
||||
PartType,
|
||||
directive,
|
||||
Directive,
|
||||
DirectiveResult,
|
||||
} from 'lit/directive.js';
|
||||
|
||||
export { AsyncDirective } from 'lit/async-directive.js';
|
||||
|
||||
export {
|
||||
isPrimitive,
|
||||
TemplateResultType,
|
||||
isTemplateResult,
|
||||
isDirectiveResult,
|
||||
getDirectiveClass,
|
||||
isSingleExpression,
|
||||
insertPart,
|
||||
setChildPartValue,
|
||||
setCommittedValue,
|
||||
getCommittedValue,
|
||||
removePart,
|
||||
clearPart,
|
||||
} from 'lit/directive-helpers.js';
|
||||
|
||||
export { asyncAppend } from 'lit/directives/async-append.js';
|
||||
export { asyncReplace } from 'lit/directives/async-replace.js';
|
||||
export { cache } from 'lit/directives/cache.js';
|
||||
export { classMap } from 'lit/directives/class-map.js';
|
||||
export { guard } from 'lit/directives/guard.js';
|
||||
export { ifDefined } from 'lit/directives/if-defined.js';
|
||||
export { repeat } from 'lit/directives/repeat.js';
|
||||
export { styleMap } from 'lit/directives/style-map.js';
|
||||
export { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
export { until } from 'lit/directives/until.js';
|
||||
|
||||
// open-wc
|
||||
export { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
export { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
// ours
|
||||
export { DelegateMixin } from './src/DelegateMixin.js';
|
||||
export { DisabledMixin } from './src/DisabledMixin.js';
|
||||
export { DisabledWithTabIndexMixin } from './src/DisabledWithTabIndexMixin.js';
|
||||
|
|
@ -18,39 +84,3 @@ export { SlotMixin } from './src/SlotMixin.js';
|
|||
export { UpdateStylesMixin } from './src/UpdateStylesMixin.js';
|
||||
export { browserDetection } from './src/browserDetection.js';
|
||||
export { EventTargetShim } from './src/EventTargetShim.js';
|
||||
export {
|
||||
css,
|
||||
CSSResult,
|
||||
CSSResultArray,
|
||||
customElement,
|
||||
defaultConverter,
|
||||
eventOptions,
|
||||
LitElement,
|
||||
notEqual,
|
||||
property,
|
||||
PropertyValues,
|
||||
query,
|
||||
queryAll,
|
||||
supportsAdoptingStyleSheets,
|
||||
unsafeCSS,
|
||||
UpdatingElement,
|
||||
} from 'lit-element';
|
||||
export {
|
||||
AttributePart,
|
||||
BooleanAttributePart,
|
||||
directive,
|
||||
EventPart,
|
||||
html,
|
||||
isDirective,
|
||||
isPrimitive,
|
||||
noChange,
|
||||
NodePart,
|
||||
nothing,
|
||||
PropertyPart,
|
||||
render,
|
||||
svg,
|
||||
SVGTemplateResult,
|
||||
TemplateResult,
|
||||
removeNodes,
|
||||
reparentNodes,
|
||||
} from 'lit-html';
|
||||
|
|
|
|||
|
|
@ -1,53 +1,63 @@
|
|||
// lit-element
|
||||
export {
|
||||
css,
|
||||
html,
|
||||
CSSResult,
|
||||
// decorators.js
|
||||
customElement,
|
||||
// updating-element.js
|
||||
defaultConverter,
|
||||
eventOptions,
|
||||
LitElement,
|
||||
notEqual,
|
||||
property,
|
||||
query,
|
||||
queryAll,
|
||||
// css-tag.js
|
||||
adoptStyles,
|
||||
css,
|
||||
getCompatibleStyle,
|
||||
supportsAdoptingStyleSheets,
|
||||
unsafeCSS,
|
||||
UpdatingElement,
|
||||
} from 'lit-element';
|
||||
// lit-html
|
||||
export {
|
||||
AttributePart,
|
||||
BooleanAttributePart,
|
||||
directive,
|
||||
EventPart,
|
||||
html,
|
||||
isDirective,
|
||||
isPrimitive,
|
||||
noChange,
|
||||
NodePart,
|
||||
nothing,
|
||||
PropertyPart,
|
||||
render,
|
||||
notEqual,
|
||||
ReactiveElement,
|
||||
svg,
|
||||
SVGTemplateResult,
|
||||
TemplateResult,
|
||||
reparentNodes,
|
||||
removeNodes,
|
||||
} from 'lit-html';
|
||||
export { asyncAppend } from 'lit-html/directives/async-append.js';
|
||||
export { asyncReplace } from 'lit-html/directives/async-replace.js';
|
||||
export { cache } from 'lit-html/directives/cache.js';
|
||||
export { classMap } from 'lit-html/directives/class-map.js';
|
||||
export { guard } from 'lit-html/directives/guard.js';
|
||||
export { ifDefined } from 'lit-html/directives/if-defined.js';
|
||||
export { repeat } from 'lit-html/directives/repeat.js';
|
||||
export { styleMap } from 'lit-html/directives/style-map.js';
|
||||
export { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
|
||||
export { until } from 'lit-html/directives/until.js';
|
||||
export { render as renderShady } from 'lit-html/lib/shady-render.js';
|
||||
noChange,
|
||||
nothing,
|
||||
render,
|
||||
LitElement,
|
||||
defaultConverter,
|
||||
} from 'lit';
|
||||
|
||||
export {
|
||||
customElement,
|
||||
property,
|
||||
state,
|
||||
eventOptions,
|
||||
query,
|
||||
queryAll,
|
||||
queryAsync,
|
||||
queryAssignedNodes,
|
||||
} from 'lit/decorators.js';
|
||||
|
||||
export { directive, Directive } from 'lit/directive.js';
|
||||
|
||||
export { AsyncDirective } from 'lit/async-directive.js';
|
||||
|
||||
export {
|
||||
isPrimitive,
|
||||
TemplateResultType,
|
||||
isTemplateResult,
|
||||
isDirectiveResult,
|
||||
getDirectiveClass,
|
||||
isSingleExpression,
|
||||
insertPart,
|
||||
setChildPartValue,
|
||||
setCommittedValue,
|
||||
getCommittedValue,
|
||||
removePart,
|
||||
clearPart,
|
||||
} from 'lit/directive-helpers.js';
|
||||
|
||||
export { asyncAppend } from 'lit/directives/async-append.js';
|
||||
export { asyncReplace } from 'lit/directives/async-replace.js';
|
||||
export { cache } from 'lit/directives/cache.js';
|
||||
export { classMap } from 'lit/directives/class-map.js';
|
||||
export { guard } from 'lit/directives/guard.js';
|
||||
export { ifDefined } from 'lit/directives/if-defined.js';
|
||||
export { repeat } from 'lit/directives/repeat.js';
|
||||
export { styleMap } from 'lit/directives/style-map.js';
|
||||
export { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
||||
export { until } from 'lit/directives/until.js';
|
||||
|
||||
// open-wc
|
||||
export { ScopedElementsMixin } from '@open-wc/scoped-elements';
|
||||
export { dedupeMixin } from '@open-wc/dedupe-mixin';
|
||||
|
|
|
|||
|
|
@ -37,9 +37,8 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@open-wc/dedupe-mixin": "^1.2.18",
|
||||
"@open-wc/scoped-elements": "^1.3.3",
|
||||
"lit-element": "~2.4.0",
|
||||
"lit-html": "^1.3.0"
|
||||
"@open-wc/scoped-elements": "^2.0.0-next.3",
|
||||
"lit": "^2.0.0-rc.2"
|
||||
},
|
||||
"keywords": [
|
||||
"lion",
|
||||
|
|
|
|||
|
|
@ -60,8 +60,8 @@ const DisabledMixinImplementation = superclass =>
|
|||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
if (name === 'disabled') {
|
||||
if (this.__isUserSettingDisabled) {
|
||||
this.__restoreDisabledTo = this.disabled;
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ const DisabledWithTabIndexMixinImplementation = superclass =>
|
|||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
||||
if (name === 'disabled') {
|
||||
if (this.disabled) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import { LitElement } from '../index.js';
|
||||
import { DelegateMixin } from '../src/DelegateMixin.js';
|
||||
|
|
@ -83,9 +84,9 @@ describe('DelegateMixin', () => {
|
|||
const element = await fixture(`<${tag}><button slot="button">click me</button></${tag}>`);
|
||||
const cb = sinon.spy();
|
||||
element.addEventListener('click', cb);
|
||||
const childEl = /** @type {HTMLElement} */ (Array.from(element.children)?.find(
|
||||
child => child.slot === 'button',
|
||||
));
|
||||
const childEl = /** @type {HTMLElement} */ (
|
||||
Array.from(element.children)?.find(child => child.slot === 'button')
|
||||
);
|
||||
childEl?.click();
|
||||
expect(cb.callCount).to.equal(1);
|
||||
});
|
||||
|
|
@ -343,14 +344,14 @@ describe('DelegateMixin', () => {
|
|||
const tagName = unsafeStatic(tag);
|
||||
|
||||
// Here, the Application Developerd tries to set the type via attribute
|
||||
const elementAttr = /** @type {ScheduledElement} */ (await fixture(
|
||||
`<${tag} type="radio"></${tag}>`,
|
||||
));
|
||||
const elementAttr = /** @type {ScheduledElement} */ (
|
||||
await fixture(`<${tag} type="radio"></${tag}>`)
|
||||
);
|
||||
expect(elementAttr.scheduledElement?.type).to.equal('radio');
|
||||
// Here, the Application Developer tries to set the type via property
|
||||
const elementProp = /** @type {ScheduledElement} */ (await fixture(
|
||||
html`<${tagName} .type=${'radio'}></${tagName}>`,
|
||||
));
|
||||
const elementProp = /** @type {ScheduledElement} */ (
|
||||
await fixture(html`<${tagName} .type=${'radio'}></${tagName}>`)
|
||||
);
|
||||
expect(elementProp.scheduledElement?.type).to.equal('radio');
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { LitElement } from '../index.js';
|
||||
import { DisabledMixin } from '../src/DisabledMixin.js';
|
||||
|
||||
|
|
@ -9,9 +10,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('reflects disabled to attribute', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
expect(el.hasAttribute('disabled')).to.be.false;
|
||||
el.makeRequestToBeDisabled();
|
||||
el.disabled = true;
|
||||
|
|
@ -20,9 +21,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('can be requested to be disabled', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
await el.updateComplete;
|
||||
|
|
@ -30,9 +31,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will not allow to become enabled after makeRequestToBeDisabled()', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
|
||||
|
|
@ -41,18 +42,18 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will stay disabled after retractRequestToBeDisabled() if it was disabled before', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
});
|
||||
|
||||
it('will become enabled after retractRequestToBeDisabled() if it was enabled before', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
@ -60,9 +61,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('may allow multiple calls to makeRequestToBeDisabled()', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
el.makeRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
@ -70,9 +71,9 @@ describe('DisabledMixin', () => {
|
|||
});
|
||||
|
||||
it('will restore last state after retractRequestToBeDisabled()', async () => {
|
||||
const el = /** @type {CanBeDisabled} */ (await fixture(
|
||||
html`<can-be-disabled></can-be-disabled>`,
|
||||
));
|
||||
const el = /** @type {CanBeDisabled} */ (
|
||||
await fixture(html`<can-be-disabled></can-be-disabled>`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
el.disabled = true;
|
||||
el.retractRequestToBeDisabled();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable lit-a11y/tabindex-no-positive */
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { LitElement } from '../index.js';
|
||||
import { DisabledWithTabIndexMixin } from '../src/DisabledWithTabIndexMixin.js';
|
||||
|
||||
|
|
@ -11,17 +11,17 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('has an initial tabIndex of 0', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html` <can-be-disabled-with-tab-index></can-be-disabled-with-tab-index> `)
|
||||
);
|
||||
expect(el.tabIndex).to.equal(0);
|
||||
expect(el.getAttribute('tabindex')).to.equal('0');
|
||||
});
|
||||
|
||||
it('sets tabIndex to -1 if disabled', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html` <can-be-disabled-with-tab-index></can-be-disabled-with-tab-index> `)
|
||||
);
|
||||
el.disabled = true;
|
||||
expect(el.tabIndex).to.equal(-1);
|
||||
await el.updateComplete;
|
||||
|
|
@ -29,9 +29,11 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('disabled does not override user provided tabindex', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5" disabled></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5" disabled></can-be-disabled-with-tab-index>
|
||||
`)
|
||||
);
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
el.disabled = false;
|
||||
await el.updateComplete;
|
||||
|
|
@ -39,9 +41,11 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('can be disabled imperatively', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`)
|
||||
);
|
||||
expect(el.getAttribute('tabindex')).to.equal('-1');
|
||||
|
||||
el.disabled = false;
|
||||
|
|
@ -56,9 +60,9 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('will not allow to change tabIndex after makeRequestToBeDisabled()', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html` <can-be-disabled-with-tab-index></can-be-disabled-with-tab-index> `)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
|
||||
el.tabIndex = 5;
|
||||
|
|
@ -68,9 +72,11 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('will restore last tabIndex after retractRequestToBeDisabled()', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5"></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html`
|
||||
<can-be-disabled-with-tab-index tabindex="5"></can-be-disabled-with-tab-index>
|
||||
`)
|
||||
);
|
||||
el.makeRequestToBeDisabled();
|
||||
expect(el.tabIndex).to.equal(-1);
|
||||
await el.updateComplete;
|
||||
|
|
@ -97,9 +103,11 @@ describe('DisabledWithTabIndexMixin', () => {
|
|||
});
|
||||
|
||||
it('may allow multiple calls to retractRequestToBeDisabled', async () => {
|
||||
const el = /** @type {WithTabIndex} */ (await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`));
|
||||
const el = /** @type {WithTabIndex} */ (
|
||||
await fixture(html`
|
||||
<can-be-disabled-with-tab-index disabled></can-be-disabled-with-tab-index>
|
||||
`)
|
||||
);
|
||||
el.retractRequestToBeDisabled();
|
||||
el.retractRequestToBeDisabled();
|
||||
expect(el.disabled).to.be.true;
|
||||
|
|
|
|||
|
|
@ -108,9 +108,9 @@ describe('SlotMixin', () => {
|
|||
const tag = defineCE(SlotPrivateText);
|
||||
const el = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));
|
||||
expect(el.didCreateConditionalSlot()).to.be.true;
|
||||
const elUserSlot = /** @type {SlotPrivateText} */ (await fixture(
|
||||
`<${tag}><p slot="conditional">foo</p><${tag}>`,
|
||||
));
|
||||
const elUserSlot = /** @type {SlotPrivateText} */ (
|
||||
await fixture(`<${tag}><p slot="conditional">foo</p><${tag}>`)
|
||||
);
|
||||
expect(elUserSlot.didCreateConditionalSlot()).to.be.false;
|
||||
renderSlot = false;
|
||||
const elNoSlot = /** @type {SlotPrivateText} */ (await fixture(`<${tag}><${tag}>`));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { defineCE, expect, fixture, html } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { css, LitElement } from '../index.js';
|
||||
import { UpdateStylesMixin } from '../src/UpdateStylesMixin.js';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture as _fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { runOverlayMixinSuite } from '../../overlays/test-suites/OverlayMixin.suite.js';
|
||||
import '@lion/dialog/define';
|
||||
|
||||
|
|
@ -62,9 +63,9 @@ describe('lion-dialog', () => {
|
|||
el._overlayInvokerNode.click();
|
||||
expect(el.opened).to.be.true;
|
||||
|
||||
const overlaysContainer = /** @type {HTMLElement} */ (document.querySelector(
|
||||
'.global-overlays',
|
||||
));
|
||||
const overlaysContainer = /** @type {HTMLElement} */ (
|
||||
document.querySelector('.global-overlays')
|
||||
);
|
||||
const wrapperNode = Array.from(overlaysContainer.children)[1];
|
||||
const nestedDialog = /** @type {LionDialog} */ (wrapperNode.querySelector('lion-dialog'));
|
||||
// @ts-expect-error you're not allowed to call protected _overlayInvokerNode in public context, but for testing it's okay
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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();
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { FormRegisteringMixin } from './registration/FormRegisteringMixin.js';
|
|||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
* @typedef {import('@lion/core').CSSResult} CSSResult
|
||||
* @typedef {import('@lion/core').CSSResultArray} CSSResultArray
|
||||
* @typedef {import('@lion/core').nothing} nothing
|
||||
* @typedef {import('@lion/core/types/SlotMixinTypes').SlotsMap} SlotsMap
|
||||
* @typedef {import('./validate/LionValidationFeedback').LionValidationFeedback} LionValidationFeedback
|
||||
* @typedef {import('../types/choice-group/ChoiceInputMixinTypes').ChoiceInputHost} ChoiceInputHost
|
||||
|
|
@ -765,7 +764,6 @@ const FormControlMixinImplementation = superclass =>
|
|||
if (this._ariaLabelledNodes.includes(element)) {
|
||||
this._ariaLabelledNodes.splice(this._ariaLabelledNodes.indexOf(element), 1);
|
||||
this._ariaLabelledNodes = [...this._ariaLabelledNodes];
|
||||
|
||||
// This value will be read when we need to reflect to attr
|
||||
/** @type {boolean} */
|
||||
this.__reorderAriaLabelledNodes = false;
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ const FormatMixinImplementation = superclass =>
|
|||
* @param {string} name
|
||||
* @param {any} oldVal
|
||||
*/
|
||||
requestUpdateInternal(name, oldVal) {
|
||||
super.requestUpdateInternal(name, oldVal);
|
||||
requestUpdate(name, oldVal) {
|
||||
super.requestUpdate(name, oldVal);
|
||||
|
||||
if (name === 'modelValue' && this.modelValue !== oldVal) {
|
||||
this._onModelValueChanged({ modelValue: this.modelValue }, { modelValue: oldVal });
|
||||
|
|
@ -525,8 +525,9 @@ const FormatMixinImplementation = superclass =>
|
|||
this._inputNode.removeEventListener('input', this._proxyInputEvent);
|
||||
this._inputNode.removeEventListener(
|
||||
this.formatOn,
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this
|
||||
._reflectBackFormattedValueDebounced),
|
||||
/** @type {EventListenerOrEventListenerObject} */ (
|
||||
this._reflectBackFormattedValueDebounced
|
||||
),
|
||||
);
|
||||
this._inputNode.removeEventListener('compositionstart', this.__onCompositionEvent);
|
||||
this._inputNode.removeEventListener('compositionend', this.__onCompositionEvent);
|
||||
|
|
|
|||
|
|
@ -35,14 +35,14 @@ const InteractionStateMixinImplementation = superclass =>
|
|||
* @param {PropertyKey} name
|
||||
* @param {*} oldVal
|
||||
*/
|
||||
requestUpdateInternal(name, oldVal) {
|
||||
super.requestUpdateInternal(name, oldVal);
|
||||
requestUpdate(name, oldVal) {
|
||||
super.requestUpdate(name, oldVal);
|
||||
if (name === 'touched' && this.touched !== oldVal) {
|
||||
this._onTouchedChanged();
|
||||
}
|
||||
|
||||
if (name === 'modelValue') {
|
||||
// We do this in requestUpdateInternal because we don't want to fire another re-render (e.g. when doing this in updated)
|
||||
// We do this in requestUpdate because we don't want to fire another re-render (e.g. when doing this in updated)
|
||||
// Furthermore, we cannot do it on model-value-changed event because it isn't fired initially.
|
||||
this.filled = !this._isEmpty();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ const ChoiceInputMixinImplementation = superclass =>
|
|||
* @param {string} name
|
||||
* @param {any} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
||||
if (name === 'modelValue') {
|
||||
if (this.modelValue.checked !== this.checked) {
|
||||
|
|
@ -298,7 +298,7 @@ const ChoiceInputMixinImplementation = superclass =>
|
|||
/**
|
||||
* @override
|
||||
* hasChanged is designed for async (updated) callback, also check for sync
|
||||
* (requestUpdateInternal) callback
|
||||
* (requestUpdate) callback
|
||||
* @param {{ modelValue:unknown }} newV
|
||||
* @param {{ modelValue:unknown }} [old]
|
||||
* @protected
|
||||
|
|
@ -309,7 +309,7 @@ const ChoiceInputMixinImplementation = superclass =>
|
|||
_old = old.modelValue;
|
||||
}
|
||||
// @ts-expect-error [external]: lit private property
|
||||
if (this.constructor._classProperties.get('modelValue').hasChanged(modelValue, _old)) {
|
||||
if (this.constructor.elementProperties.get('modelValue').hasChanged(modelValue, _old)) {
|
||||
super._onModelValueChanged({ modelValue });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,12 +360,11 @@ const FormGroupMixinImplementation = superclass =>
|
|||
if (values && typeof values === 'object') {
|
||||
Object.keys(values).forEach(name => {
|
||||
if (Array.isArray(this.formElements[name])) {
|
||||
this.formElements[name].forEach((
|
||||
/** @type {FormControl} */ el,
|
||||
/** @type {number} */ index,
|
||||
) => {
|
||||
el[property] = values[name][index]; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
this.formElements[name].forEach(
|
||||
(/** @type {FormControl} */ el, /** @type {number} */ index) => {
|
||||
el[property] = values[name][index]; // eslint-disable-line no-param-reassign
|
||||
},
|
||||
);
|
||||
}
|
||||
if (this.formElements[name]) {
|
||||
this.formElements[name][property] = values[name];
|
||||
|
|
|
|||
|
|
@ -31,13 +31,13 @@ const FormRegistrarPortalMixinImplementation = superclass =>
|
|||
* @type {(FormRegistrarPortalHost & HTMLElement) | undefined}
|
||||
*/
|
||||
this.registrationTarget = undefined;
|
||||
this.__redispatchEventForFormRegistrarPortalMixin = this.__redispatchEventForFormRegistrarPortalMixin.bind(
|
||||
this,
|
||||
);
|
||||
this.__redispatchEventForFormRegistrarPortalMixin =
|
||||
this.__redispatchEventForFormRegistrarPortalMixin.bind(this);
|
||||
this.addEventListener(
|
||||
'form-element-register',
|
||||
/** @type {EventListenerOrEventListenerObject} */ (this
|
||||
.__redispatchEventForFormRegistrarPortalMixin),
|
||||
/** @type {EventListenerOrEventListenerObject} */ (
|
||||
this.__redispatchEventForFormRegistrarPortalMixin
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import { dedupeMixin } from '@lion/core';
|
|||
* `updateSync` will only be called when new value differs from old value.
|
||||
* See: https://lit-element.polymer-project.org/guide/lifecycle#haschanged
|
||||
* - it is a stable abstraction on top of a protected/non official lifecycle LitElement api.
|
||||
* Whenever the implementation of `requestUpdateInternal` changes (this happened in the past for
|
||||
* Whenever the implementation of `requestUpdate` changes (this happened in the past for
|
||||
* `requestUpdate`) we only have to change our abstraction instead of all our components
|
||||
* @type {SyncUpdatableMixin}
|
||||
* @param {import('@open-wc/dedupe-mixin').Constructor<import('@lion/core').LitElement>} superclass
|
||||
|
|
@ -64,7 +64,7 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|||
*/
|
||||
static __syncUpdatableHasChanged(name, newValue, oldValue) {
|
||||
// @ts-expect-error [external]: accessing private lit property
|
||||
const properties = this._classProperties;
|
||||
const properties = this.elementProperties;
|
||||
if (properties.get(name) && properties.get(name).hasChanged) {
|
||||
return properties.get(name).hasChanged(newValue, oldValue);
|
||||
}
|
||||
|
|
@ -74,8 +74,10 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|||
/** @private */
|
||||
__syncUpdatableInitialize() {
|
||||
const ns = this.__SyncUpdatableNamespace;
|
||||
const ctor = /** @type {typeof SyncUpdatableMixin & typeof import('../../types/utils/SyncUpdatableMixinTypes').SyncUpdatableHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof SyncUpdatableMixin & typeof import('../../types/utils/SyncUpdatableMixinTypes').SyncUpdatableHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
|
||||
ns.initialized = true;
|
||||
// Empty queue...
|
||||
|
|
@ -93,14 +95,16 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|||
* @param {string} name
|
||||
* @param {*} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
||||
this.__SyncUpdatableNamespace = this.__SyncUpdatableNamespace || {};
|
||||
const ns = this.__SyncUpdatableNamespace;
|
||||
|
||||
const ctor = /** @type {typeof SyncUpdatableMixin & typeof import('../../types/utils/SyncUpdatableMixinTypes').SyncUpdatableHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof SyncUpdatableMixin & typeof import('../../types/utils/SyncUpdatableMixinTypes').SyncUpdatableHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
// Before connectedCallback: queue
|
||||
if (!ns.initialized) {
|
||||
ns.queue = ns.queue || new Set();
|
||||
|
|
@ -114,7 +118,7 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|||
}
|
||||
|
||||
/**
|
||||
* An abstraction that has the exact same api as `requestUpdateInternal`, but taking
|
||||
* An abstraction that has the exact same api as `requestUpdate`, but taking
|
||||
* into account:
|
||||
* - [member order independence](https://github.com/webcomponents/gold-standard/wiki/Member-Order-Independence)
|
||||
* - property effects start when all (light) dom has initialized (on firstUpdated)
|
||||
|
|
@ -122,7 +126,7 @@ const SyncUpdatableMixinImplementation = superclass =>
|
|||
* - compatible with propertyAccessor.`hasChanged`: no manual checks needed or accidentally
|
||||
* run property effects / events when no change happened
|
||||
* effects when values didn't change
|
||||
* All code previously present in requestUpdateInternal can be placed in this method.
|
||||
* All code previously present in requestUpdate can be placed in this method.
|
||||
* @param {string} name
|
||||
* @param {*} oldValue
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -39,8 +39,10 @@ export const ValidateMixinImplementation = superclass =>
|
|||
SyncUpdatableMixin(DisabledMixin(SlotMixin(ScopedElementsMixin(superclass)))),
|
||||
) {
|
||||
static get scopedElements() {
|
||||
const scopedElementsCtor = /** @type {typeof import('@open-wc/scoped-elements/src/types').ScopedElementsHost} */ (super
|
||||
.constructor);
|
||||
const scopedElementsCtor =
|
||||
/** @type {typeof import('@open-wc/scoped-elements/src/types').ScopedElementsHost} */ (
|
||||
super.constructor
|
||||
);
|
||||
return {
|
||||
...scopedElementsCtor.scopedElements,
|
||||
'lion-validation-feedback': LionValidationFeedback,
|
||||
|
|
@ -482,10 +484,12 @@ export const ValidateMixinImplementation = superclass =>
|
|||
* @private
|
||||
*/
|
||||
__executeResultValidators(regularValidationResult) {
|
||||
const resultValidators = /** @type {ResultValidator[]} */ (this._allValidators.filter(v => {
|
||||
const vCtor = /** @type {typeof Validator} */ (v.constructor);
|
||||
return !vCtor.async && v instanceof ResultValidator;
|
||||
}));
|
||||
const resultValidators = /** @type {ResultValidator[]} */ (
|
||||
this._allValidators.filter(v => {
|
||||
const vCtor = /** @type {typeof Validator} */ (v.constructor);
|
||||
return !vCtor.async && v instanceof ResultValidator;
|
||||
})
|
||||
);
|
||||
|
||||
return resultValidators.filter(v =>
|
||||
v.executeOnResults({
|
||||
|
|
@ -511,8 +515,10 @@ export const ValidateMixinImplementation = superclass =>
|
|||
this.__validationResult = [...resultOutCome, ...syncAndAsyncOutcome];
|
||||
// this._storeResultsOnInstance(this.__validationResult);
|
||||
|
||||
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
|
||||
/** @type {Object.<string, Object.<string, boolean>>} */
|
||||
const validationStates = ctor.validationTypes.reduce(
|
||||
|
|
@ -582,8 +588,10 @@ export const ValidateMixinImplementation = superclass =>
|
|||
console.error(errorMessage, this);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
if (ctor.validationTypes.indexOf(v.type) === -1) {
|
||||
const vCtor = /** @type {typeof Validator} */ (v.constructor);
|
||||
// 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('hasFeedbackFor')
|
||||
) {
|
||||
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
||||
this.showsFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
||||
.map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined))
|
||||
.filter(Boolean));
|
||||
this.showsFeedbackFor = /** @type {string[]} */ (
|
||||
ctor.validationTypes
|
||||
.map(type => (this._hasFeedbackVisibleFor(type) ? type : undefined))
|
||||
.filter(Boolean)
|
||||
);
|
||||
this._updateFeedbackComponent();
|
||||
}
|
||||
|
||||
|
|
@ -791,9 +803,9 @@ export const ValidateMixinImplementation = superclass =>
|
|||
}
|
||||
|
||||
if (changedProperties.has('validationStates')) {
|
||||
const prevStates = /** @type {{[key: string]: object;}} */ (changedProperties.get(
|
||||
'validationStates',
|
||||
));
|
||||
const prevStates = /** @type {{[key: string]: object;}} */ (
|
||||
changedProperties.get('validationStates')
|
||||
);
|
||||
if (prevStates) {
|
||||
Object.entries(this.validationStates).forEach(([type, feedbackObj]) => {
|
||||
if (
|
||||
|
|
@ -811,21 +823,25 @@ export const ValidateMixinImplementation = superclass =>
|
|||
* @protected
|
||||
*/
|
||||
_updateShouldShowFeedbackFor() {
|
||||
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
|
||||
// Necessary typecast because types aren't smart enough to understand that we filter out undefined
|
||||
const newShouldShowFeedbackFor = /** @type {string[]} */ (ctor.validationTypes
|
||||
.map(type =>
|
||||
this.feedbackCondition(
|
||||
type,
|
||||
this._feedbackConditionMeta,
|
||||
this._showFeedbackConditionFor.bind(this),
|
||||
const newShouldShowFeedbackFor = /** @type {string[]} */ (
|
||||
ctor.validationTypes
|
||||
.map(type =>
|
||||
this.feedbackCondition(
|
||||
type,
|
||||
this._feedbackConditionMeta,
|
||||
this._showFeedbackConditionFor.bind(this),
|
||||
)
|
||||
? type
|
||||
: undefined,
|
||||
)
|
||||
? type
|
||||
: undefined,
|
||||
)
|
||||
.filter(Boolean));
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
if (JSON.stringify(this.shouldShowFeedbackFor) !== JSON.stringify(newShouldShowFeedbackFor)) {
|
||||
this.shouldShowFeedbackFor = newShouldShowFeedbackFor;
|
||||
|
|
@ -841,8 +857,10 @@ export const ValidateMixinImplementation = superclass =>
|
|||
* @protected
|
||||
*/
|
||||
_prioritizeAndFilterFeedback({ validationResult }) {
|
||||
const ctor = /** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (this
|
||||
.constructor);
|
||||
const ctor =
|
||||
/** @type {typeof import('../../types/validate/ValidateMixinTypes').ValidateHost} */ (
|
||||
this.constructor
|
||||
);
|
||||
const types = ctor.validationTypes;
|
||||
// Sort all validators based on the type provided.
|
||||
const res = validationResult
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
static get validatorName() {
|
||||
return 'IsEmail';
|
||||
|
|
|
|||
|
|
@ -36,11 +36,13 @@ export const runRegistrationSuite = customConfig => {
|
|||
const { parentTagString, childTagString } = cfg;
|
||||
|
||||
it('can register a formElement', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(html`
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`
|
||||
<${parentTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
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 () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(html`
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`
|
||||
<${parentTag}>
|
||||
<div>
|
||||
<${childTag}></${childTag}>
|
||||
</div>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formElements.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('supports nested registration parents', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(html`
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`
|
||||
<${parentTag}>
|
||||
<${parentTag} class="sub-group">
|
||||
<${childTag}></${childTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formElements.length).to.equal(1);
|
||||
|
||||
const subGroup = /** @type {RegistrarClass} */ (el.querySelector('.sub-group'));
|
||||
|
|
@ -95,20 +101,24 @@ export const runRegistrationSuite = customConfig => {
|
|||
}
|
||||
const tagWrapperString = defineCE(PerformUpdate);
|
||||
const tagWrapper = unsafeStatic(tagWrapperString);
|
||||
const el = /** @type {PerformUpdate} */ (await fixture(html`
|
||||
const el = /** @type {PerformUpdate} */ (
|
||||
await fixture(html`
|
||||
<${tagWrapper}>
|
||||
<${childTag}></${childTag}>
|
||||
</${tagWrapper}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formElements.length).to.equal(1);
|
||||
});
|
||||
|
||||
it('can dynamically add/remove elements', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(html`
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`
|
||||
<${parentTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const newField = await fixture(html`
|
||||
<${childTag}></${childTag}>
|
||||
`);
|
||||
|
|
@ -122,20 +132,24 @@ export const runRegistrationSuite = customConfig => {
|
|||
});
|
||||
|
||||
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}>
|
||||
<${childTag} pos="0"></${childTag}>
|
||||
<${childTag} pos="1"></${childTag}>
|
||||
<${childTag} pos="2"></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
/** INSERT field before the pos=1 */
|
||||
/**
|
||||
* @typedef {Object.<string, string>} prop
|
||||
*/
|
||||
const newField = /** @type {RegisteringClass & prop} */ (await fixture(html`
|
||||
const newField = /** @type {RegisteringClass & prop} */ (
|
||||
await fixture(html`
|
||||
<${childTag}></${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
newField.setAttribute('pos', 'inserted-before-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');
|
||||
|
||||
/** 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}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
topField.setAttribute('pos', 'inserted-before-0');
|
||||
el.insertBefore(topField, el.children[0]);
|
||||
|
||||
|
|
@ -159,9 +175,9 @@ export const runRegistrationSuite = customConfig => {
|
|||
|
||||
describe('FormRegistrarPortalMixin', () => {
|
||||
it('forwards registrations to the .registrationTarget', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(
|
||||
html`<${parentTag}></${parentTag}>`,
|
||||
));
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`<${parentTag}></${parentTag}>`)
|
||||
);
|
||||
await fixture(html`
|
||||
<${portalTag} .registrationTarget=${el}>
|
||||
<${childTag}></${childTag}>
|
||||
|
|
@ -172,9 +188,9 @@ export const runRegistrationSuite = customConfig => {
|
|||
});
|
||||
|
||||
it('can dynamically add/remove elements', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(
|
||||
html`<${parentTag}></${parentTag}>`,
|
||||
));
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`<${parentTag}></${parentTag}>`)
|
||||
);
|
||||
const portal = await fixture(html`
|
||||
<${portalTag} .registrationTarget=${el}>
|
||||
<${childTag}></${childTag}>
|
||||
|
|
@ -194,13 +210,15 @@ export const runRegistrationSuite = customConfig => {
|
|||
});
|
||||
|
||||
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}>
|
||||
<${childTag}></${childTag}>
|
||||
<${childTag}></${childTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements.length).to.equal(3);
|
||||
|
||||
|
|
@ -232,9 +250,9 @@ export const runRegistrationSuite = customConfig => {
|
|||
});
|
||||
|
||||
it('keeps working if moving the portal itself', async () => {
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(
|
||||
html`<${parentTag}></${parentTag}>`,
|
||||
));
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`<${parentTag}></${parentTag}>`)
|
||||
);
|
||||
const portal = await fixture(html`
|
||||
<${portalTag} .registrationTarget=${el}>
|
||||
<${childTag}></${childTag}>
|
||||
|
|
@ -270,9 +288,9 @@ export const runRegistrationSuite = customConfig => {
|
|||
);
|
||||
const delayedPortalTag = unsafeStatic(delayedPortalString);
|
||||
|
||||
const el = /** @type {RegistrarClass} */ (await fixture(
|
||||
html`<${parentTag}></${parentTag}>`,
|
||||
));
|
||||
const el = /** @type {RegistrarClass} */ (
|
||||
await fixture(html`<${parentTag}></${parentTag}>`)
|
||||
);
|
||||
await fixture(html`
|
||||
<${delayedPortalTag} .registrationTarget=${el}>
|
||||
<${childTag}></${childTag}>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { parseDate } from '@lion/localize';
|
||||
import { aTimeout, defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { aTimeout, defineCE, expect, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { FormatMixin } from '../src/FormatMixin.js';
|
||||
import { Unparseable, Validator } from '../index.js';
|
||||
|
|
@ -95,7 +97,7 @@ export function runFormatMixinSuite(customConfig) {
|
|||
}
|
||||
|
||||
describe('FormatMixin', async () => {
|
||||
/** @type {{d: any}} */
|
||||
/** @type {{_$litStatic$: any}} */
|
||||
let tag;
|
||||
/** @type {FormatClass} */
|
||||
let nonFormat;
|
||||
|
|
@ -148,9 +150,9 @@ export function runFormatMixinSuite(customConfig) {
|
|||
*/
|
||||
describe('ModelValue', () => {
|
||||
it('fires `model-value-changed` for every programmatic modelValue change', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(
|
||||
html`<${tag}><input slot="input"></${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`<${tag}><input slot="input"></${tag}>`)
|
||||
);
|
||||
let counter = 0;
|
||||
let isTriggeredByUser = false;
|
||||
|
||||
|
|
@ -172,18 +174,19 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('fires `model-value-changed` for every user input, adding `isTriggeredByUser` in event detail', async () => {
|
||||
const formatEl = /** @type {FormatClass} */ (await fixture(
|
||||
html`<${tag}><input slot="input"></${tag}>`,
|
||||
));
|
||||
const formatEl = /** @type {FormatClass} */ (
|
||||
await fixture(html`<${tag}><input slot="input"></${tag}>`)
|
||||
);
|
||||
|
||||
let counter = 0;
|
||||
let isTriggeredByUser = false;
|
||||
formatEl.addEventListener('model-value-changed', (
|
||||
/** @param {CustomEvent} event */ event,
|
||||
) => {
|
||||
counter += 1;
|
||||
isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser;
|
||||
});
|
||||
formatEl.addEventListener(
|
||||
'model-value-changed',
|
||||
(/** @param {CustomEvent} event */ event) => {
|
||||
counter += 1;
|
||||
isTriggeredByUser = /** @type {CustomEvent} */ (event).detail.isTriggeredByUser;
|
||||
},
|
||||
);
|
||||
|
||||
mimicUserInput(formatEl, generateValueBasedOnType());
|
||||
expect(counter).to.equal(1);
|
||||
|
|
@ -205,7 +208,8 @@ export function runFormatMixinSuite(customConfig) {
|
|||
|
||||
it('synchronizes _inputNode.value as a fallback mechanism on init (when no modelValue provided)', async () => {
|
||||
// Note that in lion-field, the attribute would be put on <lion-field>, not on <input>
|
||||
const formatElem = /** @type {FormatClass} */ (await fixture(html`
|
||||
const formatElem = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
value="string"
|
||||
.formatter=${/** @param {string} value */ value => `foo: ${value}`}
|
||||
|
|
@ -215,7 +219,8 @@ export function runFormatMixinSuite(customConfig) {
|
|||
>
|
||||
<input slot="input" value="string" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// Now check if the format/parse/serialize loop has been triggered
|
||||
await formatElem.updateComplete;
|
||||
expect(formatElem.formattedValue).to.equal('foo: string');
|
||||
|
|
@ -228,20 +233,23 @@ export function runFormatMixinSuite(customConfig) {
|
|||
|
||||
describe('Unparseable values', () => {
|
||||
it('converts to Unparseable when wrong value inputted by user', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .parser=${
|
||||
/** @param {string} viewValue */ viewValue => Number(viewValue) || undefined
|
||||
}
|
||||
/** @param {string} viewValue */ viewValue => Number(viewValue) || undefined
|
||||
}
|
||||
>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.modelValue).to.be.an.instanceof(Unparseable);
|
||||
});
|
||||
|
||||
it('preserves the viewValue when unparseable', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.parser=${
|
||||
/** @param {string} viewValue */ viewValue => Number(viewValue) || undefined
|
||||
|
|
@ -249,14 +257,16 @@ export function runFormatMixinSuite(customConfig) {
|
|||
>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
mimicUserInput(el, 'test');
|
||||
expect(el.formattedValue).to.equal('test');
|
||||
expect(el.value).to.equal('test');
|
||||
});
|
||||
|
||||
it('displays the viewValue when modelValue is of type Unparseable', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.parser=${
|
||||
/** @param {string} viewValue */ viewValue => Number(viewValue) || undefined
|
||||
|
|
@ -264,17 +274,20 @@ export function runFormatMixinSuite(customConfig) {
|
|||
>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = new Unparseable('foo');
|
||||
expect(el.value).to.equal('foo');
|
||||
});
|
||||
|
||||
it('empty strings are not Unparseable', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<input slot="input" value="string">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// This could happen when the user erases the input value
|
||||
mimicUserInput(el, '');
|
||||
// For backwards compatibility, we keep the modelValue an empty string here.
|
||||
|
|
@ -303,11 +316,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
|
||||
describe('Presenting value to end user', () => {
|
||||
it('reflects back formatted value to user on leave', async () => {
|
||||
const formatEl = /** @type {FormatClass} */ (await fixture(html`
|
||||
const formatEl = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .formatter="${/** @param {string} value */ value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(formatEl);
|
||||
|
||||
const generatedViewValue = generateValueBasedOnType({ viewValue: true });
|
||||
|
|
@ -322,11 +337,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('reflects back .formattedValue immediately when .modelValue changed imperatively', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .formatter="${/** @param {string} value */ value => `foo: ${value}`}">
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
|
|
@ -351,7 +368,8 @@ export function runFormatMixinSuite(customConfig) {
|
|||
const parserSpy = sinon.spy(value => value.replace('foo: ', ''));
|
||||
const serializerSpy = sinon.spy(value => `[foo] ${value}`);
|
||||
const preprocessorSpy = sinon.spy(value => value.replace('bar', ''));
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.formatter=${formatterSpy}
|
||||
.parser=${parserSpy}
|
||||
|
|
@ -361,7 +379,8 @@ export function runFormatMixinSuite(customConfig) {
|
|||
>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(formatterSpy.called).to.be.true;
|
||||
expect(serializerSpy.called).to.be.true;
|
||||
|
||||
|
|
@ -407,11 +426,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
toggleValue: true,
|
||||
});
|
||||
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .formatter=${formatterSpy}>
|
||||
<input slot="input" .value="${generatedViewValue}">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(formatterSpy.callCount).to.equal(1);
|
||||
|
||||
el.hasFeedbackFor.push('error');
|
||||
|
|
@ -446,9 +467,11 @@ export function runFormatMixinSuite(customConfig) {
|
|||
|
||||
it('has formatOptions available in formatter', async () => {
|
||||
const formatterSpy = sinon.spy(value => `foo: ${value}`);
|
||||
const generatedViewValue = /** @type {string} */ (generateValueBasedOnType({
|
||||
viewValue: true,
|
||||
}));
|
||||
const generatedViewValue = /** @type {string} */ (
|
||||
generateValueBasedOnType({
|
||||
viewValue: true,
|
||||
})
|
||||
);
|
||||
await fixture(html`
|
||||
<${tag} value="${generatedViewValue}" .formatter="${formatterSpy}"
|
||||
.formatOptions="${{ locale: 'en-GB', decimalSeparator: '-' }}">
|
||||
|
|
@ -483,9 +506,11 @@ export function runFormatMixinSuite(customConfig) {
|
|||
}
|
||||
|
||||
it('sets formatOptions.mode to "pasted" (and restores to "auto")', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${reflectingTag}><input slot="input"></${reflectingTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const formatterSpy = sinon.spy(el, 'formatter');
|
||||
paste(el);
|
||||
expect(formatterSpy).to.be.called;
|
||||
|
|
@ -496,9 +521,11 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('sets protected value "_isPasting" for Subclassers', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${reflectingTag}><input slot="input"></${reflectingTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const formatterSpy = sinon.spy(el, 'formatter');
|
||||
paste(el);
|
||||
expect(formatterSpy).to.have.been.called;
|
||||
|
|
@ -510,9 +537,11 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('calls formatter and "_reflectBackOn()"', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const reflectBackSpy = sinon.spy(el, '_reflectBackOn');
|
||||
paste(el);
|
||||
|
|
@ -520,9 +549,11 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it(`updates viewValue when "_reflectBackOn()" configured to reflect`, async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${reflectingTag}><input slot="input"></${reflectingTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const reflectBackSpy = sinon.spy(el, '_reflectBackOn');
|
||||
paste(el);
|
||||
|
|
@ -536,11 +567,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
/** @type {?} */
|
||||
const generatedValue = generateValueBasedOnType();
|
||||
const parserSpy = sinon.spy();
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .parser="${parserSpy}">
|
||||
<input slot="input" .value="${generatedValue}">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(parserSpy.callCount).to.equal(1);
|
||||
// This could happen for instance in a reset
|
||||
|
|
@ -562,11 +595,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
const toBeCorrectedVal = `${val}$`;
|
||||
const preprocessorSpy = sinon.spy(v => v.replace(/\$$/g, ''));
|
||||
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .preprocessor=${preprocessorSpy}>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
|
|
@ -581,11 +616,13 @@ export function runFormatMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('does not preprocess during composition', async () => {
|
||||
const el = /** @type {FormatClass} */ (await fixture(html`
|
||||
const el = /** @type {FormatClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} .preprocessor=${(/** @type {string} */ v) => v.replace(/\$$/g, '')}>
|
||||
<input slot="input">
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
|
|
|
|||
|
|
@ -82,9 +82,9 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('sets an attribute "filled" if the input has a non-empty modelValue', async () => {
|
||||
const el = /** @type {IState} */ (await fixture(
|
||||
html`<${tag} .modelValue=${'hello'}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {IState} */ (
|
||||
await fixture(html`<${tag} .modelValue=${'hello'}></${tag}>`)
|
||||
);
|
||||
expect(el.hasAttribute('filled')).to.equal(true);
|
||||
el.modelValue = '';
|
||||
await el.updateComplete;
|
||||
|
|
@ -97,9 +97,11 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
it('fires "(touched|dirty)-state-changed" event when state changes', async () => {
|
||||
const touchedSpy = sinon.spy();
|
||||
const dirtySpy = sinon.spy();
|
||||
const el = /** @type {IState} */ (await fixture(
|
||||
html`<${tag} @touched-changed=${touchedSpy} @dirty-changed=${dirtySpy}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {IState} */ (
|
||||
await fixture(
|
||||
html`<${tag} @touched-changed=${touchedSpy} @dirty-changed=${dirtySpy}></${tag}>`,
|
||||
)
|
||||
);
|
||||
|
||||
el.touched = true;
|
||||
expect(touchedSpy.callCount).to.equal(1);
|
||||
|
|
@ -109,14 +111,18 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('sets prefilled once instantiated', async () => {
|
||||
const el = /** @type {IState} */ (await fixture(html`
|
||||
const el = /** @type {IState} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${'prefilled'}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.prefilled).to.be.true;
|
||||
|
||||
const nonPrefilled = /** @type {IState} */ (await fixture(html`
|
||||
const nonPrefilled = /** @type {IState} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${''}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(nonPrefilled.prefilled).to.be.false;
|
||||
});
|
||||
|
||||
|
|
@ -125,9 +131,9 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
(${cfg.allowedModelValueTypes.map(t => t.name).join(', ')})`, async () => {
|
||||
/** @typedef {{_inputNode: HTMLElement}} inputNodeInterface */
|
||||
|
||||
const el = /** @type {IState & inputNodeInterface} */ (await fixture(
|
||||
html`<${tag}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {IState & inputNodeInterface} */ (
|
||||
await fixture(html`<${tag}></${tag}>`)
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {*} modelValue
|
||||
|
|
@ -213,9 +219,11 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
|
||||
describe('Validation integration with states', () => {
|
||||
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}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-private] in test
|
||||
expect(el.shouldShowFeedbackFor).to.deep.equal([]);
|
||||
el.submitted = true;
|
||||
|
|
@ -225,9 +233,11 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
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}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
|
|
@ -257,9 +267,9 @@ export function runInteractionStateMixinSuite(customConfig) {
|
|||
}
|
||||
const tagLeaveString = defineCE(IStateCustomBlur);
|
||||
const tagLeave = unsafeStatic(tagLeaveString);
|
||||
const el = /** @type {IStateCustomBlur} */ (await fixture(
|
||||
html`<${tagLeave}></${tagLeave}>`,
|
||||
));
|
||||
const el = /** @type {IStateCustomBlur} */ (
|
||||
await fixture(html`<${tagLeave}></${tagLeave}>`)
|
||||
);
|
||||
el.dispatchEvent(new Event('custom-blur'));
|
||||
expect(el.touched).to.be.true;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -117,21 +117,25 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('validates on initialization (once form field has bootstrapped/initialized)', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
});
|
||||
|
||||
it('revalidates when ".modelValue" changes', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new AlwaysValid()]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
el.modelValue = 'x';
|
||||
|
|
@ -139,13 +143,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('revalidates when child ".modelValue" changes', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
._repropagationRole="${'fieldset'}"
|
||||
.validators=${[new AlwaysValid()]}
|
||||
.modelValue=${'myValue'}
|
||||
><lion-field id="child"><input slot="input"></lion-field></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
/** @type {LionField} */ (el.querySelector('#child')).modelValue = 'test';
|
||||
await el.updateComplete;
|
||||
|
|
@ -153,12 +159,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('revalidates when ".validators" changes', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new AlwaysValid()]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
el.validators = [new MinLength(3)];
|
||||
|
|
@ -166,12 +174,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('clears current results when ".modelValue" changes', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new AlwaysValid()]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const clearSpy = sinon.spy(el, '__clearValidationResults');
|
||||
|
|
@ -192,9 +202,11 @@ export function runValidateMixinSuite(customConfig) {
|
|||
it('firstly checks for empty values', async () => {
|
||||
const alwaysValid = new AlwaysValid();
|
||||
const alwaysValidExecuteSpy = sinon.spy(alwaysValid, 'execute');
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[alwaysValid]}>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-private] in test
|
||||
const isEmptySpy = sinon.spy(el, '__isEmpty');
|
||||
const validateSpy = sinon.spy(el, 'validate');
|
||||
|
|
@ -210,9 +222,11 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('secondly checks for synchronous Validators: creates RegularValidationResult', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new AlwaysValid()]}>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-private] in test
|
||||
const isEmptySpy = sinon.spy(el, '__isEmpty');
|
||||
// @ts-ignore [allow-private] in test
|
||||
|
|
@ -222,11 +236,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('thirdly schedules asynchronous Validators: creates RegularValidationResult', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new AlwaysValid(), new AsyncAlwaysValid()]}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-private] in test
|
||||
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
||||
// @ts-ignore [allow-private] in test
|
||||
|
|
@ -242,12 +258,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
}
|
||||
}
|
||||
|
||||
let el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
let el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new AlwaysValid(), new MyResult()]}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
||||
|
|
@ -278,11 +296,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
describe('Finalization', () => {
|
||||
it('fires private "validate-performed" event on every cycle', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new AlwaysValid(), new AsyncAlwaysInvalid()]}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const cbSpy = sinon.spy();
|
||||
el.addEventListener('validate-performed', cbSpy);
|
||||
el.modelValue = 'nonEmpty';
|
||||
|
|
@ -290,11 +310,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('resolves ".validateComplete" Promise', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new AsyncAlwaysInvalid()]}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = 'nonEmpty';
|
||||
// @ts-ignore [allow-private] in test
|
||||
const validateResolveSpy = sinon.spy(el, '__validateCompleteResolve');
|
||||
|
|
@ -395,9 +417,11 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('Validators will not be called on empty values', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new IsCat()]}>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
el.modelValue = 'cat';
|
||||
expect(el.validationStates.error.IsCat).to.be.undefined;
|
||||
|
|
@ -410,12 +434,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
it('Validators get retriggered on parameter change', async () => {
|
||||
const isCatValidator = new IsCat('Felix');
|
||||
const catSpy = sinon.spy(isCatValidator, 'execute');
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[isCatValidator]}
|
||||
.modelValue=${'cat'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = 'cat';
|
||||
expect(catSpy.callCount).to.equal(1);
|
||||
isCatValidator.param = 'Garfield';
|
||||
|
|
@ -459,13 +485,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
// default execution trigger is keyup (think of password availability backend)
|
||||
// can configure execution trigger (blur, etc?)
|
||||
it('handles "execute" functions returning promises', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.modelValue=${'dog'}
|
||||
.validators=${[new IsAsyncCat()]}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const validator = el.validators[0];
|
||||
expect(validator instanceof Validator).to.be.true;
|
||||
|
|
@ -476,9 +504,11 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('sets ".isPending/[is-pending]" when validation is in progress', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${'dog'}>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.isPending).to.be.false;
|
||||
expect(el.hasAttribute('is-pending')).to.be.false;
|
||||
|
||||
|
|
@ -498,11 +528,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const asyncV = new IsAsyncCat();
|
||||
const asyncVExecuteSpy = sinon.spy(asyncV, 'execute');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${'dog'}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// debounce started
|
||||
el.validators = [asyncV];
|
||||
expect(asyncVExecuteSpy.called).to.equal(0);
|
||||
|
|
@ -528,11 +560,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const asyncV = new IsAsyncCat();
|
||||
const asyncVAbortSpy = sinon.spy(asyncV, 'abortExecution');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${'dog'}>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// debounce started
|
||||
el.validators = [asyncV];
|
||||
expect(asyncVAbortSpy.called).to.equal(0);
|
||||
|
|
@ -546,7 +580,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const asyncV = new IsAsyncCat();
|
||||
const asyncVExecuteSpy = sinon.spy(asyncV, 'execute');
|
||||
|
||||
const el = /** @type {ValidateElement & { isFocused: boolean }} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement & { isFocused: boolean }} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.isFocused=${true}
|
||||
.modelValue=${'dog'}
|
||||
|
|
@ -558,7 +593,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
>
|
||||
${lightDom}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(asyncVExecuteSpy.called).to.equal(0);
|
||||
el.isFocused = false;
|
||||
|
|
@ -635,12 +671,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const resultValidator = new MySuccessResultValidator();
|
||||
const resultValidateSpy = sinon.spy(resultValidator, 'executeOnResults');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${withSuccessTag}
|
||||
.validators=${[new MinLength(3), resultValidator]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${withSuccessTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-private] in test
|
||||
const prevValidationResult = el.__prevValidationResult;
|
||||
// @ts-ignore [allow-private] in test
|
||||
|
|
@ -671,12 +709,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const validator = new AlwaysInvalid();
|
||||
const resultV = new AlwaysInvalidResult();
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[validator, resultV]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
const totalValidationResult = el.__validationResult;
|
||||
|
|
@ -686,12 +726,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
describe('Required Validator integration', () => {
|
||||
it('will result in erroneous state when form control is empty', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required()]}
|
||||
.modelValue=${''}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.validationStates.error.Required).to.be.true;
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
|
||||
|
|
@ -701,12 +743,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('calls private ".__isEmpty" by default', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required()]}
|
||||
.modelValue=${''}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const validator = /** @type {Validator} */ (el.validators.find(v => v instanceof Required));
|
||||
const executeSpy = sinon.spy(validator, 'execute');
|
||||
// @ts-ignore [allow-private] in test
|
||||
|
|
@ -725,12 +769,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const customRequiredTagString = defineCE(_isEmptyValidate);
|
||||
const customRequiredTag = unsafeStatic(customRequiredTagString);
|
||||
|
||||
const el = /** @type {_isEmptyValidate} */ (await fixture(html`
|
||||
const el = /** @type {_isEmptyValidate} */ (
|
||||
await fixture(html`
|
||||
<${customRequiredTag}
|
||||
.validators=${[new Required()]}
|
||||
.modelValue=${{ model: 'foo' }}
|
||||
>${lightDom}</${customRequiredTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const providedIsEmptySpy = sinon.spy(el, '_isEmpty');
|
||||
el.modelValue = { model: '' };
|
||||
|
|
@ -741,24 +787,28 @@ export function runValidateMixinSuite(customConfig) {
|
|||
it('prevents other Validators from being called when input is empty', async () => {
|
||||
const alwaysInvalid = new AlwaysInvalid();
|
||||
const alwaysInvalidSpy = sinon.spy(alwaysInvalid, 'execute');
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required(), alwaysInvalid]}
|
||||
.modelValue=${''}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(alwaysInvalidSpy.callCount).to.equal(0); // __isRequired returned false (invalid)
|
||||
el.modelValue = 'foo';
|
||||
expect(alwaysInvalidSpy.callCount).to.equal(1); // __isRequired returned true (valid)
|
||||
});
|
||||
|
||||
it('adds [aria-required="true"] to "._inputNode"', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required()]}
|
||||
.modelValue=${''}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_inputNode?.getAttribute('aria-required')).to.equal('true');
|
||||
|
|
@ -779,11 +829,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const preconfTag = unsafeStatic(preconfTagString);
|
||||
|
||||
it('can be stored for custom inputs', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${preconfTag}
|
||||
.validators=${[new MinLength(3)]}
|
||||
.modelValue=${'12'}
|
||||
></${preconfTag}>`));
|
||||
></${preconfTag}>`)
|
||||
);
|
||||
|
||||
expect(el.validationStates.error.AlwaysInvalid).to.be.true;
|
||||
expect(el.validationStates.error.MinLength).to.be.true;
|
||||
|
|
@ -800,10 +852,12 @@ export function runValidateMixinSuite(customConfig) {
|
|||
);
|
||||
const altPreconfTag = unsafeStatic(altPreconfTagString);
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${altPreconfTag}
|
||||
.modelValue=${'12'}
|
||||
></${altPreconfTag}>`));
|
||||
></${altPreconfTag}>`)
|
||||
);
|
||||
|
||||
expect(el.validationStates.error.MinLength).to.be.true;
|
||||
el.defaultValidators[0].param = 2;
|
||||
|
|
@ -811,10 +865,12 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('can be requested via "._allValidators" getter', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${preconfTag}
|
||||
.validators=${[new MinLength(3)]}
|
||||
></${preconfTag}>`));
|
||||
></${preconfTag}>`)
|
||||
);
|
||||
const { _allValidators } = getFormControlMembers(el);
|
||||
|
||||
expect(el.validators.length).to.equal(1);
|
||||
|
|
@ -834,11 +890,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
describe('State storage and reflection', () => {
|
||||
it('stores validity of individual Validators in ".validationStates.error[validator.validatorName]"', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.modelValue=${'a'}
|
||||
.validators=${[new MinLength(3), new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>`));
|
||||
>${lightDom}</${tag}>`)
|
||||
);
|
||||
|
||||
expect(el.validationStates.error.MinLength).to.be.true;
|
||||
expect(el.validationStates.error.AlwaysInvalid).to.be.true;
|
||||
|
|
@ -849,11 +907,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('removes "non active" states whenever modelValue becomes undefined', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new MinLength(3)]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = 'a';
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
expect(el.validationStates.error).to.not.eql({});
|
||||
|
|
@ -865,11 +925,13 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
it('clears current validation results when validators array updated', async () => {
|
||||
const validators = [new Required()];
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${validators}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
expect(el.validationStates.error).to.eql({ Required: true });
|
||||
|
||||
|
|
@ -883,7 +945,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('can be configured to change visibility conditions per type', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators="${[new Required({}, { type: 'error' })]}"
|
||||
.feedbackCondition="${(
|
||||
|
|
@ -897,7 +960,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
return defaultCondition(type);
|
||||
}}"
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.showsFeedbackFor).to.eql(['error']);
|
||||
});
|
||||
|
|
@ -905,13 +969,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
describe('Events', () => {
|
||||
it('fires "showsFeedbackForChanged" event async after feedbackData got synced to feedbackElement', async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(7)]}
|
||||
@showsFeedbackForChanged=${spy};
|
||||
@showsFeedbackForChanged=${spy}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = 'a';
|
||||
await el.updateComplete;
|
||||
expect(spy).to.have.callCount(1);
|
||||
|
|
@ -927,13 +993,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
it('fires "showsFeedbackFor{type}Changed" event async when type visibility changed', async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(7)]}
|
||||
@showsFeedbackForErrorChanged=${spy};
|
||||
@showsFeedbackForErrorChanged=${spy}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue = 'a';
|
||||
await el.updateComplete;
|
||||
expect(spy).to.have.callCount(1);
|
||||
|
|
@ -949,13 +1017,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
it('fires "{type}StateChanged" event async when type validity changed', async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(7)]}
|
||||
@errorStateChanged=${spy};
|
||||
@errorStateChanged=${spy}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(spy).to.have.callCount(0);
|
||||
|
||||
el.modelValue = 'a';
|
||||
|
|
@ -975,12 +1045,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
describe('Accessibility', () => {
|
||||
it.skip('calls "._inputNode.setCustomValidity(errorMessage)"', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.modelValue=${'123'}
|
||||
.validators=${[new MinLength(3, { message: 'foo' })]}>
|
||||
<input slot="input">
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
if (_inputNode) {
|
||||
|
|
@ -1013,7 +1085,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const customTypeTag = unsafeStatic(customTypeTagString);
|
||||
|
||||
it('supports additional validationTypes in .hasFeedbackFor', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${customTypeTag}
|
||||
.validators=${[
|
||||
new MinLength(2, { type: 'x' }),
|
||||
|
|
@ -1022,7 +1095,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
]}
|
||||
.modelValue=${'1234'}
|
||||
>${lightDom}</${customTypeTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.deep.equal([]);
|
||||
|
||||
el.modelValue = '123'; // triggers y
|
||||
|
|
@ -1036,7 +1110,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
});
|
||||
|
||||
it('supports additional validationTypes in .validationStates', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${customTypeTag}
|
||||
.validators=${[
|
||||
new MinLength(2, { type: 'x' }),
|
||||
|
|
@ -1045,7 +1120,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
]}
|
||||
.modelValue=${'1234'}
|
||||
>${lightDom}</${customTypeTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.validationStates).to.eql({
|
||||
x: {},
|
||||
error: {},
|
||||
|
|
@ -1076,7 +1152,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
it('orders feedback based on provided "validationTypes"', async () => {
|
||||
// we set submitted to always show error message in the test
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${customTypeTag}
|
||||
.submitted=${true}
|
||||
._visibleMessagesAmount=${Infinity}
|
||||
|
|
@ -1087,7 +1164,8 @@ export function runValidateMixinSuite(customConfig) {
|
|||
]}
|
||||
.modelValue=${'1'}
|
||||
>${lightDom}</${customTypeTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.feedbackComplete;
|
||||
|
|
@ -1132,13 +1210,15 @@ export function runValidateMixinSuite(customConfig) {
|
|||
const elTag = unsafeStatic(elTagString);
|
||||
|
||||
// we set submitted to always show errors
|
||||
const el = /** @type {ValidateHasX} */ (await fixture(html`
|
||||
const el = /** @type {ValidateHasX} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(2, { type: 'x' })]}
|
||||
.modelValue=${'1'}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.feedbackComplete;
|
||||
expect(el.hasX).to.be.true;
|
||||
expect(el.hasXVisible).to.be.true;
|
||||
|
|
@ -1186,14 +1266,16 @@ export function runValidateMixinSuite(customConfig) {
|
|||
|
||||
const spy = sinon.spy();
|
||||
// we set prefilled to always show errors
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.prefilled=${true}
|
||||
@hasFeedbackForXChanged=${spy}
|
||||
.validators=${[new MinLength(2, { type: 'x' })]}
|
||||
.modelValue=${'1'}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(spy).to.have.callCount(1);
|
||||
el.modelValue = '1';
|
||||
expect(spy).to.have.callCount(1);
|
||||
|
|
@ -1228,12 +1310,14 @@ export function runValidateMixinSuite(customConfig) {
|
|||
},
|
||||
);
|
||||
const elTag = unsafeStatic(elTagString);
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.validators=${[new AlwaysInvalid()]}
|
||||
.modelValue=${'myValue'}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const spy = sinon.spy(el, '_updateShouldShowFeedbackFor');
|
||||
|
|
@ -1282,14 +1366,16 @@ export function runValidateMixinSuite(customConfig) {
|
|||
},
|
||||
);
|
||||
const elTag = unsafeStatic(elTagString);
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.validators=${[
|
||||
new AlwaysInvalid({}, { type: 'error' }),
|
||||
new AlwaysInvalid({}, { type: 'info' }),
|
||||
]}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
for (const [modelValue, expected] of [
|
||||
['A', ['error']],
|
||||
|
|
|
|||
|
|
@ -66,9 +66,11 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
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}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
el.modelValue = 'a';
|
||||
await el.feedbackComplete;
|
||||
|
|
@ -87,14 +89,16 @@ export function runValidateMixinFeedbackPart() {
|
|||
}
|
||||
const elTagString = defineCE(ValidateElementCustomTypes);
|
||||
const elTag = unsafeStatic(elTagString);
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.submitted=${true}
|
||||
.validators=${[
|
||||
new MinLength(2, { type: 'x' }),
|
||||
new MinLength(3, { type: 'error' }),
|
||||
]}>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
el.modelValue = '1';
|
||||
await el.updateComplete;
|
||||
|
|
@ -116,12 +120,14 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
it('passes a message to the "._feedbackNode"', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.modelValue=${'cat'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData).to.deep.equal([]);
|
||||
|
|
@ -132,13 +138,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
it('has configurable feedback visibility hook', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.modelValue=${'cat'}
|
||||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
|
|
@ -153,13 +161,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
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}
|
||||
.submitted=${true}
|
||||
.modelValue=${'cat'}
|
||||
.validators=${[new AlwaysInvalid(), new MinLength(4)]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.updateComplete;
|
||||
|
|
@ -179,13 +189,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
return 'this ends up in "._feedbackNode"';
|
||||
};
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.modelValue=${'cat'}
|
||||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData).to.be.undefined;
|
||||
|
|
@ -208,13 +220,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
return 'this ends up in "._feedbackNode"';
|
||||
};
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.modelValue=${'cat'}
|
||||
.validators=${[new AlwaysInvalid()]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData).to.be.undefined;
|
||||
|
|
@ -248,8 +262,9 @@ export function runValidateMixinFeedbackPart() {
|
|||
render() {
|
||||
let name = '';
|
||||
if (this.feedbackData && this.feedbackData.length > 0) {
|
||||
const ctor = /** @type {typeof Validator} */ (this.feedbackData[0]?.validator
|
||||
?.constructor);
|
||||
const ctor = /** @type {typeof Validator} */ (
|
||||
this.feedbackData[0]?.validator?.constructor
|
||||
);
|
||||
name = ctor.validatorName;
|
||||
}
|
||||
return html`Custom for ${name}`;
|
||||
|
|
@ -257,13 +272,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
}
|
||||
const customFeedbackTagString = defineCE(ValidateElementCustomRender);
|
||||
const customFeedbackTag = unsafeStatic(customFeedbackTagString);
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new ContainsLowercaseA(), new AlwaysInvalid()]}>
|
||||
<${customFeedbackTag} slot="feedback"><${customFeedbackTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.localName).to.equal(customFeedbackTagString);
|
||||
|
|
@ -282,12 +299,14 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
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}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(3, { getMessage: () => 'custom via config' })]}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = 'a';
|
||||
|
|
@ -297,13 +316,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
});
|
||||
|
||||
it('updates the feedback component when locale changes', async () => {
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(3)]}
|
||||
.modelValue=${'1'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
await el.feedbackComplete;
|
||||
|
|
@ -323,7 +344,8 @@ export function runValidateMixinFeedbackPart() {
|
|||
}
|
||||
const elTagString = defineCE(ValidateElementCustomTypes);
|
||||
const elTag = unsafeStatic(elTagString);
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.submitted=${true}
|
||||
.validators=${[
|
||||
|
|
@ -331,7 +353,8 @@ export function runValidateMixinFeedbackPart() {
|
|||
new DefaultSuccess(null, { getMessage: () => 'This is a success message' }),
|
||||
]}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = 'a';
|
||||
|
|
@ -347,13 +370,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
|
||||
describe('Accessibility', () => {
|
||||
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}
|
||||
submitted
|
||||
.validators=${[new Required()]}
|
||||
.modelValue=${'a'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const inputNode = _inputNode;
|
||||
|
|
@ -386,13 +411,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
|
||||
const constructorMessageSpy = sinon.spy(ctorValidator, 'getMessage');
|
||||
|
||||
el = /** @type {ValidateElementCustomTypes} */ (await fixture(html`
|
||||
el = /** @type {ValidateElementCustomTypes} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.submitted=${true}
|
||||
.validators=${[constructorValidator]}
|
||||
.modelValue=${'cat'}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(constructorMessageSpy.args[0][0]).to.eql({
|
||||
|
|
@ -408,13 +435,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
const instanceMessageSpy = sinon.spy();
|
||||
const instanceValidator = new MinLength(4, { getMessage: instanceMessageSpy });
|
||||
|
||||
el = /** @type {ValidateElementCustomTypes} */ (await fixture(html`
|
||||
el = /** @type {ValidateElementCustomTypes} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.submitted=${true}
|
||||
.validators=${[instanceValidator]}
|
||||
.modelValue=${'cat'}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(instanceMessageSpy.args[0][0]).to.eql({
|
||||
|
|
@ -435,14 +464,16 @@ export function runValidateMixinFeedbackPart() {
|
|||
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
|
||||
const spy = sinon.spy(ctorValidator, 'getMessage');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[constructorValidator]}
|
||||
.modelValue=${'cat'}
|
||||
.fieldName=${new Promise(resolve => resolve('myField'))}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
expect(spy.args[0][0]).to.eql({
|
||||
|
|
@ -464,14 +495,16 @@ export function runValidateMixinFeedbackPart() {
|
|||
const ctorValidator = /** @type {typeof MinLength} */ (constructorValidator.constructor);
|
||||
const spy = sinon.spy(ctorValidator, 'getMessage');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[constructorValidator]}
|
||||
.modelValue=${'cat'}
|
||||
.fieldName=${new Promise(resolve => resolve('myField'))}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
await el.feedbackComplete;
|
||||
|
||||
|
|
@ -500,13 +533,15 @@ export function runValidateMixinFeedbackPart() {
|
|||
* The Queue system solves this by queueing the updateFeedbackComponent tasks and
|
||||
* await them one by one.
|
||||
*/
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.submitted=${true}
|
||||
.validators=${[new MinLength(3)]}
|
||||
.modelValue=${'1'}
|
||||
>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
el.modelValue = '12345';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { LitElement } from '@lion/core';
|
|||
import { LionInput } from '@lion/input';
|
||||
import '@lion/fieldset/define';
|
||||
import { FormGroupMixin, Required } from '@lion/form-core';
|
||||
import { expect, html, fixture, fixtureSync, unsafeStatic } from '@open-wc/testing';
|
||||
import { expect, fixture, fixtureSync } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
import sinon from 'sinon';
|
||||
import { ChoiceGroupMixin } from '../../src/choice-group/ChoiceGroupMixin.js';
|
||||
import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js';
|
||||
|
|
@ -41,13 +43,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
describe(`ChoiceGroupMixin: ${cfg.parentTagString}`, () => {
|
||||
if (cfg.choiceType === 'single') {
|
||||
it('has a single modelValue representing the currently checked radio value', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.modelValue).to.equal('female');
|
||||
el.formElements[0].checked = true;
|
||||
expect(el.modelValue).to.equal('male');
|
||||
|
|
@ -56,13 +60,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('has a single formattedValue representing the currently checked radio value', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formattedValue).to.equal('female');
|
||||
el.formElements[0].checked = true;
|
||||
expect(el.formattedValue).to.equal('male');
|
||||
|
|
@ -72,16 +78,20 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
}
|
||||
|
||||
it('throws if a child element without a modelValue like { value: "foo", checked: false } tries to register', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
const invalidChild = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
`)
|
||||
);
|
||||
const invalidChild = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${childTag} .modelValue=${'Lara'}></${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(() => {
|
||||
el.addFormElement(invalidChild);
|
||||
|
|
@ -91,31 +101,37 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('automatically sets the name property of child fields to its own name', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements[0].name).to.equal('gender[]');
|
||||
expect(el.formElements[1].name).to.equal('gender[]');
|
||||
|
||||
const validChild = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const validChild = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.appendChild(validChild);
|
||||
|
||||
expect(el.formElements[2].name).to.equal('gender[]');
|
||||
});
|
||||
|
||||
it('automatically updates the name property of child fields to its own name', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag}></${childTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements[0].name).to.equal('gender[]');
|
||||
expect(el.formElements[1].name).to.equal('gender[]');
|
||||
|
|
@ -129,12 +145,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('prevents updating the name property of a child if it is different from its parent', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag}></${childTag}>
|
||||
<${childTag}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements[0].name).to.equal('gender[]');
|
||||
expect(el.formElements[1].name).to.equal('gender[]');
|
||||
|
|
@ -146,12 +164,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('allows updating the name property of a child if parent tagName does not include childTagname', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTagFoo}></${childTagFoo}>
|
||||
<${childTagFoo}></${childTagFoo}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements[0].name).to.equal('gender[]');
|
||||
expect(el.formElements[1].name).to.equal('gender[]');
|
||||
|
|
@ -163,12 +183,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('allows setting the condition for syncing the name property of a child to parent', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTagBar}></${childTagBar}>
|
||||
<${childTagBar}></${childTagBar}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formElements[0].name).to.equal('gender[]');
|
||||
expect(el.formElements[1].name).to.equal('gender[]');
|
||||
|
|
@ -180,29 +202,35 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('adjusts the name of a child element if it has a different name than the group', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const invalidChild = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const invalidChild = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${childTag} name="foo" .choiceValue=${'male'}></${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.addFormElement(invalidChild);
|
||||
await invalidChild.updateComplete;
|
||||
expect(invalidChild.name).to.equal('gender[]');
|
||||
});
|
||||
|
||||
it('can set initial modelValue on creation', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]" .modelValue=${'other'}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.equal('other');
|
||||
|
|
@ -213,13 +241,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can set initial serializedValue on creation', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]" .serializedValue=${'other'}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.serializedValue).to.equal('other');
|
||||
|
|
@ -230,13 +260,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can set initial formattedValue on creation', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]" .formattedValue=${'other'}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.formattedValue).to.equal('other');
|
||||
|
|
@ -247,13 +279,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('correctly handles modelValue being set before registrationComplete', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (fixtureSync(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
fixtureSync(html`
|
||||
<${parentTag} name="gender[]" .modelValue=${null}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
el.modelValue = 'other';
|
||||
|
|
@ -267,13 +301,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('correctly handles serializedValue being set before registrationComplete', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (fixtureSync(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
fixtureSync(html`
|
||||
<${parentTag} name="gender[]" .serializedValue=${null}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
// @ts-expect-error
|
||||
|
|
@ -289,13 +325,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can handle null and undefined modelValues', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]" .modelValue=${null}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.equal('');
|
||||
|
|
@ -315,12 +353,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
it('can handle complex data via choiceValue', async () => {
|
||||
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
||||
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="data[]">
|
||||
<${childTag} .choiceValue=${{ some: 'data' }}></${childTag}>
|
||||
<${childTag} .choiceValue=${date} checked></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.equal(date);
|
||||
|
|
@ -334,12 +374,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can handle 0 and empty string as valid values', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="data[]">
|
||||
<${childTag} .choiceValue=${0} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${''}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.equal(0);
|
||||
|
|
@ -353,7 +395,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can check a choice by supplying an available modelValue', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag}
|
||||
.modelValue="${{ value: 'male', checked: false }}"
|
||||
|
|
@ -365,7 +408,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
.modelValue="${{ value: 'other', checked: false }}"
|
||||
></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.equal('female');
|
||||
|
|
@ -377,7 +421,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can check a choice by supplying an available modelValue even if this modelValue is an array or object', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag}
|
||||
.modelValue="${{ value: { v: 'male' }, checked: false }}"
|
||||
|
|
@ -389,7 +434,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
.modelValue="${{ value: { v: 'other' }, checked: false }}"
|
||||
></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.modelValue).to.eql({ v: 'female' });
|
||||
|
|
@ -407,7 +453,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
|
||||
it('expect child nodes to only fire one model-value-changed event per instance', async () => {
|
||||
let counter = 0;
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag}
|
||||
name="gender[]"
|
||||
@model-value-changed=${() => {
|
||||
|
|
@ -420,7 +467,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
counter = 0; // reset after setup which may result in different results
|
||||
|
||||
|
|
@ -454,14 +502,16 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can be required', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]" .validators=${[new Required()]}>
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag}
|
||||
.choiceValue=${{ subObject: 'satisfies required' }}
|
||||
></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates.error).to.exist;
|
||||
expect(el.validationStates.error.Required).to.exist;
|
||||
|
|
@ -478,12 +528,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('returns serialized value', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.formElements[0].checked = true;
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.serializedValue).to.deep.equal('male');
|
||||
|
|
@ -493,12 +545,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('returns serialized value on unchecked state', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.serializedValue).to.deep.equal('');
|
||||
|
|
@ -508,12 +562,14 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can be cleared', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.formElements[0].checked = true;
|
||||
el.clear();
|
||||
|
||||
|
|
@ -526,13 +582,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
|
||||
describe('multipleChoice', () => {
|
||||
it('has a single modelValue representing all currently checked values', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} multiple-choice name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.modelValue).to.eql(['female']);
|
||||
el.formElements[0].checked = true;
|
||||
|
|
@ -542,13 +600,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('has a single serializedValue representing all currently checked values', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} multiple-choice name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.serializedValue).to.eql(['female']);
|
||||
el.formElements[0].checked = true;
|
||||
|
|
@ -558,13 +618,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('has a single formattedValue representing all currently checked values', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} multiple-choice name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.formattedValue).to.eql(['female']);
|
||||
el.formElements[0].checked = true;
|
||||
|
|
@ -574,13 +636,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('can check multiple checkboxes by setting the modelValue', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} multiple-choice name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
el.modelValue = ['male', 'other'];
|
||||
expect(el.modelValue).to.eql(['male', 'other']);
|
||||
|
|
@ -589,13 +653,15 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
});
|
||||
|
||||
it('unchecks non-matching checkboxes when setting the modelValue', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<${parentTag} multiple-choice name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'} checked></${childTag}>
|
||||
<${childTag} .choiceValue=${'female'}></${childTag}>
|
||||
<${childTag} .choiceValue=${'other'} checked></${childTag}>
|
||||
</${parentTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.modelValue).to.eql(['male', 'other']);
|
||||
expect(el.formElements[0].checked).to.be.true;
|
||||
|
|
@ -610,7 +676,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
|
||||
describe('Integration with a parent form/fieldset', () => {
|
||||
it('will serialize all children with their serializedValue', async () => {
|
||||
const el = /** @type {ChoiceInputGroup} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInputGroup} */ (
|
||||
await fixture(html`
|
||||
<lion-fieldset>
|
||||
<${parentTag} name="gender[]">
|
||||
<${childTag} .choiceValue=${'male'} checked disabled></${childTag}>
|
||||
|
|
@ -618,7 +685,8 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
<${childTag} .choiceValue=${'other'}></${childTag}>
|
||||
</${parentTag}>
|
||||
</lion-fieldset>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
if (cfg.choiceType === 'single') {
|
||||
expect(el.serializedValue).to.deep.equal({ 'gender[]': ['female'] });
|
||||
|
|
@ -641,19 +709,19 @@ export function runChoiceGroupMixinSuite({ parentTagString, childTagString, choi
|
|||
</lion-fieldset>
|
||||
`);
|
||||
|
||||
const choiceGroupEl = /** @type {ChoiceInputGroup} */ (formEl.querySelector(
|
||||
'[name=choice-group]',
|
||||
));
|
||||
const choiceGroupEl = /** @type {ChoiceInputGroup} */ (
|
||||
formEl.querySelector('[name=choice-group]')
|
||||
);
|
||||
if (choiceGroupEl.multipleChoice) {
|
||||
return;
|
||||
}
|
||||
/** @typedef {{ checked: boolean }} checkedInterface */
|
||||
const option1El = /** @type {HTMLElement & checkedInterface} */ (formEl.querySelector(
|
||||
'#option1',
|
||||
));
|
||||
const option2El = /** @type {HTMLElement & checkedInterface} */ (formEl.querySelector(
|
||||
'#option2',
|
||||
));
|
||||
const option1El = /** @type {HTMLElement & checkedInterface} */ (
|
||||
formEl.querySelector('#option1')
|
||||
);
|
||||
const option2El = /** @type {HTMLElement & checkedInterface} */ (
|
||||
formEl.querySelector('#option2')
|
||||
);
|
||||
formEl.addEventListener('model-value-changed', formSpy);
|
||||
choiceGroupEl?.addEventListener('model-value-changed', choiceGroupSpy);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Required } from '@lion/form-core';
|
||||
import { LionInput } from '@lion/input';
|
||||
import { expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import { ChoiceInputMixin } from '../../src/choice-group/ChoiceInputMixin.js';
|
||||
|
|
@ -15,6 +16,7 @@ customElements.define('choice-group-input', ChoiceInput);
|
|||
|
||||
/**
|
||||
* @param {{ tagString?:string, tagType?: string}} [config]
|
||||
* @deprecated
|
||||
*/
|
||||
export function runChoiceInputMixinSuite({ tagString } = {}) {
|
||||
const cfg = {
|
||||
|
|
@ -29,9 +31,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
});
|
||||
|
||||
it('has choiceValue', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .choiceValue=${'foo'}></${tag}>`)
|
||||
);
|
||||
|
||||
expect(el.choiceValue).to.equal('foo');
|
||||
expect(el.modelValue).to.deep.equal({
|
||||
|
|
@ -43,9 +45,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
it('can handle complex data via choiceValue', async () => {
|
||||
const date = new Date(2018, 11, 24, 10, 33, 30, 0);
|
||||
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .choiceValue=${date}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .choiceValue=${date}></${tag}>`)
|
||||
);
|
||||
|
||||
expect(el.choiceValue).to.equal(date);
|
||||
expect(el.modelValue.value).to.equal(date);
|
||||
|
|
@ -53,14 +55,16 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('fires one "model-value-changed" event if choiceValue or checked state or modelValue changed', async () => {
|
||||
let counter = 0;
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
@model-value-changed=${() => {
|
||||
counter += 1;
|
||||
}}
|
||||
.choiceValue=${'foo'}
|
||||
></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(counter).to.equal(1); // undefined to set value
|
||||
|
||||
el.checked = true;
|
||||
|
|
@ -78,7 +82,8 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('fires one "user-input-changed" event after user interaction', async () => {
|
||||
let counter = 0;
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
@user-input-changed="${() => {
|
||||
counter += 1;
|
||||
|
|
@ -86,7 +91,8 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
>
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
expect(counter).to.equal(0);
|
||||
|
|
@ -100,13 +106,15 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('fires one "click" event when clicking label or input, using the right target', async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
@click="${spy}"
|
||||
>
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode, _labelNode } = getFormControlMembers(el);
|
||||
|
||||
el.click();
|
||||
|
|
@ -122,7 +130,8 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
it('adds "isTriggerByUser" flag on model-value-changed', async () => {
|
||||
let isTriggeredByUser;
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
@model-value-changed="${(/** @type {CustomEvent} */ event) => {
|
||||
isTriggeredByUser = event.detail.isTriggeredByUser;
|
||||
|
|
@ -130,7 +139,8 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
>
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
_inputNode.dispatchEvent(new CustomEvent('change', { bubbles: true }));
|
||||
|
|
@ -138,9 +148,11 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
});
|
||||
|
||||
it('can be required', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag} .choiceValue=${'foo'} .validators=${[new Required()]}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.hasFeedbackFor).to.include('error');
|
||||
expect(el.validationStates.error).to.exist;
|
||||
|
|
@ -156,9 +168,11 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
expect(el.checked).to.equal(false, 'initially unchecked');
|
||||
|
||||
const precheckedElementAttr = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const precheckedElementAttr = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag} .checked=${true}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(precheckedElementAttr.checked).to.equal(true, 'initially checked via attribute');
|
||||
});
|
||||
|
||||
|
|
@ -196,9 +210,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
});
|
||||
|
||||
it('synchronizes modelValue to checked state and vice versa', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .choiceValue=${'foo'}></${tag}>`)
|
||||
);
|
||||
expect(el.checked).to.be.false;
|
||||
expect(el.modelValue).to.deep.equal({
|
||||
checked: false,
|
||||
|
|
@ -215,9 +229,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
it('ensures optimal synchronize performance by preventing redundant computation steps', async () => {
|
||||
/* we are checking private apis here to make sure we do not have cyclical updates
|
||||
which can be quite common for these type of connected data */
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .choiceValue=${'foo'}></${tag}>`)
|
||||
);
|
||||
expect(el.checked).to.be.false;
|
||||
|
||||
// @ts-ignore [allow-private] in test
|
||||
|
|
@ -245,11 +259,13 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
/** @param {ChoiceInput} el */
|
||||
const hasAttr = el => el.hasAttribute('checked');
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
const elChecked = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const elChecked = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag} .checked=${true}>
|
||||
<input slot="input" />
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
const { _inputNode: _inputNodeChecked } = getFormControlMembers(elChecked);
|
||||
|
||||
|
|
@ -294,14 +310,16 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
describe('Format/parse/serialize loop', () => {
|
||||
it('creates a modelValue object like { checked: true, value: foo } on init', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .choiceValue=${'foo'}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .choiceValue=${'foo'}></${tag}>`)
|
||||
);
|
||||
expect(el.modelValue).deep.equal({ value: 'foo', checked: false });
|
||||
|
||||
const elChecked = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const elChecked = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag} .choiceValue=${'foo'} .checked=${true}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(elChecked.modelValue).deep.equal({ value: 'foo', checked: true });
|
||||
});
|
||||
|
||||
|
|
@ -309,9 +327,11 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
const el = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
expect(el.formattedValue).to.equal('');
|
||||
|
||||
const elementWithValue = /** @type {ChoiceInput} */ (await fixture(html`
|
||||
const elementWithValue = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`
|
||||
<${tag} .choiceValue=${'foo'}></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(elementWithValue.formattedValue).to.equal('foo');
|
||||
});
|
||||
|
||||
|
|
@ -325,9 +345,9 @@ export function runChoiceInputMixinSuite({ tagString } = {}) {
|
|||
|
||||
describe('Interaction states', () => {
|
||||
it('is considered prefilled when checked and not considered prefilled when unchecked', async () => {
|
||||
const el = /** @type {ChoiceInput} */ (await fixture(
|
||||
html`<${tag} .checked=${true}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {ChoiceInput} */ (
|
||||
await fixture(html`<${tag} .checked=${true}></${tag}>`)
|
||||
);
|
||||
expect(el.prefilled).equal(true, 'checked element not considered prefilled');
|
||||
|
||||
const elUnchecked = /** @type {ChoiceInput} */ (await fixture(html`<${tag}></${tag}>`));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { defineCE, expect, html, unsafeStatic, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { defineCE, expect, fixture } from '@open-wc/testing';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { LionInput } from '@lion/input';
|
||||
import '@lion/form-core/define';
|
||||
|
|
@ -47,12 +48,14 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
|
||||
describe('FormGroupMixin with LionField', () => {
|
||||
it('serializes undefined values as "" (nb radios/checkboxes are always serialized)', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="custom[]"></${childTag}>
|
||||
<${childTag} name="custom[]"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
fieldset.formElements['custom[]'][0].modelValue = 'custom 1';
|
||||
fieldset.formElements['custom[]'][1].modelValue = undefined;
|
||||
|
||||
|
|
@ -62,12 +65,14 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('suffixes child labels with group label, just like in <fieldset>', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} label="set">
|
||||
<${childTag} name="A" label="fieldA"></${childTag}>
|
||||
<${childTag} name="B" label="fieldB"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
|
||||
/**
|
||||
|
|
@ -88,8 +93,10 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
|
||||
// Test the cleanup on disconnected
|
||||
el.removeChild(field1);
|
||||
await field1.updateComplete;
|
||||
expect(getLabels(field1)).to.eql([field1._labelNode.id]);
|
||||
|
||||
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
|
||||
// await field1.updateComplete;
|
||||
// expect(getLabels(field1)).to.eql([field1._labelNode.id]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -110,7 +117,8 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
childAriaFixture = async (
|
||||
msgSlotType = 'feedback', // eslint-disable-line no-shadow
|
||||
) => {
|
||||
const dom = /** @type {FormGroup} */ (await fixture(html`
|
||||
const dom = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="l1_g">
|
||||
<${childTag} name="l1_fa">
|
||||
<div slot="${msgSlotType}" id="msg_l1_fa"></div>
|
||||
|
|
@ -144,7 +152,8 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
<div slot="${msgSlotType}" id="msg_l1_g"></div>
|
||||
<!-- group referred by: #msg_l1_g (local) -->
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
return dom;
|
||||
};
|
||||
|
||||
|
|
@ -163,18 +172,18 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
const msg_l2_fb = /** @type {FormChild} */ (childAriaFixture.querySelector('#msg_l2_fb'));
|
||||
|
||||
// Field elements: all inputs pointing to message elements
|
||||
const input_l1_fa = /** @type {HTMLInputElement} */ (childAriaFixture.querySelector(
|
||||
'input[name=l1_fa]',
|
||||
));
|
||||
const input_l1_fb = /** @type {HTMLInputElement} */ (childAriaFixture.querySelector(
|
||||
'input[name=l1_fb]',
|
||||
));
|
||||
const input_l2_fa = /** @type {HTMLInputElement} */ (childAriaFixture.querySelector(
|
||||
'input[name=l2_fa]',
|
||||
));
|
||||
const input_l2_fb = /** @type {HTMLInputElement} */ (childAriaFixture.querySelector(
|
||||
'input[name=l2_fb]',
|
||||
));
|
||||
const input_l1_fa = /** @type {HTMLInputElement} */ (
|
||||
childAriaFixture.querySelector('input[name=l1_fa]')
|
||||
);
|
||||
const input_l1_fb = /** @type {HTMLInputElement} */ (
|
||||
childAriaFixture.querySelector('input[name=l1_fb]')
|
||||
);
|
||||
const input_l2_fa = /** @type {HTMLInputElement} */ (
|
||||
childAriaFixture.querySelector('input[name=l2_fa]')
|
||||
);
|
||||
const input_l2_fb = /** @type {HTMLInputElement} */ (
|
||||
childAriaFixture.querySelector('input[name=l2_fb]')
|
||||
);
|
||||
|
||||
if (!cleanupPhase) {
|
||||
// 'L1' fields (inside lion-fieldset[name="l1_g"]) should point to l1(group) msg
|
||||
|
|
@ -222,18 +231,18 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
).to.equal(true, 'order of ids');
|
||||
} else {
|
||||
// cleanupPhase
|
||||
const control_l1_fa = /** @type {LionField} */ (childAriaFixture.querySelector(
|
||||
'[name=l1_fa]',
|
||||
));
|
||||
const control_l1_fb = /** @type {LionField} */ (childAriaFixture.querySelector(
|
||||
'[name=l1_fb]',
|
||||
));
|
||||
const control_l2_fa = /** @type {LionField} */ (childAriaFixture.querySelector(
|
||||
'[name=l2_fa]',
|
||||
));
|
||||
const control_l2_fb = /** @type {LionField} */ (childAriaFixture.querySelector(
|
||||
'[name=l2_fb]',
|
||||
));
|
||||
const control_l1_fa = /** @type {LionField} */ (
|
||||
childAriaFixture.querySelector('[name=l1_fa]')
|
||||
);
|
||||
const control_l1_fb = /** @type {LionField} */ (
|
||||
childAriaFixture.querySelector('[name=l1_fb]')
|
||||
);
|
||||
const control_l2_fa = /** @type {LionField} */ (
|
||||
childAriaFixture.querySelector('[name=l2_fa]')
|
||||
);
|
||||
const control_l2_fb = /** @type {LionField} */ (
|
||||
childAriaFixture.querySelector('[name=l2_fb]')
|
||||
);
|
||||
|
||||
// @ts-expect-error removeChild should always be inherited via LitElement?
|
||||
control_l1_fa._parentFormGroup.removeChild(control_l1_fa);
|
||||
|
|
@ -303,12 +312,14 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|||
await childAriaTest(await childAriaFixture('help-text'));
|
||||
});
|
||||
|
||||
it(`cleans up feedback message belonging to fieldset on disconnect`, async () => {
|
||||
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
|
||||
it.skip(`cleans up feedback message belonging to fieldset on disconnect`, async () => {
|
||||
const el = await childAriaFixture('feedback');
|
||||
await childAriaTest(el, { cleanupPhase: true });
|
||||
});
|
||||
|
||||
it(`cleans up help-text message belonging to fieldset on disconnect`, async () => {
|
||||
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
|
||||
it.skip(`cleans up help-text message belonging to fieldset on disconnect`, async () => {
|
||||
const el = await childAriaFixture('help-text');
|
||||
await childAriaTest(el, { cleanupPhase: true });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,7 @@
|
|||
import { LitElement, ifDefined } from '@lion/core';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import {
|
||||
defineCE,
|
||||
expect,
|
||||
html,
|
||||
triggerFocusFor,
|
||||
unsafeStatic,
|
||||
fixture,
|
||||
aTimeout,
|
||||
} from '@open-wc/testing';
|
||||
import { defineCE, expect, triggerFocusFor, fixture, aTimeout } from '@open-wc/testing';
|
||||
import sinon from 'sinon';
|
||||
import { IsNumber, Validator, LionField } from '@lion/form-core';
|
||||
import '@lion/form-core/define';
|
||||
|
|
@ -59,30 +52,32 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
describe('FormGroupMixin', () => {
|
||||
// TODO: Tests below belong to FormControlMixin. Preferably run suite integration test
|
||||
it(`has a fieldName based on the label`, async () => {
|
||||
const el1 = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag} label="foo">${inputSlots}</${tag}>`,
|
||||
));
|
||||
const el1 = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag} label="foo">${inputSlots}</${tag}>`)
|
||||
);
|
||||
const { _labelNode: _labelNode1 } = getFormControlMembers(el1);
|
||||
expect(el1.fieldName).to.equal(_labelNode1.textContent);
|
||||
|
||||
const el2 = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}><label slot="label">bar</label>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const el2 = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}><label slot="label">bar</label>${inputSlots}</${tag}>`)
|
||||
);
|
||||
const { _labelNode: _labelNode2 } = getFormControlMembers(el2);
|
||||
expect(el2.fieldName).to.equal(_labelNode2.textContent);
|
||||
});
|
||||
|
||||
it(`has a fieldName based on the name if no label exists`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag} name="foo">${inputSlots}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag} name="foo">${inputSlots}</${tag}>`)
|
||||
);
|
||||
expect(el.fieldName).to.equal(el.name);
|
||||
});
|
||||
|
||||
it(`can override fieldName`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} label="foo" .fieldName="${'bar'}">${inputSlots}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el.__fieldName).to.equal(el.fieldName);
|
||||
});
|
||||
|
|
@ -100,13 +95,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it(`supports in html wrapped form elements`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<div>
|
||||
<${childTag} name="foo"></${childTag}>
|
||||
</div>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formElements.length).to.equal(1);
|
||||
el.children[0].removeChild(el.formElements.foo);
|
||||
expect(el.formElements.length).to.equal(0);
|
||||
|
|
@ -206,9 +203,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
it('can dynamically add/remove elements', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`<${tag}>${inputSlots}</${tag}>`));
|
||||
const newField = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${childTag} name="lastName"></${childTag}>`,
|
||||
));
|
||||
const newField = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${childTag} name="lastName"></${childTag}>`)
|
||||
);
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
// @ts-ignore [allow-protected] in test
|
||||
|
|
@ -226,12 +223,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
// TODO: Tests below belong to FormGroupMixin. Preferably run suite integration test
|
||||
|
||||
it('can read/write all values (of every input) via this.modelValue', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="lastName"></${childTag}>
|
||||
<${tag} name="newfieldset">${inputSlots}</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const newFieldset = /** @type {FormGroup} */ (el.querySelector(tagString));
|
||||
el.formElements.lastName.modelValue = 'Bar';
|
||||
newFieldset.formElements['hobbies[]'][0].modelValue = { checked: true, value: 'chess' };
|
||||
|
|
@ -301,7 +300,8 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not list disabled values in this.modelValue', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="a" disabled .modelValue="${'x'}"></${childTag}>
|
||||
<${childTag} name="b" .modelValue="${'x'}"></${childTag}>
|
||||
|
|
@ -313,7 +313,8 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
<${childTag} name="e" .modelValue="${'x'}"></${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.modelValue).to.deep.equal({
|
||||
b: 'x',
|
||||
newFieldset: {
|
||||
|
|
@ -323,12 +324,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not throw if setter data of this.modelValue can not be handled', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="firstName" .modelValue=${'foo'}></${childTag}>
|
||||
<${childTag} name="lastName" .modelValue=${'bar'}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const initState = {
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
|
|
@ -343,9 +346,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('disables/enables all its formElements if it becomes disabled/enabled', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag} disabled>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag} disabled>${inputSlots}</${tag}>`)
|
||||
);
|
||||
expect(el.formElements.color.disabled).to.be.true;
|
||||
expect(el.formElements['hobbies[]'][0].disabled).to.be.true;
|
||||
expect(el.formElements['hobbies[]'][1].disabled).to.be.true;
|
||||
|
|
@ -358,11 +361,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not propagate/override initial disabled value on nested form elements', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${tag} name="sub" disabled>${inputSlots}</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.disabled).to.equal(false);
|
||||
expect(el.formElements.sub.disabled).to.be.true;
|
||||
|
|
@ -372,11 +377,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('can set initial modelValue on creation', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${{ lastName: 'Bar' }}>
|
||||
<${childTag} name="lastName"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.modelValue).to.eql({
|
||||
lastName: 'Bar',
|
||||
|
|
@ -384,11 +391,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('can set initial serializedValue on creation', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue=${{ lastName: 'Bar' }}>
|
||||
<${childTag} name="lastName"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.modelValue).to.eql({ lastName: 'Bar' });
|
||||
});
|
||||
|
|
@ -409,13 +418,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="color" .validators=${[
|
||||
new IsCat(),
|
||||
]} .modelValue=${'blue'}></${childTag}>
|
||||
new IsCat(),
|
||||
]} .modelValue=${'blue'}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.formElements.color.validationStates.error.IsCat).to.be.true;
|
||||
});
|
||||
|
||||
|
|
@ -442,13 +453,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="color" .validators=${[
|
||||
new IsCat(),
|
||||
]} .modelValue=${'blue'}></${childTag}>
|
||||
new IsCat(),
|
||||
]} .modelValue=${'blue'}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.validationStates.error.FormElementsHaveNoError).to.be.true;
|
||||
expect(el.formElements.color.validationStates.error.IsCat).to.be.true;
|
||||
|
|
@ -470,14 +483,18 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
return hasError;
|
||||
}
|
||||
}
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new HasEvenNumberOfChildren()]}>
|
||||
<${childTag} id="c1" name="c1"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const child2 = /** @type {FormGroup} */ (await fixture(html`
|
||||
`)
|
||||
);
|
||||
const child2 = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${childTag} name="c2"></${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.validationStates.error.HasEvenNumberOfChildren).to.be.true;
|
||||
|
||||
el.appendChild(child2);
|
||||
|
|
@ -495,18 +512,18 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
describe('Interaction states', () => {
|
||||
it('has false states (dirty, touched, prefilled) on init', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
expect(fieldset.dirty).to.equal(false, 'dirty');
|
||||
expect(fieldset.touched).to.equal(false, 'touched');
|
||||
expect(fieldset.prefilled).to.equal(false, 'prefilled');
|
||||
});
|
||||
|
||||
it('sets dirty when value changed', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
fieldset.formElements['hobbies[]'][0].modelValue = { checked: true, value: 'football' };
|
||||
expect(fieldset.dirty).to.be.true;
|
||||
});
|
||||
|
|
@ -540,32 +557,38 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('becomes prefilled if all form elements are prefilled', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="input1" .modelValue="${'prefilled'}"></${childTag}>
|
||||
<${childTag} name="input2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.prefilled).to.be.false;
|
||||
|
||||
const el2 = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el2 = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="input1" .modelValue="${'prefilled'}"></${childTag}>
|
||||
<${childTag} name="input2" .modelValue="${'prefilled'}"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el2.prefilled).to.be.true;
|
||||
});
|
||||
|
||||
it(`becomes "touched" once the last element of a group becomes blurred by keyboard
|
||||
interaction (e.g. tabbing through the checkbox-group)`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">My group</label>
|
||||
<${childTag} name="myGroup[]" label="Option 1" value="1"></${childTag}>
|
||||
<${childTag} name="myGroup[]" label="Option 2" value="2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const button = /** @type {HTMLButtonElement} */ (await fixture(`<button>Blur</button>`));
|
||||
|
||||
|
|
@ -582,22 +605,26 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
it(`becomes "touched" once the group as a whole becomes blurred via mouse interaction after
|
||||
keyboard interaction (e.g. focus is moved inside the group and user clicks somewhere outside
|
||||
the group)`, async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="input1"></${childTag}>
|
||||
<${childTag} name="input2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
const el2 = /** @type {FormGroup} */ (await fixture(html`
|
||||
`)
|
||||
);
|
||||
const el2 = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="input1"></${childTag}>
|
||||
<${childTag} name="input2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const outside = /** @type {HTMLButtonElement} */ (await fixture(
|
||||
html`<button>outside</button>`,
|
||||
));
|
||||
const outside = /** @type {HTMLButtonElement} */ (
|
||||
await fixture(html`<button>outside</button>`)
|
||||
);
|
||||
|
||||
outside.click();
|
||||
expect(el.touched, 'unfocused fieldset should stay untouched').to.be.false;
|
||||
|
|
@ -627,14 +654,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const outSideButton = /** @type {FormGroup} */ (await fixture(
|
||||
html`<button>outside</button>`,
|
||||
));
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const outSideButton = /** @type {FormGroup} */ (
|
||||
await fixture(html`<button>outside</button>`)
|
||||
);
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new Input1IsTen()]}>
|
||||
<${childTag} name="input1" .validators=${[new IsNumber()]}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const input1 = /** @type {FormChild} */ (el.querySelector('[name=input1]'));
|
||||
input1.modelValue = 2;
|
||||
input1.focus();
|
||||
|
|
@ -657,15 +686,17 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
return hasError;
|
||||
}
|
||||
}
|
||||
const outSideButton = /** @type {FormGroup} */ (await fixture(
|
||||
html`<button>outside</button>`,
|
||||
));
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const outSideButton = /** @type {FormGroup} */ (
|
||||
await fixture(html`<button>outside</button>`)
|
||||
);
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new Input1IsTen()]}>
|
||||
<${childTag} name="input1" .validators=${[new IsNumber()]}></${childTag}>
|
||||
<${childTag} name="input2" .validators=${[new IsNumber()]}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const inputs = /** @type {FormChild[]} */ (Array.from(el.querySelectorAll(childTagString)));
|
||||
inputs[1].modelValue = 2; // make it dirty
|
||||
inputs[1].focus();
|
||||
|
|
@ -677,20 +708,24 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not become dirty when elements are prefilled', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .serializedValue="${{ input1: 'x', input2: 'y' }}">
|
||||
<${childTag} name="input1" ></${childTag}>
|
||||
<${childTag} name="input2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.dirty).to.be.false;
|
||||
|
||||
const el2 = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el2 = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue="${{ input1: 'x', input2: 'y' }}">
|
||||
<${childTag} name="input1" ></${childTag}>
|
||||
<${childTag} name="input2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el2.dirty).to.be.false;
|
||||
});
|
||||
});
|
||||
|
|
@ -698,9 +733,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
// TODO: this should be tested in FormGroupMixin
|
||||
describe('serializedValue', () => {
|
||||
it('use form elements serializedValue', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
fieldset.formElements['hobbies[]'][0].serializer = /** @param {?} v */ v =>
|
||||
`${v.value}-serialized`;
|
||||
fieldset.formElements['hobbies[]'][0].modelValue = { checked: false, value: 'Bar' };
|
||||
|
|
@ -720,9 +755,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('treats names with ending [] as arrays', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
fieldset.formElements['hobbies[]'][0].modelValue = { checked: false, value: 'chess' };
|
||||
fieldset.formElements['hobbies[]'][1].modelValue = { checked: false, value: 'rugby' };
|
||||
fieldset.formElements['gender[]'][0].modelValue = { checked: false, value: 'male' };
|
||||
|
|
@ -742,21 +777,25 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('0 is a valid value to be serialized', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="price"></${childTag}>
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
fieldset.formElements.price.modelValue = 0;
|
||||
expect(fieldset.serializedValue).to.deep.equal({ price: 0 });
|
||||
});
|
||||
|
||||
it('allows for nested fieldsets', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="userData">
|
||||
<${childTag} name="comment"></${childTag}>
|
||||
<${tag} name="newfieldset">${inputSlots}</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const newFieldset = /** @type {FormGroup} */ (fieldset.querySelector(tagString));
|
||||
newFieldset.formElements['hobbies[]'][0].modelValue = { checked: false, value: 'chess' };
|
||||
newFieldset.formElements['hobbies[]'][1].modelValue = { checked: false, value: 'rugby' };
|
||||
|
|
@ -785,12 +824,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not serialize disabled values', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="custom[]"></${childTag}>
|
||||
<${childTag} name="custom[]"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
fieldset.formElements['custom[]'][0].modelValue = 'custom 1';
|
||||
fieldset.formElements['custom[]'][1].disabled = true;
|
||||
|
||||
|
|
@ -800,12 +841,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('will exclude form elements within a disabled fieldset', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="userData">
|
||||
<${childTag} name="comment"></${childTag}>
|
||||
<${tag} name="newfieldset">${inputSlots}</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const newFieldset = /** @type {FormGroup} */ (fieldset.querySelector(tagString));
|
||||
fieldset.formElements.comment.modelValue = 'Foo';
|
||||
|
|
@ -848,11 +891,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('updates the formElements keys when a name attribute changes', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(html`
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="foo" .modelValue=${'qux'}></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(fieldset.serializedValue.foo).to.equal('qux');
|
||||
fieldset.formElements[0].name = 'bar';
|
||||
await fieldset.updateComplete;
|
||||
|
|
@ -863,11 +908,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
describe('Reset', () => {
|
||||
it('restores default values if changes were made', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} id="firstName" name="firstName" .modelValue="${'Foo'}"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await /** @type {FormChild} */ (el.querySelector(childTagString)).updateComplete;
|
||||
|
||||
const input = /** @type {FormChild} */ (el.querySelector('#firstName'));
|
||||
|
|
@ -882,11 +929,13 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('restores default values of arrays if changes were made', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} id="firstName" name="firstName[]" .modelValue="${'Foo'}"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await /** @type {FormChild} */ (el.querySelector(childTagString)).updateComplete;
|
||||
|
||||
const input = /** @type {FormChild} */ (el.querySelector('#firstName'));
|
||||
|
|
@ -901,13 +950,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('restores default values of a nested fieldset if changes were made', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${tag} id="name" name="name[]">
|
||||
<${childTag} id="firstName" name="firstName" .modelValue="${'Foo'}"></${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await Promise.all([
|
||||
/** @type {FormChild} */ (el.querySelector(tagString)).updateComplete,
|
||||
/** @type {FormChild} */ (el.querySelector(childTagString)).updateComplete,
|
||||
|
|
@ -928,9 +979,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('clears interaction state', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag} touched dirty>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag} touched dirty>${inputSlots}</${tag}>`)
|
||||
);
|
||||
// Safety check initially
|
||||
// @ts-ignore [allow-protected] in test
|
||||
el._setValueForAllFormElements('prefilled', true);
|
||||
|
|
@ -957,9 +1008,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('clears submitted state', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
fieldset.submitted = true;
|
||||
fieldset.resetGroup();
|
||||
expect(fieldset.submitted).to.equal(false);
|
||||
|
|
@ -999,12 +1050,14 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[new ColorContainsA()]}>
|
||||
<${childTag} name="color" .validators=${[new IsCat()]}></${childTag}>
|
||||
<${childTag} name="color2"></${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
expect(el.validationStates.error.ColorContainsA).to.be.true;
|
||||
expect(el.formElements.color.hasFeedbackFor).to.deep.equal([]);
|
||||
|
|
@ -1024,14 +1077,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('has access to `_initialModelValue` based on initial children states', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
<${childTag} name="child[]" .modelValue="${'bar1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
el.modelValue['child[]'] = ['foo2', 'bar2'];
|
||||
// @ts-ignore [allow-protected] in test
|
||||
|
|
@ -1039,17 +1094,21 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('does not wrongly recompute `_initialModelValue` after dynamic changes of children', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.modelValue['child[]'] = ['foo2'];
|
||||
const childEl = /** @type {FormGroup} */ (await fixture(html`
|
||||
const childEl = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${childTag} name="child[]" .modelValue="${'bar1'}">
|
||||
</${childTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.appendChild(childEl);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el._initialModelValue['child[]']).to.eql(['foo1', 'bar1']);
|
||||
|
|
@ -1057,14 +1116,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
describe('resetGroup method', () => {
|
||||
it('calls resetGroup on children fieldsets', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="parentFieldset">
|
||||
<${tag} name="childFieldset">
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const childFieldsetEl = el.querySelector(tagString);
|
||||
// @ts-expect-error
|
||||
const resetGroupSpy = sinon.spy(childFieldsetEl, 'resetGroup');
|
||||
|
|
@ -1073,14 +1134,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('calls reset on children fields', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="parentFieldset">
|
||||
<${tag} name="childFieldset">
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const childFieldsetEl = /** @type {FormChild} */ (el.querySelector(childTagString));
|
||||
const resetSpy = sinon.spy(childFieldsetEl, 'reset');
|
||||
el.resetGroup();
|
||||
|
|
@ -1090,14 +1153,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
describe('clearGroup method', () => {
|
||||
it('calls clearGroup on children fieldset', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="parentFieldset">
|
||||
<${tag} name="childFieldset">
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const childFieldsetEl = el.querySelector(tagString);
|
||||
// @ts-expect-error
|
||||
const clearGroupSpy = sinon.spy(childFieldsetEl, 'clearGroup');
|
||||
|
|
@ -1106,14 +1171,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('calls clear on children fields', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="parentFieldset">
|
||||
<${tag} name="childFieldset">
|
||||
<${childTag} name="child[]" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const childFieldsetEl = /** @type {FormChild} */ (el.querySelector(childTagString));
|
||||
const clearSpy = sinon.spy(childFieldsetEl, 'clear');
|
||||
el.clearGroup();
|
||||
|
|
@ -1121,14 +1188,16 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('should clear the value of fields', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag} name="parentFieldset">
|
||||
<${tag} name="childFieldset">
|
||||
<${childTag} name="child" .modelValue="${'foo1'}">
|
||||
</${childTag}>
|
||||
</${tag}>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
el.clearGroup();
|
||||
expect(
|
||||
/** @type {FormChild} */ (el.querySelector('[name="child"]')).modelValue,
|
||||
|
|
@ -1139,9 +1208,9 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
describe('Accessibility', () => {
|
||||
it('has role="group" set', async () => {
|
||||
const fieldset = /** @type {FormGroup} */ (await fixture(
|
||||
html`<${tag}>${inputSlots}</${tag}>`,
|
||||
));
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
await fixture(html`<${tag}>${inputSlots}</${tag}>`)
|
||||
);
|
||||
fieldset.formElements['hobbies[]'][0].modelValue = { checked: false, value: 'chess' };
|
||||
fieldset.formElements['hobbies[]'][1].modelValue = { checked: false, value: 'rugby' };
|
||||
fieldset.formElements['gender[]'][0].modelValue = { checked: false, value: 'male' };
|
||||
|
|
@ -1152,15 +1221,17 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it('has an aria-labelledby from element with slot="label"', async () => {
|
||||
const el = /** @type {FormGroup} */ (await fixture(html`
|
||||
const el = /** @type {FormGroup} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">My Label</label>
|
||||
${inputSlots}
|
||||
</${tag}>
|
||||
`));
|
||||
const label = /** @type {HTMLElement} */ (Array.from(el.children).find(
|
||||
child => child.slot === 'label',
|
||||
));
|
||||
`)
|
||||
);
|
||||
const label = /** @type {HTMLElement} */ (
|
||||
Array.from(el.children).find(child => child.slot === 'label')
|
||||
);
|
||||
expect(el.hasAttribute('aria-labelledby')).to.equal(true);
|
||||
expect(el.getAttribute('aria-labelledby')).contains(label.id);
|
||||
});
|
||||
|
|
@ -1204,13 +1275,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
it(`when rendering children right from the start, sets their values correctly
|
||||
based on prefilled model/seriazedValue`, async () => {
|
||||
const el = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag}
|
||||
.fields="${['firstName', 'lastName']}"
|
||||
.modelValue="${{ firstName: 'foo', lastName: 'bar' }}"
|
||||
>
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1218,13 +1291,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(fieldset.formElements[0].modelValue).to.equal('foo');
|
||||
expect(fieldset.formElements[1].modelValue).to.equal('bar');
|
||||
|
||||
const el2 = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el2 = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag}
|
||||
.fields="${['firstName', 'lastName']}"
|
||||
.serializedValue="${{ firstName: 'foo', lastName: 'bar' }}"
|
||||
>
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el2.updateComplete;
|
||||
const fieldset2 = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el2.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1235,10 +1310,12 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
it(`when rendering children delayed, sets their values
|
||||
correctly based on prefilled model/seriazedValue`, async () => {
|
||||
const el = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .modelValue="${{ firstName: 'foo', lastName: 'bar' }}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1248,10 +1325,12 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(fieldset.formElements[0].modelValue).to.equal('foo');
|
||||
expect(fieldset.formElements[1].modelValue).to.equal('bar');
|
||||
|
||||
const el2 = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el2 = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .serializedValue="${{ firstName: 'foo', lastName: 'bar' }}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el2.updateComplete;
|
||||
const fieldset2 = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el2.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1264,13 +1343,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
|
||||
it(`when rendering children partly delayed, sets their values correctly based on
|
||||
prefilled model/seriazedValue`, async () => {
|
||||
const el = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .fields="${['firstName']}" .modelValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1280,13 +1361,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(fieldset.formElements[0].modelValue).to.equal('foo');
|
||||
expect(fieldset.formElements[1].modelValue).to.equal('bar');
|
||||
|
||||
const el2 = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el2 = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .fields="${['firstName']}" .serializedValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el2.updateComplete;
|
||||
const fieldset2 = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el2.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1305,13 +1388,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(elm.prefilled).to.be.true;
|
||||
}
|
||||
|
||||
const el = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .fields="${['firstName']}" .modelValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1324,13 +1409,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expectInteractionStatesToBeCorrectFor(fieldset.formElements[1]);
|
||||
expectInteractionStatesToBeCorrectFor(fieldset);
|
||||
|
||||
const el2 = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el2 = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .fields="${['firstName']}" .serializedValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el2.updateComplete;
|
||||
const fieldset2 = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el2.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1345,13 +1432,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
});
|
||||
|
||||
it(`prefilled children values take precedence over parent values`, async () => {
|
||||
const el = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .modelValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const fieldset = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(tagString)
|
||||
|
|
@ -1364,13 +1453,15 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|||
expect(fieldset.formElements[0].modelValue).to.equal('wins');
|
||||
expect(fieldset.formElements[1].modelValue).to.equal('winsAsWell');
|
||||
|
||||
const el2 = /** @type {DynamicCWrapper} */ (await fixture(html`
|
||||
const el2 = /** @type {DynamicCWrapper} */ (
|
||||
await fixture(html`
|
||||
<${dynamicChildrenTag} .serializedValue="${{
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
firstName: 'foo',
|
||||
lastName: 'bar',
|
||||
}}">
|
||||
</${dynamicChildrenTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
await el2.updateComplete;
|
||||
const fieldset2 = /** @type {FormGroup} */ (
|
||||
/** @type {ShadowRoot} */ (el2.shadowRoot).querySelector(tagString)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { defineCE, expect, fixture, html, oneEvent, unsafeStatic } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture, oneEvent } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import { FocusMixin } from '../src/FocusMixin.js';
|
||||
|
||||
const windowWithOptionalPolyfill = /** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
|
||||
const windowWithOptionalPolyfill =
|
||||
/** @type {Window & typeof globalThis & {applyFocusVisiblePolyfill?: function}} */ (window);
|
||||
|
||||
/**
|
||||
* Checks two things:
|
||||
|
|
@ -74,9 +76,11 @@ describe('FocusMixin', () => {
|
|||
const tag = unsafeStatic(tagString);
|
||||
|
||||
it('focuses/blurs the underlaying native element on .focus()/.blur()', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
|
|
@ -87,9 +91,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('has an attribute focused when focused', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
el.focus();
|
||||
await el.updateComplete;
|
||||
|
|
@ -101,9 +107,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('becomes focused/blurred if the native element gets focused/blurred', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
|
|
@ -115,9 +123,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('dispatches [focus, blur] events', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
setTimeout(() => el.focus());
|
||||
const focusEv = await oneEvent(el, 'focus');
|
||||
expect(focusEv).to.be.instanceOf(Event);
|
||||
|
|
@ -137,9 +147,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('dispatches [focusin, focusout] events with { bubbles: true, composed: true }', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
setTimeout(() => el.focus());
|
||||
const focusinEv = await oneEvent(el, 'focusin');
|
||||
expect(focusinEv).to.be.instanceOf(Event);
|
||||
|
|
@ -160,9 +172,11 @@ describe('FocusMixin', () => {
|
|||
|
||||
describe('Having :focus-visible within', () => {
|
||||
it('sets focusedVisible to true when focusable element matches :focus-visible', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
|
|
@ -204,9 +218,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('has an attribute focused-visible when focusedVisible is true', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
||||
|
|
@ -251,9 +267,11 @@ describe('FocusMixin', () => {
|
|||
});
|
||||
|
||||
it('sets focusedVisible to true when focusable element if :focus-visible polyfill is loaded', async () => {
|
||||
const el = /** @type {Focusable} */ (await fixture(html`
|
||||
const el = /** @type {Focusable} */ (
|
||||
await fixture(html`
|
||||
<${tag}><input slot="input"></${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
// @ts-ignore [allow-protected] in test
|
||||
const { _focusableNode } = el;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, html, defineCE, unsafeStatic, fixture } from '@open-wc/testing';
|
||||
import { expect, defineCE, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
|
|
@ -30,108 +31,130 @@ describe('FormControlMixin', () => {
|
|||
|
||||
describe('Label and helpText api', () => {
|
||||
it('has a label', async () => {
|
||||
const elAttr = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
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`
|
||||
const elProp = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.label=${'Email address'}
|
||||
>${inputSlot}
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(elProp.label).to.equal('Email address', 'as a property');
|
||||
|
||||
const elElem = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const elElem = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">Email address</label>
|
||||
${inputSlot}
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(elElem.label).to.equal('Email address', 'as an element');
|
||||
});
|
||||
|
||||
it('has a label that supports inner html', async () => {
|
||||
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const el = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">Email <span>address</span></label>
|
||||
${inputSlot}
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(el.label).to.equal('Email address');
|
||||
});
|
||||
|
||||
it('only takes label of direct child', async () => {
|
||||
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const el = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${tag} label="Email address">
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(el.label).to.equal('');
|
||||
});
|
||||
|
||||
it('can have a help-text', async () => {
|
||||
const elAttr = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
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`
|
||||
const elProp = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.helpText=${'We will not send you any spam'}
|
||||
>${inputSlot}
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(elProp.helpText).to.equal('We will not send you any spam', 'as a property');
|
||||
|
||||
const elElem = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const elElem = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<div slot="help-text">We will not send you any spam</div>
|
||||
${inputSlot}
|
||||
</${tag}>`));
|
||||
</${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`
|
||||
const el = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<div slot="help-text">We will not send you any <span>spam</span></div>
|
||||
${inputSlot}
|
||||
</${tag}>`));
|
||||
</${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`
|
||||
const el = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<${tag} help-text="We will not send you any spam">
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(el.helpText).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Accessibility', () => {
|
||||
it('does not duplicate aria-describedby and aria-labelledby ids on reconnect', async () => {
|
||||
const wrapper = /** @type {HTMLElement} */ (await fixture(html`
|
||||
const wrapper = /** @type {HTMLElement} */ (
|
||||
await fixture(html`
|
||||
<div id="wrapper">
|
||||
<${tag} help-text="This element will be disconnected/reconnected">${inputSlot}</${tag}>
|
||||
</div>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
const labelIdsBefore = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsBefore = /** @type {string} */ (_inputNode.getAttribute(
|
||||
'aria-describedby',
|
||||
));
|
||||
const descriptionIdsBefore = /** @type {string} */ (
|
||||
_inputNode.getAttribute('aria-describedby')
|
||||
);
|
||||
// Reconnect
|
||||
wrapper.removeChild(el);
|
||||
wrapper.appendChild(el);
|
||||
const labelIdsAfter = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
const descriptionIdsAfter = /** @type {string} */ (_inputNode.getAttribute(
|
||||
'aria-describedby',
|
||||
));
|
||||
const descriptionIdsAfter = /** @type {string} */ (
|
||||
_inputNode.getAttribute('aria-describedby')
|
||||
);
|
||||
|
||||
expect(labelIdsBefore).to.equal(labelIdsAfter);
|
||||
expect(descriptionIdsBefore).to.equal(descriptionIdsAfter);
|
||||
|
|
@ -139,11 +162,13 @@ describe('FormControlMixin', () => {
|
|||
|
||||
it('clicking the label should call `_onLabelClick`', async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const el = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${tag} ._onLabelClick="${spy}">
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _labelNode } = getFormControlMembers(el);
|
||||
|
||||
expect(spy).to.not.have.been.called;
|
||||
|
|
@ -232,7 +257,8 @@ describe('FormControlMixin', () => {
|
|||
describe('Adding extra labels and descriptions', () => {
|
||||
it(`supports centrally orchestrated labels/descriptions via addToAriaLabelledBy() /
|
||||
removeFromAriaLabelledBy() / addToAriaDescribedBy() / removeFromAriaDescribedBy()`, async () => {
|
||||
const wrapper = /** @type {HTMLElement} */ (await fixture(html`
|
||||
const wrapper = /** @type {HTMLElement} */ (
|
||||
await fixture(html`
|
||||
<div id="wrapper">
|
||||
<${tag}>
|
||||
${inputSlot}
|
||||
|
|
@ -241,7 +267,8 @@ describe('FormControlMixin', () => {
|
|||
</${tag}>
|
||||
<div id="additionalLabel"> This also needs to be read whenever the input has focus</div>
|
||||
<div id="additionalDescription"> Same for this </div>
|
||||
</div>`));
|
||||
</div>`)
|
||||
);
|
||||
const el = /** @type {FormControlMixinClass} */ (wrapper.querySelector(tagString));
|
||||
const { _inputNode } = getFormControlMembers(el);
|
||||
|
||||
|
|
@ -257,9 +284,9 @@ describe('FormControlMixin', () => {
|
|||
expect(/** @type {string} */ (_inputNode.getAttribute('aria-labelledby'))).to.contain(
|
||||
`label-${inputId}`,
|
||||
);
|
||||
const additionalLabel = /** @type {HTMLElement} */ (wrapper.querySelector(
|
||||
'#additionalLabel',
|
||||
));
|
||||
const additionalLabel = /** @type {HTMLElement} */ (
|
||||
wrapper.querySelector('#additionalLabel')
|
||||
);
|
||||
el.addToAriaLabelledBy(additionalLabel);
|
||||
await el.updateComplete;
|
||||
let labelledbyAttr = /** @type {string} */ (_inputNode.getAttribute('aria-labelledby'));
|
||||
|
|
@ -392,13 +419,15 @@ describe('FormControlMixin', () => {
|
|||
it('redispatches one event from host', async () => {
|
||||
const formSpy = sinon.spy();
|
||||
const fieldsetSpy = sinon.spy();
|
||||
const formEl = /** @type {FormControlMixinClass} */ (await fixture(html`
|
||||
const formEl = /** @type {FormControlMixinClass} */ (
|
||||
await fixture(html`
|
||||
<${groupTag} name="form" ._repropagationRole=${'form-group'} @model-value-changed=${formSpy}>
|
||||
<${groupTag} name="fieldset" ._repropagationRole=${'form-group'} @model-value-changed=${fieldsetSpy}>
|
||||
<${tag} name="field"></${tag}>
|
||||
</${groupTag}>
|
||||
</${groupTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const fieldsetEl = formEl.querySelector('[name=fieldset]');
|
||||
|
||||
expect(fieldsetSpy.callCount).to.equal(1);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { html } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { runRegistrationSuite } from '../test-suites/FormRegistrationMixins.suite.js';
|
||||
|
||||
runRegistrationSuite({
|
||||
|
|
|
|||
|
|
@ -2,14 +2,8 @@ import { unsafeHTML } from '@lion/core';
|
|||
import { localize } from '@lion/localize';
|
||||
import { localizeTearDown } from '@lion/localize/test-helpers';
|
||||
import { Required, Validator } from '@lion/form-core';
|
||||
import {
|
||||
expect,
|
||||
fixture,
|
||||
html,
|
||||
triggerBlurFor,
|
||||
triggerFocusFor,
|
||||
unsafeStatic,
|
||||
} from '@open-wc/testing';
|
||||
import { expect, fixture, triggerBlurFor, triggerFocusFor } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/form-core/define-field';
|
||||
|
|
@ -60,31 +54,31 @@ describe('<lion-field>', () => {
|
|||
});
|
||||
|
||||
it(`has a fieldName based on the label`, async () => {
|
||||
const el1 = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} label="foo">${inputSlot}</${tag}>`,
|
||||
));
|
||||
const el1 = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag} label="foo">${inputSlot}</${tag}>`)
|
||||
);
|
||||
const { _labelNode: _labelNode1 } = getFormControlMembers(el1);
|
||||
|
||||
expect(el1.fieldName).to.equal(_labelNode1.textContent);
|
||||
|
||||
const el2 = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag}><label slot="label">bar</label>${inputSlot}</${tag}>`,
|
||||
));
|
||||
const el2 = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag}><label slot="label">bar</label>${inputSlot}</${tag}>`)
|
||||
);
|
||||
const { _labelNode: _labelNode2 } = getFormControlMembers(el2);
|
||||
expect(el2.fieldName).to.equal(_labelNode2.textContent);
|
||||
});
|
||||
|
||||
it(`has a fieldName based on the name if no label exists`, async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} name="foo">${inputSlot}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag} name="foo">${inputSlot}</${tag}>`)
|
||||
);
|
||||
expect(el.fieldName).to.equal(el.name);
|
||||
});
|
||||
|
||||
it(`can override fieldName`, async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag} label="foo" .fieldName="${'bar'}">${inputSlot}</${tag}>`)
|
||||
);
|
||||
// @ts-ignore [allow-protected] in test
|
||||
expect(el.__fieldName).to.equal(el.fieldName);
|
||||
});
|
||||
|
|
@ -134,9 +128,9 @@ describe('<lion-field>', () => {
|
|||
});
|
||||
|
||||
it('can be cleared which erases value, validation and interaction states', async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(
|
||||
html`<${tag} value="Some value from attribute">${inputSlot}</${tag}>`,
|
||||
));
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag} value="Some value from attribute">${inputSlot}</${tag}>`)
|
||||
);
|
||||
el.clear();
|
||||
expect(el.modelValue).to.equal('');
|
||||
el.modelValue = 'Some value from property';
|
||||
|
|
@ -146,10 +140,12 @@ describe('<lion-field>', () => {
|
|||
});
|
||||
|
||||
it('can be reset which restores original modelValue', async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag} .modelValue="${'foo'}">
|
||||
${inputSlot}
|
||||
</${tag}>`));
|
||||
</${tag}>`)
|
||||
);
|
||||
expect(el._initialModelValue).to.equal('foo');
|
||||
el.modelValue = 'bar';
|
||||
el.reset();
|
||||
|
|
@ -171,13 +167,15 @@ describe('<lion-field>', () => {
|
|||
<div slot="feedback" id="feedback-[id]">[feedback] </span>
|
||||
</lion-field>
|
||||
~~~`, async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag}>
|
||||
<label slot="label">My Name</label>
|
||||
${inputSlot}
|
||||
<span slot="help-text">Enter your Name</span>
|
||||
<span slot="feedback">No name entered</span>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const nativeInput = getSlot(el, 'input');
|
||||
// @ts-ignore allow protected accessors in tests
|
||||
const inputId = el._inputId;
|
||||
|
|
@ -188,14 +186,16 @@ describe('<lion-field>', () => {
|
|||
|
||||
it(`allows additional slots (prefix, suffix, before, after) to be included in labelledby
|
||||
(via attribute data-label) and in describedby (via attribute data-description)`, async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`<${tag}>
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`<${tag}>
|
||||
${inputSlot}
|
||||
<span slot="before" data-label>[before]</span>
|
||||
<span slot="after" data-label>[after]</span>
|
||||
<span slot="prefix" data-description>[prefix]</span>
|
||||
<span slot="suffix" data-description>[suffix]</span>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const nativeInput = getSlot(el, 'input');
|
||||
// @ts-ignore allow protected accessors in tests
|
||||
|
|
@ -234,14 +234,16 @@ describe('<lion-field>', () => {
|
|||
return result;
|
||||
}
|
||||
};
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new HasX()]}
|
||||
.modelValue=${'a@b.nl'}
|
||||
>
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
/**
|
||||
* @param {import("../index.js").LionField} _sceneEl
|
||||
|
|
@ -303,7 +305,8 @@ describe('<lion-field>', () => {
|
|||
return result;
|
||||
}
|
||||
};
|
||||
const disabledEl = /** @type {LionField} */ (await fixture(html`
|
||||
const disabledEl = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
disabled
|
||||
.validators=${[new HasX()]}
|
||||
|
|
@ -311,15 +314,18 @@ describe('<lion-field>', () => {
|
|||
>
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
`));
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
`)
|
||||
);
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new HasX()]}
|
||||
.modelValue=${'a@b.nl'}
|
||||
>
|
||||
${inputSlot}
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
expect(el.validationStates.error.HasX).to.exist;
|
||||
|
|
@ -329,11 +335,13 @@ describe('<lion-field>', () => {
|
|||
});
|
||||
|
||||
it('can be required', async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.validators=${[new Required()]}
|
||||
>${inputSlot}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
expect(el.hasFeedbackFor).to.deep.equal(['error']);
|
||||
expect(el.validationStates.error.Required).to.exist;
|
||||
el.modelValue = 'cat';
|
||||
|
|
@ -356,13 +364,15 @@ describe('<lion-field>', () => {
|
|||
return hasError;
|
||||
}
|
||||
};
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}
|
||||
.modelValue=${'init-string'}
|
||||
.formatter=${formatterSpy}
|
||||
.validators=${[new Bar()]}
|
||||
>${inputSlot}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(formatterSpy.callCount).to.equal(0);
|
||||
expect(el.formattedValue).to.equal('init-string');
|
||||
|
|
@ -379,7 +389,8 @@ describe('<lion-field>', () => {
|
|||
|
||||
describe(`Content projection`, () => {
|
||||
it('renders correctly all slot elements in light DOM', async () => {
|
||||
const el = /** @type {LionField} */ (await fixture(html`
|
||||
const el = /** @type {LionField} */ (
|
||||
await fixture(html`
|
||||
<${tag}>
|
||||
<label slot="label">[label]</label>
|
||||
${inputSlot}
|
||||
|
|
@ -390,7 +401,8 @@ describe('<lion-field>', () => {
|
|||
<span slot="suffix">[suffix]</span>
|
||||
<span slot="feedback">[feedback]</span>
|
||||
</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
const names = [
|
||||
'label',
|
||||
|
|
@ -405,10 +417,9 @@ describe('<lion-field>', () => {
|
|||
names.forEach(slotName => {
|
||||
const slotLight = /** @type {HTMLElement} */ (el.querySelector(`[slot="${slotName}"]`));
|
||||
slotLight.setAttribute('test-me', 'ok');
|
||||
// @ts-expect-error
|
||||
const slot = /** @type {ShadowHTMLElement} */ (el.shadowRoot.querySelector(
|
||||
`slot[name="${slotName}"]`,
|
||||
));
|
||||
const slot = /** @type {ShadowHTMLElement} */ (
|
||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelector(`slot[name="${slotName}"]`)
|
||||
);
|
||||
const assignedNodes = slot.assignedNodes();
|
||||
expect(assignedNodes.length).to.equal(1);
|
||||
expect(assignedNodes[0].getAttribute('test-me')).to.equal('ok');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { defineCE, expect, fixture, fixtureSync, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture, fixtureSync } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import { SyncUpdatableMixin } from '../../src/utils/SyncUpdatableMixin.js';
|
||||
|
||||
|
|
@ -43,9 +44,9 @@ describe('SyncUpdatableMixin', () => {
|
|||
|
||||
const tagString = defineCE(UpdatableImplementation);
|
||||
const tag = unsafeStatic(tagString);
|
||||
const el = /** @type {UpdatableImplementation} */ (fixtureSync(
|
||||
html`<${tag} prop-b="b"></${tag}>`,
|
||||
));
|
||||
const el = /** @type {UpdatableImplementation} */ (
|
||||
fixtureSync(html`<${tag} prop-b="b"></${tag}>`)
|
||||
);
|
||||
|
||||
// Getters setters work as expected, without running property effects
|
||||
expect(el.propA).to.equal('init-a');
|
||||
|
|
@ -102,9 +103,9 @@ describe('SyncUpdatableMixin', () => {
|
|||
|
||||
const tagString = defineCE(UpdatableImplementation);
|
||||
const tag = unsafeStatic(tagString);
|
||||
const el = /** @type {UpdatableImplementation} */ (fixtureSync(
|
||||
html`<${tag} prop-b="b" .propA="${'a'}"></${tag}>`,
|
||||
));
|
||||
const el = /** @type {UpdatableImplementation} */ (
|
||||
fixtureSync(html`<${tag} prop-b="b" .propA="${'a'}"></${tag}>`)
|
||||
);
|
||||
|
||||
// Derived
|
||||
expect(el.derived).to.be.undefined;
|
||||
|
|
@ -114,19 +115,19 @@ describe('SyncUpdatableMixin', () => {
|
|||
expect(el.derived).to.equal('ab');
|
||||
expect(hasCalledRunPropertyEffect).to.be.true;
|
||||
|
||||
const el2 = /** @type {UpdatableImplementation} */ (await fixture(
|
||||
html`<${tag} .propA="${'a'}"></${tag}>`,
|
||||
));
|
||||
const el2 = /** @type {UpdatableImplementation} */ (
|
||||
await fixture(html`<${tag} .propA="${'a'}"></${tag}>`)
|
||||
);
|
||||
expect(el2.derived).to.equal('ainit-b');
|
||||
|
||||
const el3 = /** @type {UpdatableImplementation} */ (await fixture(
|
||||
html`<${tag} .propB="${'b'}"></${tag}>`,
|
||||
));
|
||||
const el3 = /** @type {UpdatableImplementation} */ (
|
||||
await fixture(html`<${tag} .propB="${'b'}"></${tag}>`)
|
||||
);
|
||||
expect(el3.derived).to.equal('init-ab');
|
||||
|
||||
const el4 = /** @type {UpdatableImplementation} */ (await fixture(
|
||||
html`<${tag} .propA=${'a'} .propB="${'b'}"></${tag}>`,
|
||||
));
|
||||
const el4 = /** @type {UpdatableImplementation} */ (
|
||||
await fixture(html`<${tag} .propA=${'a'} .propB="${'b'}"></${tag}>`)
|
||||
);
|
||||
expect(el4.derived).to.equal('ab');
|
||||
});
|
||||
|
||||
|
|
@ -150,8 +151,8 @@ describe('SyncUpdatableMixin', () => {
|
|||
* @param {string} name
|
||||
* @param {*} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
if (name === 'prop') {
|
||||
propChangedCount += 1;
|
||||
}
|
||||
|
|
@ -223,9 +224,9 @@ describe('SyncUpdatableMixin', () => {
|
|||
|
||||
const tagString = defineCE(UpdatableImplementation);
|
||||
const tag = unsafeStatic(tagString);
|
||||
const el = /** @type {UpdatableImplementation} */ (fixtureSync(
|
||||
html`<${tag} prop-b="b" .propA="${'a'}"></${tag}>`,
|
||||
));
|
||||
const el = /** @type {UpdatableImplementation} */ (
|
||||
fixtureSync(html`<${tag} prop-b="b" .propA="${'a'}"></${tag}>`)
|
||||
);
|
||||
const spy = sinon.spy(el, '_runPropertyEffect');
|
||||
expect(spy.callCount).to.equal(0);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import { browserDetection } from '@lion/core';
|
||||
import { getAriaElementsInRightDomOrder } from '../../src/utils/getAriaElementsInRightDomOrder.js';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html, unsafeStatic, defineCE } from '@open-wc/testing';
|
||||
import { expect, fixture, defineCE } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { LionField } from '@lion/form-core';
|
||||
import { getFormControlMembers } from '@lion/form-core/test-helpers';
|
||||
import { Required } from '../../src/validate/validators/Required.js';
|
||||
|
|
@ -31,9 +32,9 @@ describe('Required validation', async () => {
|
|||
const validator = new Required();
|
||||
|
||||
it('get aria-required attribute if element is part of the right tag names', async () => {
|
||||
const el = /** @type {FormControlHost & HTMLElement} */ (await fixture(
|
||||
html`<${tag}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormControlHost & HTMLElement} */ (
|
||||
await fixture(html`<${tag}></${tag}>`)
|
||||
);
|
||||
|
||||
Required._compatibleTags.forEach(tagName => {
|
||||
inputNodeTag = /** @type {HTMLElementWithValue} */ (document.createElement(tagName));
|
||||
|
|
@ -53,9 +54,9 @@ describe('Required validation', async () => {
|
|||
expect(_inputNode).to.not.have.attribute('aria-required');
|
||||
});
|
||||
it('get aria-required attribute if element is part of the right roles', async () => {
|
||||
const el = /** @type {FormControlHost & HTMLElement} */ (await fixture(
|
||||
html`<${tag}></${tag}>`,
|
||||
));
|
||||
const el = /** @type {FormControlHost & HTMLElement} */ (
|
||||
await fixture(html`<${tag}></${tag}>`)
|
||||
);
|
||||
|
||||
Required._compatibleRoles.forEach(role => {
|
||||
// @ts-ignore
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { LitElement } from '@lion/core';
|
||||
import { defineCE, expect, fixture, html, unsafeStatic } from '@open-wc/testing';
|
||||
import { defineCE, expect, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import { ValidateMixin } from '../../src/validate/ValidateMixin.js';
|
||||
import { Validator } from '../../src/validate/Validator.js';
|
||||
|
|
@ -171,9 +172,11 @@ describe('Validator', () => {
|
|||
const connectSpy = sinon.spy(myVal, 'onFormControlConnect');
|
||||
const disconnectSpy = sinon.spy(myVal, 'onFormControlDisconnect');
|
||||
|
||||
const el = /** @type {ValidateElement} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElement} */ (
|
||||
await fixture(html`
|
||||
<${tag} .validators=${[myVal]}>${lightDom}</${tag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
|
||||
expect(connectSpy.callCount).to.equal(1);
|
||||
expect(connectSpy.calledWith(el)).to.equal(true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable no-unused-vars, no-param-reassign */
|
||||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import sinon from 'sinon';
|
||||
import '@lion/form-core/define-validation-feedback';
|
||||
import { AlwaysInvalid, AlwaysValid } from '../../test-helpers/index.js';
|
||||
|
|
@ -10,9 +11,9 @@ import { AlwaysInvalid, AlwaysValid } from '../../test-helpers/index.js';
|
|||
|
||||
describe('lion-validation-feedback', () => {
|
||||
it('renders a validation message', async () => {
|
||||
const el = /** @type {LionValidationFeedback} */ (await fixture(
|
||||
html`<lion-validation-feedback></lion-validation-feedback>`,
|
||||
));
|
||||
const el = /** @type {LionValidationFeedback} */ (
|
||||
await fixture(html`<lion-validation-feedback></lion-validation-feedback>`)
|
||||
);
|
||||
expect(el).shadowDom.to.equal('');
|
||||
el.feedbackData = [{ message: 'hello', type: 'error', validator: new AlwaysInvalid() }];
|
||||
await el.updateComplete;
|
||||
|
|
@ -20,9 +21,9 @@ describe('lion-validation-feedback', () => {
|
|||
});
|
||||
|
||||
it('renders the validation type attribute', async () => {
|
||||
const el = /** @type {LionValidationFeedback} */ (await fixture(
|
||||
html`<lion-validation-feedback></lion-validation-feedback>`,
|
||||
));
|
||||
const el = /** @type {LionValidationFeedback} */ (
|
||||
await fixture(html`<lion-validation-feedback></lion-validation-feedback>`)
|
||||
);
|
||||
el.feedbackData = [{ message: 'hello', type: 'error', validator: new AlwaysInvalid() }];
|
||||
await el.updateComplete;
|
||||
expect(el.getAttribute('type')).to.equal('error');
|
||||
|
|
@ -33,9 +34,9 @@ describe('lion-validation-feedback', () => {
|
|||
});
|
||||
|
||||
it('success message clears after 3s', async () => {
|
||||
const el = /** @type {LionValidationFeedback} */ (await fixture(
|
||||
html`<lion-validation-feedback></lion-validation-feedback>`,
|
||||
));
|
||||
const el = /** @type {LionValidationFeedback} */ (
|
||||
await fixture(html`<lion-validation-feedback></lion-validation-feedback>`)
|
||||
);
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
|
|
@ -55,9 +56,9 @@ describe('lion-validation-feedback', () => {
|
|||
});
|
||||
|
||||
it('does not clear error messages', async () => {
|
||||
const el = /** @type {LionValidationFeedback} */ (await fixture(
|
||||
html`<lion-validation-feedback></lion-validation-feedback>`,
|
||||
));
|
||||
const el = /** @type {LionValidationFeedback} */ (
|
||||
await fixture(html`<lion-validation-feedback></lion-validation-feedback>`)
|
||||
);
|
||||
|
||||
const clock = sinon.useFakeTimers();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Constructor } from '@open-wc/dedupe-mixin';
|
||||
import { BooleanAttributePart, LitElement } from '@lion/core';
|
||||
import { LitElement } from '@lion/core';
|
||||
import { FormatNumberOptions } from '@lion/localize/types/LocalizeMixinTypes';
|
||||
import { ValidateHost } from './validate/ValidateMixinTypes';
|
||||
import { FormControlHost } from './FormControlMixinTypes';
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export declare class ChoiceInputHost {
|
|||
protected get _inputNode(): HTMLElement;
|
||||
|
||||
protected _proxyInputEvent(): void;
|
||||
protected requestUpdateInternal(name: string, oldValue: any): void;
|
||||
protected requestUpdate(name: string, oldValue: any): void;
|
||||
protected _choiceGraphicTemplate(): TemplateResult;
|
||||
protected _afterTemplate(): TemplateResult;
|
||||
protected _preventDuplicateLabelClick(ev: Event): void;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export declare interface SyncUpdatableNamespace {
|
|||
|
||||
export declare class SyncUpdatableHost {
|
||||
/**
|
||||
* An abstraction that has the exact same api as `requestUpdateInternal`, but taking
|
||||
* An abstraction that has the exact same api as `requestUpdate`, but taking
|
||||
* into account:
|
||||
* - [member order independence](https://github.com/webcomponents/gold-standard/wiki/Member-Order-Independence)
|
||||
* - property effects start when all (light) dom has initialized (on firstUpdated)
|
||||
|
|
@ -18,7 +18,7 @@ export declare class SyncUpdatableHost {
|
|||
* - compatible with propertyAccessor.`hasChanged`: no manual checks needed or accidentally
|
||||
* run property effects / events when no change happened
|
||||
* effects when values didn't change
|
||||
* All code previously present in requestUpdateInternal can be placed in this method.
|
||||
* All code previously present in requestUpdate can be placed in this method.
|
||||
* @param {string} name
|
||||
* @param {*} oldValue
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { getAllTagNames } from './helpers/helpers.js';
|
||||
import './helpers/umbrella-form.js';
|
||||
import '@lion/dialog/define';
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
|
||||
import { elementUpdated, expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import './helpers/umbrella-form.js';
|
||||
import { getAllFieldsAndFormGroups } from './helpers/helpers.js';
|
||||
|
||||
|
|
@ -81,9 +82,11 @@ describe(`Submitting/Resetting/Clearing Form`, async () => {
|
|||
});
|
||||
|
||||
it('calling resetGroup() should reset all metadata (interaction states and initial values)', async () => {
|
||||
const el = /** @type {UmbrellaForm} */ (await fixture(
|
||||
html`<umbrella-form .serializedValue="${fullyPrefilledSerializedValue}"></umbrella-form>`,
|
||||
));
|
||||
const el = /** @type {UmbrellaForm} */ (
|
||||
await fixture(
|
||||
html`<umbrella-form .serializedValue="${fullyPrefilledSerializedValue}"></umbrella-form>`,
|
||||
)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const formEl = el._lionFormNode;
|
||||
|
||||
|
|
@ -125,9 +128,11 @@ describe(`Submitting/Resetting/Clearing Form`, async () => {
|
|||
|
||||
// Wait till ListboxMixin properly clears
|
||||
it('calling clearGroup() should clear all fields', async () => {
|
||||
const el = /** @type {UmbrellaForm} */ (await fixture(
|
||||
html`<umbrella-form .serializedValue="${fullyPrefilledSerializedValue}"></umbrella-form>`,
|
||||
));
|
||||
const el = /** @type {UmbrellaForm} */ (
|
||||
await fixture(
|
||||
html`<umbrella-form .serializedValue="${fullyPrefilledSerializedValue}"></umbrella-form>`,
|
||||
)
|
||||
);
|
||||
await el.updateComplete;
|
||||
const formEl = el._lionFormNode;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import { getAllTagNames } from './helpers/helpers.js';
|
||||
import './helpers/umbrella-form.js';
|
||||
|
||||
|
|
@ -64,28 +65,30 @@ describe('Form Integrations', () => {
|
|||
|
||||
describe('Form Integrations', () => {
|
||||
it('does not become dirty when elements are prefilled', async () => {
|
||||
const el = /** @type {UmbrellaForm} */ (await fixture(
|
||||
html`<umbrella-form
|
||||
.serializedValue="${{
|
||||
full_name: { first_name: '', last_name: '' },
|
||||
date: '2000-12-12',
|
||||
datepicker: '2020-12-12',
|
||||
bio: '',
|
||||
money: '',
|
||||
iban: '',
|
||||
email: '',
|
||||
checkers: ['foo', 'bar'],
|
||||
dinosaurs: 'brontosaurus',
|
||||
favoriteFruit: 'Banana',
|
||||
favoriteMovie: 'Rocky',
|
||||
favoriteColor: 'hotpink',
|
||||
lyrics: '1',
|
||||
range: 2.3,
|
||||
terms: [],
|
||||
comments: '',
|
||||
}}"
|
||||
></umbrella-form>`,
|
||||
));
|
||||
const el = /** @type {UmbrellaForm} */ (
|
||||
await fixture(
|
||||
html`<umbrella-form
|
||||
.serializedValue="${{
|
||||
full_name: { first_name: '', last_name: '' },
|
||||
date: '2000-12-12',
|
||||
datepicker: '2020-12-12',
|
||||
bio: '',
|
||||
money: '',
|
||||
iban: '',
|
||||
email: '',
|
||||
checkers: ['foo', 'bar'],
|
||||
dinosaurs: 'brontosaurus',
|
||||
favoriteFruit: 'Banana',
|
||||
favoriteMovie: 'Rocky',
|
||||
favoriteColor: 'hotpink',
|
||||
lyrics: '1',
|
||||
range: 2.3,
|
||||
terms: [],
|
||||
comments: '',
|
||||
}}"
|
||||
></umbrella-form>`,
|
||||
)
|
||||
);
|
||||
|
||||
await el._lionFormNode.initComplete;
|
||||
expect(el._lionFormNode.dirty).to.be.false;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture, html, defineCE, unsafeStatic } from '@open-wc/testing';
|
||||
import { expect, fixture, defineCE } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { Required, DefaultSuccess, Validator } from '@lion/form-core';
|
||||
import { loadDefaultFeedbackMessages } from '@lion/validate-messages';
|
||||
import { LionInput } from '@lion/input';
|
||||
|
|
@ -41,7 +42,8 @@ describe('Form Validation Integrations', () => {
|
|||
}
|
||||
const elTagString = defineCE(ValidateElementCustomTypes);
|
||||
const elTag = unsafeStatic(elTagString);
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (await fixture(html`
|
||||
const el = /** @type {ValidateElementCustomTypes} */ (
|
||||
await fixture(html`
|
||||
<${elTag}
|
||||
.validators=${[
|
||||
new Required(null, { getMessage: () => 'error' }),
|
||||
|
|
@ -49,7 +51,8 @@ describe('Form Validation Integrations', () => {
|
|||
new DefaultSuccess(),
|
||||
]}
|
||||
>${lightDom}</${elTag}>
|
||||
`));
|
||||
`)
|
||||
);
|
||||
const { _feedbackNode } = getFormControlMembers(el);
|
||||
|
||||
expect(_feedbackNode.feedbackData?.length).to.equal(0);
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import '@lion/input-stepper/define';
|
|||
|
||||
export class UmbrellaForm extends LitElement {
|
||||
get _lionFormNode() {
|
||||
return /** @type {import('@lion/form').LionForm} */ (this.shadowRoot?.querySelector(
|
||||
'lion-form',
|
||||
));
|
||||
return /** @type {import('@lion/form').LionForm} */ (
|
||||
this.shadowRoot?.querySelector('lion-form')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, html, unsafeStatic, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import sinon from 'sinon';
|
||||
|
|
@ -111,9 +112,9 @@ const choiceDispatchesCountOnInteraction = (tagname, count) => {
|
|||
const tag = unsafeStatic(tagname);
|
||||
const spy = sinon.spy();
|
||||
it(getInteractionTitle(count), async () => {
|
||||
const el = /** @type {HTMLElement & {checked: boolean}} */ (await fixture(
|
||||
html`<${tag} .choiceValue="${'option'}"></${tag}>`,
|
||||
));
|
||||
const el = /** @type {HTMLElement & {checked: boolean}} */ (
|
||||
await fixture(html`<${tag} .choiceValue="${'option'}"></${tag}>`)
|
||||
);
|
||||
el.addEventListener('model-value-changed', spy);
|
||||
el.checked = true;
|
||||
expect(spy.callCount).to.equal(count);
|
||||
|
|
@ -161,17 +162,17 @@ const choiceGroupDispatchesCountOnInteraction = (groupTagname, itemTagname, coun
|
|||
`);
|
||||
|
||||
el.addEventListener('model-value-changed', spy);
|
||||
const option2 = /** @type {HTMLElement & {checked: boolean}} */ (el.querySelector(
|
||||
`${itemTagname}:nth-child(2)`,
|
||||
));
|
||||
const option2 = /** @type {HTMLElement & {checked: boolean}} */ (
|
||||
el.querySelector(`${itemTagname}:nth-child(2)`)
|
||||
);
|
||||
option2.checked = true;
|
||||
expect(spy.callCount).to.equal(count);
|
||||
|
||||
spy.resetHistory();
|
||||
|
||||
const option3 = /** @type {HTMLElement & {checked: boolean}} */ (el.querySelector(
|
||||
`${itemTagname}:nth-child(3)`,
|
||||
));
|
||||
const option3 = /** @type {HTMLElement & {checked: boolean}} */ (
|
||||
el.querySelector(`${itemTagname}:nth-child(3)`)
|
||||
);
|
||||
option3.checked = true;
|
||||
expect(spy.callCount).to.equal(count);
|
||||
});
|
||||
|
|
@ -233,15 +234,17 @@ describe('lion-select', () => {
|
|||
|
||||
it(getInteractionTitle(interactionCount), async () => {
|
||||
const spy = sinon.spy();
|
||||
const el = /** @type {LionSelect} */ (await fixture(html`
|
||||
<lion-select>
|
||||
<select slot="input">
|
||||
<option value="option1"></option>
|
||||
<option value="option2"></option>
|
||||
<option value="option3"></option>
|
||||
</select>
|
||||
</lion-select>
|
||||
`));
|
||||
const el = /** @type {LionSelect} */ (
|
||||
await fixture(html`
|
||||
<lion-select>
|
||||
<select slot="input">
|
||||
<option value="option1"></option>
|
||||
<option value="option2"></option>
|
||||
<option value="option3"></option>
|
||||
</select>
|
||||
</lion-select>
|
||||
`)
|
||||
);
|
||||
el.addEventListener('model-value-changed', spy);
|
||||
const option2 = /** @type {HTMLOptionElement} */ (el.querySelector('option:nth-child(2)'));
|
||||
|
||||
|
|
@ -464,9 +467,10 @@ describe('detail.isTriggeredByUser', () => {
|
|||
}
|
||||
|
||||
const name = controlName === 'checkbox-group' ? 'test[]' : 'test';
|
||||
const el = /** @type {LitElement & FormControl & {value: string} & {registrationComplete: Promise<boolean>} & {formElements: Array.<FormControl & {value: string}>}} */ (await fixture(
|
||||
html`<${tag} name="${name}">${childrenEl}</${tag}>`,
|
||||
));
|
||||
const el =
|
||||
/** @type {LitElement & FormControl & {value: string} & {registrationComplete: Promise<boolean>} & {formElements: Array.<FormControl & {value: string}>}} */ (
|
||||
await fixture(html`<${tag} name="${name}">${childrenEl}</${tag}>`)
|
||||
);
|
||||
await el.registrationComplete;
|
||||
el.addEventListener('model-value-changed', spy);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import '@lion/fieldset/define';
|
||||
import '@lion/input/define';
|
||||
import { expect, html, fixture } from '@open-wc/testing';
|
||||
import { expect, fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import sinon from 'sinon';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
import {
|
||||
expect,
|
||||
fixture as _fixture,
|
||||
html,
|
||||
oneEvent,
|
||||
aTimeout,
|
||||
unsafeStatic,
|
||||
defineCE,
|
||||
} from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture, oneEvent, aTimeout, defineCE } from '@open-wc/testing';
|
||||
import { html, unsafeStatic } from 'lit/static-html.js';
|
||||
import { spy } from 'sinon';
|
||||
import { LionField } from '@lion/form-core';
|
||||
import { LionFieldset } from '@lion/fieldset';
|
||||
|
|
@ -61,9 +54,9 @@ describe('<lion-form>', () => {
|
|||
</form>
|
||||
</lion-form>
|
||||
`);
|
||||
const resetButton = /** @type {HTMLInputElement} */ (withDefaults.querySelector(
|
||||
'input[type=reset]',
|
||||
));
|
||||
const resetButton = /** @type {HTMLInputElement} */ (
|
||||
withDefaults.querySelector('input[type=reset]')
|
||||
);
|
||||
|
||||
withDefaults.formElements.firstName.modelValue = 'updatedFoo';
|
||||
expect(withDefaults.modelValue).to.deep.equal({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { expect, fixture as _fixture, html } from '@open-wc/testing';
|
||||
import { expect, fixture as _fixture } from '@open-wc/testing';
|
||||
import { html } from 'lit/static-html.js';
|
||||
import '@lion/helpers/define-sb-action-logger';
|
||||
|
||||
/**
|
||||
|
|
@ -53,9 +54,9 @@ describe('sb-action-logger', () => {
|
|||
it('shows a visual cue whenever something is logged to the logger', async () => {
|
||||
const el = await fixture(html`<sb-action-logger></sb-action-logger>`);
|
||||
|
||||
const cueEl = /** @type {HTMLElement} */ (el.shadowRoot?.querySelector(
|
||||
'.header__log-cue-overlay',
|
||||
));
|
||||
const cueEl = /** @type {HTMLElement} */ (
|
||||
el.shadowRoot?.querySelector('.header__log-cue-overlay')
|
||||
);
|
||||
expect(cueEl.classList.contains('header__log-cue-overlay--slide')).to.be.false;
|
||||
|
||||
el.log('Hello, World!');
|
||||
|
|
@ -65,9 +66,9 @@ describe('sb-action-logger', () => {
|
|||
it('has a visual counter that counts the amount of total logs', async () => {
|
||||
const el = await fixture(html`<sb-action-logger></sb-action-logger>`);
|
||||
|
||||
const cueEl = /** @type {HTMLElement} */ (el.shadowRoot?.querySelector(
|
||||
'.header__log-cue-overlay',
|
||||
));
|
||||
const cueEl = /** @type {HTMLElement} */ (
|
||||
el.shadowRoot?.querySelector('.header__log-cue-overlay')
|
||||
);
|
||||
|
||||
expect(cueEl.classList.contains('.header__log-cue-overlay--slide')).to.be.false;
|
||||
|
||||
|
|
@ -100,12 +101,12 @@ describe('sb-action-logger', () => {
|
|||
|
||||
const loggerEl = /** @type {HTMLElement} */ (el.shadowRoot?.querySelector('.logger'));
|
||||
|
||||
const firstLogCount = /** @type {HTMLElement} */ (loggerEl.firstElementChild?.querySelector(
|
||||
'.logger__log-count',
|
||||
));
|
||||
const lastLogCount = /** @type {HTMLElement} */ (loggerEl.lastElementChild?.querySelector(
|
||||
'.logger__log-count',
|
||||
));
|
||||
const firstLogCount = /** @type {HTMLElement} */ (
|
||||
loggerEl.firstElementChild?.querySelector('.logger__log-count')
|
||||
);
|
||||
const lastLogCount = /** @type {HTMLElement} */ (
|
||||
loggerEl.lastElementChild?.querySelector('.logger__log-count')
|
||||
);
|
||||
|
||||
expect(loggerEl.children.length).to.equal(4);
|
||||
expect(firstLogCount.innerText).to.equal('3');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/**
|
||||
* @typedef {import('lit-html').nothing} nothing
|
||||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
* @typedef {import('@lion/core').nothing} nothing
|
||||
*/
|
||||
|
||||
export class IconManager {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import { css, html, LitElement, nothing, render, TemplateResult } from '@lion/core';
|
||||
import { css, html, LitElement, nothing, render, isTemplateResult } from '@lion/core';
|
||||
import { icons } from './icons.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/core').TemplateResult} TemplateResult
|
||||
* @typedef {(tag: (strings: TemplateStringsArray, ... expr: string[]) => string) => string} TagFunction
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {?} wrappedSvgObject
|
||||
*/
|
||||
|
|
@ -14,7 +19,7 @@ function unwrapSvg(wrappedSvgObject) {
|
|||
* @param {TemplateResult|nothing} svg
|
||||
*/
|
||||
function validateSvg(svg) {
|
||||
if (!(svg === nothing || svg instanceof TemplateResult)) {
|
||||
if (!(svg === nothing || isTemplateResult(svg))) {
|
||||
throw new Error(
|
||||
'icon accepts only lit-html templates or functions like "tag => tag`<svg>...</svg>`"',
|
||||
);
|
||||
|
|
@ -98,7 +103,10 @@ export class LionIcon extends LitElement {
|
|||
this.role = 'img';
|
||||
this.ariaLabel = '';
|
||||
this.iconId = '';
|
||||
/** @private */
|
||||
/**
|
||||
* @private
|
||||
* @type {TemplateResult|nothing|TagFunction}
|
||||
*/
|
||||
this.__svg = nothing;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +135,7 @@ export class LionIcon extends LitElement {
|
|||
/**
|
||||
* On IE11, svgs without focusable false appear in the tab order
|
||||
* so make sure to have <svg focusable="false"> in svg files
|
||||
* @param {TemplateResult|nothing} svg
|
||||
* @param {TemplateResult|nothing|TagFunction} svg
|
||||
*/
|
||||
set svg(svg) {
|
||||
this.__svg = svg;
|
||||
|
|
@ -138,6 +146,9 @@ export class LionIcon extends LitElement {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {TemplateResult|nothing|TagFunction}
|
||||
*/
|
||||
get svg() {
|
||||
return this.__svg;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { nothing, until } from '@lion/core';
|
||||
import { aTimeout, expect, fixture as _fixture, fixtureSync, html } from '@open-wc/testing';
|
||||
import { nothing, until, html } from '@lion/core';
|
||||
import { aTimeout, expect, fixture as _fixture, fixtureSync } from '@open-wc/testing';
|
||||
import '@lion/icon/define';
|
||||
import { icons } from '../src/icons.js';
|
||||
import hammerSvg from './hammer.svg.js';
|
||||
|
|
@ -145,7 +145,7 @@ describe('lion-icon', () => {
|
|||
await el.updateComplete;
|
||||
el.svg = nothing;
|
||||
await el.updateComplete;
|
||||
expect(el.innerHTML).to.equal('<!----><!---->'); // don't use lightDom.to.equal(''), it gives false positives
|
||||
expect(el.innerHTML).to.equal('<!---->'); // don't use lightDom.to.equal(''), it gives false positives
|
||||
});
|
||||
|
||||
it('does not render "null" if changed from valid input to null', async () => {
|
||||
|
|
@ -153,7 +153,7 @@ describe('lion-icon', () => {
|
|||
await el.updateComplete;
|
||||
el.svg = nothing;
|
||||
await el.updateComplete;
|
||||
expect(el.innerHTML).to.equal('<!----><!---->'); // don't use lightDom.to.equal(''), it gives false positives
|
||||
expect(el.innerHTML).to.equal('<!---->'); // don't use lightDom.to.equal(''), it gives false positives
|
||||
});
|
||||
|
||||
it('supports icons using an icon id', async () => {
|
||||
|
|
|
|||
|
|
@ -18,30 +18,34 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('uses formatAmount for formatting', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount></lion-input-amount>`)
|
||||
);
|
||||
expect(el.formatter).to.equal(formatAmount);
|
||||
});
|
||||
|
||||
it('formatAmount uses currency provided on webcomponent', async () => {
|
||||
// JOD displays 3 fraction digits by default
|
||||
localize.locale = 'fr-FR';
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
html`<lion-input-amount currency="JOD" .modelValue="${123}"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(
|
||||
html`<lion-input-amount currency="JOD" .modelValue="${123}"></lion-input-amount>`,
|
||||
)
|
||||
);
|
||||
expect(el.formattedValue).to.equal('123,000');
|
||||
});
|
||||
|
||||
it('formatAmount uses locale provided in formatOptions', async () => {
|
||||
let el = /** @type {LionInputAmount} */ (await fixture(
|
||||
html`
|
||||
<lion-input-amount
|
||||
.formatOptions="${{ locale: 'en-GB' }}"
|
||||
.modelValue="${123}"
|
||||
></lion-input-amount>
|
||||
`,
|
||||
));
|
||||
let el = /** @type {LionInputAmount} */ (
|
||||
await fixture(
|
||||
html`
|
||||
<lion-input-amount
|
||||
.formatOptions="${{ locale: 'en-GB' }}"
|
||||
.modelValue="${123}"
|
||||
></lion-input-amount>
|
||||
`,
|
||||
)
|
||||
);
|
||||
expect(el.formattedValue).to.equal('123.00');
|
||||
el = await fixture(
|
||||
html`
|
||||
|
|
@ -55,9 +59,11 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('ignores global locale change if property is provided', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(html`
|
||||
<lion-input-amount .modelValue=${123456.78} .locale="${'en-GB'}"></lion-input-amount>
|
||||
`));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(html`
|
||||
<lion-input-amount .modelValue=${123456.78} .locale="${'en-GB'}"></lion-input-amount>
|
||||
`)
|
||||
);
|
||||
expect(el.formattedValue).to.equal('123,456.78'); // British
|
||||
localize.locale = 'nl-NL';
|
||||
await aTimeout(0);
|
||||
|
|
@ -65,24 +71,24 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('uses parseAmount for parsing', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount></lion-input-amount>`)
|
||||
);
|
||||
expect(el.parser).to.equal(parseAmount);
|
||||
});
|
||||
|
||||
it('sets inputmode attribute to decimal', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount></lion-input-amount>`)
|
||||
);
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.getAttribute('inputmode')).to.equal('decimal');
|
||||
});
|
||||
|
||||
it('has type="text" to activate default keyboard on mobile with all necessary symbols', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount></lion-input-amount>`)
|
||||
);
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.type).to.equal('text');
|
||||
});
|
||||
|
|
@ -93,9 +99,9 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('displays currency if provided', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="EUR"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
|
||||
);
|
||||
expect(
|
||||
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after')
|
||||
?.innerText,
|
||||
|
|
@ -104,9 +110,9 @@ describe('<lion-input-amount>', () => {
|
|||
|
||||
it('displays correct currency for TRY if locale is tr-TR', async () => {
|
||||
localize.locale = 'tr-TR';
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="TRY"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount currency="TRY"></lion-input-amount>`)
|
||||
);
|
||||
expect(
|
||||
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'after')
|
||||
?.innerText,
|
||||
|
|
@ -114,9 +120,9 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('can update currency', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="EUR"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
|
||||
);
|
||||
el.currency = 'USD';
|
||||
await el.updateComplete;
|
||||
expect(
|
||||
|
|
@ -126,9 +132,11 @@ describe('<lion-input-amount>', () => {
|
|||
});
|
||||
|
||||
it('ignores currency if a suffix is already present', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(
|
||||
`<lion-input-amount currency="EUR"><span slot="suffix">my-currency</span></lion-input-amount>`,
|
||||
)
|
||||
);
|
||||
expect(
|
||||
/** @type {HTMLElement[]} */ (Array.from(el.children)).find(child => child.slot === 'suffix')
|
||||
?.innerText,
|
||||
|
|
@ -143,18 +151,18 @@ describe('<lion-input-amount>', () => {
|
|||
|
||||
describe('Accessibility', () => {
|
||||
it('adds currency id to aria-labelledby of input', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="EUR"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
|
||||
);
|
||||
expect(el._currencyDisplayNode?.getAttribute('data-label')).to.be.not.null;
|
||||
const { _inputNode } = getInputMembers(/** @type {* & LionInput} */ (el));
|
||||
expect(_inputNode.getAttribute('aria-labelledby')).to.contain(el._currencyDisplayNode?.id);
|
||||
});
|
||||
|
||||
it('adds an aria-label to currency slot', async () => {
|
||||
const el = /** @type {LionInputAmount} */ (await fixture(
|
||||
`<lion-input-amount currency="EUR"></lion-input-amount>`,
|
||||
));
|
||||
const el = /** @type {LionInputAmount} */ (
|
||||
await fixture(`<lion-input-amount currency="EUR"></lion-input-amount>`)
|
||||
);
|
||||
expect(el._currencyDisplayNode?.getAttribute('aria-label')).to.equal('euros');
|
||||
el.currency = 'USD';
|
||||
await el.updateComplete;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { LionCalendar } from '@lion/calendar';
|
||||
import { html, ifDefined, ScopedElementsMixin } from '@lion/core';
|
||||
import { html, ScopedElementsMixin, ifDefined, render } from '@lion/core';
|
||||
import { LionInputDate } from '@lion/input-date';
|
||||
import {
|
||||
OverlayMixin,
|
||||
|
|
@ -9,6 +10,10 @@ import {
|
|||
} from '@lion/overlays';
|
||||
import { LionCalendarOverlayFrame } from './LionCalendarOverlayFrame.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('@lion/core').RenderOptions} RenderOptions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @customElement lion-input-datepicker
|
||||
*/
|
||||
|
|
@ -62,13 +67,13 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
...super.slots,
|
||||
[this._calendarInvokerSlot]: () => {
|
||||
const renderParent = document.createElement('div');
|
||||
/** @type {typeof LionInputDatepicker} */ (this.constructor).render(
|
||||
render(
|
||||
this._invokerTemplate(),
|
||||
renderParent,
|
||||
{
|
||||
/** @type {RenderOptions} */ ({
|
||||
scopeName: this.localName,
|
||||
eventContext: this,
|
||||
},
|
||||
}),
|
||||
);
|
||||
return /** @type {HTMLElement} */ (renderParent.firstElementChild);
|
||||
},
|
||||
|
|
@ -169,9 +174,9 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
* @protected
|
||||
*/
|
||||
get _calendarNode() {
|
||||
return /** @type {LionCalendar} */ (this._overlayCtrl.contentNode.querySelector(
|
||||
'[slot="content"]',
|
||||
));
|
||||
return /** @type {LionCalendar} */ (
|
||||
this._overlayCtrl.contentNode.querySelector('[slot="content"]')
|
||||
);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
|
|
@ -204,8 +209,8 @@ export class LionInputDatepicker extends ScopedElementsMixin(
|
|||
* @param {PropertyKey} name
|
||||
* @param {?} oldValue
|
||||
*/
|
||||
requestUpdateInternal(name, oldValue) {
|
||||
super.requestUpdateInternal(name, oldValue);
|
||||
requestUpdate(name, oldValue) {
|
||||
super.requestUpdate(name, oldValue);
|
||||
|
||||
if (name === 'disabled' || name === 'readOnly') {
|
||||
this.__toggleInvokerDisabled();
|
||||
|
|
|
|||
|
|
@ -57,13 +57,15 @@ export class DatepickerInputObject {
|
|||
}
|
||||
|
||||
get overlayHeadingEl() {
|
||||
return /** @type {HTMLElement} */ (this.overlayEl &&
|
||||
this.overlayEl.shadowRoot?.querySelector('.calendar-overlay__heading'));
|
||||
return /** @type {HTMLElement} */ (
|
||||
this.overlayEl && this.overlayEl.shadowRoot?.querySelector('.calendar-overlay__heading')
|
||||
);
|
||||
}
|
||||
|
||||
get overlayCloseButtonEl() {
|
||||
return /** @type {HTMLElement} */ (this.calendarEl &&
|
||||
this.overlayEl.shadowRoot?.querySelector('#close-button'));
|
||||
return /** @type {HTMLElement} */ (
|
||||
this.calendarEl && this.overlayEl.shadowRoot?.querySelector('#close-button')
|
||||
);
|
||||
}
|
||||
|
||||
get calendarEl() {
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
/** @type {HTMLSlotElement} */ (elObj.overlayHeadingEl.querySelector(
|
||||
'slot[name="heading"]',
|
||||
)).assignedNodes()[0],
|
||||
/** @type {HTMLSlotElement} */ (
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]')
|
||||
).assignedNodes()[0],
|
||||
).lightDom.to.equal('Pick your date');
|
||||
});
|
||||
|
||||
|
|
@ -90,9 +90,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const elObj = new DatepickerInputObject(el);
|
||||
await elObj.openCalendar();
|
||||
expect(
|
||||
/** @type {HTMLSlotElement} */ (elObj.overlayHeadingEl.querySelector(
|
||||
'slot[name="heading"]',
|
||||
)).assignedNodes()[0],
|
||||
/** @type {HTMLSlotElement} */ (
|
||||
elObj.overlayHeadingEl.querySelector('slot[name="heading"]')
|
||||
).assignedNodes()[0],
|
||||
).lightDom.to.equal('foo');
|
||||
});
|
||||
|
||||
|
|
@ -315,9 +315,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-input-datepicker calendar-heading="foo"></lion-input-datepicker>
|
||||
`);
|
||||
const calendarEl = /** @type {LionCalendar} */ (el.shadowRoot?.querySelector(
|
||||
'[data-tag-name="lion-calendar"]',
|
||||
));
|
||||
const calendarEl = /** @type {LionCalendar} */ (
|
||||
el.shadowRoot?.querySelector('lion-calendar')
|
||||
);
|
||||
const { dateSelectedByUser } = getProtectedMembersCalendar(calendarEl);
|
||||
// First set a fixed date as if selected by a user
|
||||
dateSelectedByUser(new Date('December 17, 2020 03:24:00 GMT+0000'));
|
||||
|
|
@ -342,9 +342,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
const el = await fixture(html`
|
||||
<lion-input-datepicker calendar-heading="foo"></lion-input-datepicker>
|
||||
`);
|
||||
const calendarEl = /** @type {LionCalendar} */ (el.shadowRoot?.querySelector(
|
||||
'[data-tag-name="lion-calendar"]',
|
||||
));
|
||||
const calendarEl = /** @type {LionCalendar} */ (
|
||||
el.shadowRoot?.querySelector('lion-calendar')
|
||||
);
|
||||
const { dateSelectedByUser } = getProtectedMembersCalendar(calendarEl);
|
||||
|
||||
// First set a fixed date as if selected by a user
|
||||
|
|
@ -356,9 +356,9 @@ describe('<lion-input-datepicker>', () => {
|
|||
await elObj.openCalendar();
|
||||
|
||||
// Select the first date button, which is 29th of previous month (November)
|
||||
const firstDateBtn = /** @type {HTMLButtonElement} */ (calendarEl?.shadowRoot?.querySelector(
|
||||
'.calendar__day-button',
|
||||
));
|
||||
const firstDateBtn = /** @type {HTMLButtonElement} */ (
|
||||
calendarEl?.shadowRoot?.querySelector('.calendar__day-button')
|
||||
);
|
||||
firstDateBtn.click();
|
||||
|
||||
expect(/** @type {Date} */ (el.modelValue).getTime()).to.equal(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable max-classes-per-file */
|
||||
/* eslint-disable max-classes-per-file, import/no-extraneous-dependencies */
|
||||
|
||||
import { localize } from '@lion/localize';
|
||||
import { Unparseable, Validator } from '@lion/form-core';
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue