From 6f08e9296bd3f3c08f9144024090029bfa3fe86a Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Mon, 28 Sep 2020 12:20:45 +0200 Subject: [PATCH] feat: add types for fieldset and form --- .changeset/silly-chairs-grow.md | 6 +++ packages/fieldset/src/LionFieldset.js | 7 +-- packages/form/src/LionForm.js | 60 ++++++++++++++--------- packages/form/test/lion-form.test.js | 69 ++++++++++++++------------- tsconfig.json | 2 + 5 files changed, 86 insertions(+), 58 deletions(-) create mode 100644 .changeset/silly-chairs-grow.md diff --git a/.changeset/silly-chairs-grow.md b/.changeset/silly-chairs-grow.md new file mode 100644 index 000000000..bc447ec6d --- /dev/null +++ b/.changeset/silly-chairs-grow.md @@ -0,0 +1,6 @@ +--- +'@lion/fieldset': minor +'@lion/form': minor +--- + +Added types for form and fieldset packages. diff --git a/packages/fieldset/src/LionFieldset.js b/packages/fieldset/src/LionFieldset.js index c87415d07..c5cbb234d 100644 --- a/packages/fieldset/src/LionFieldset.js +++ b/packages/fieldset/src/LionFieldset.js @@ -18,13 +18,14 @@ import { FormGroupMixin } from '@lion/form-core'; * cannot be accessed individually via object keys. * * @customElement lion-fieldset - * @extends {LitElement} */ +// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110 export class LionFieldset extends FormGroupMixin(LitElement) { constructor() { super(); - /** @override from FormRegistrarMixin */ + /** @override FormRegistrarMixin */ this._isFormOrFieldset = true; - this._repropagationRole = 'fieldset'; // configures FormControlMixin + /** @override FormControlMixin */ + this._repropagationRole = 'fieldset'; } } diff --git a/packages/form/src/LionForm.js b/packages/form/src/LionForm.js index 9080972c6..226991999 100644 --- a/packages/form/src/LionForm.js +++ b/packages/form/src/LionForm.js @@ -1,17 +1,38 @@ import { LionFieldset } from '@lion/fieldset'; +const throwFormNodeError = () => { + throw new Error( + 'No form node found. Did you put a
element inside your custom-form element?', + ); +}; + /** * LionForm: form wrapper providing extra features and integration with lion-field elements. * * @customElement lion-form - * @extends {LionFieldset} */ // eslint-disable-next-line no-unused-vars export class LionForm extends LionFieldset { + constructor() { + super(); + /** @param {Event} ev */ + this._submit = ev => { + ev.preventDefault(); + ev.stopPropagation(); + this.submitGroup(); + this.dispatchEvent(new Event('submit', { bubbles: true })); + }; + /** @param {Event} ev */ + this._reset = ev => { + ev.preventDefault(); + ev.stopPropagation(); + this.resetGroup(); + this.dispatchEvent(new Event('reset', { bubbles: true })); + }; + } + connectedCallback() { - if (super.connectedCallback) { - super.connectedCallback(); - } + super.connectedCallback(); this.__registerEventsForLionForm(); // @override LionFieldset: makes sure a11y is handled by ._formNode @@ -19,39 +40,32 @@ export class LionForm extends LionFieldset { } disconnectedCallback() { - if (super.disconnectedCallback) { - super.disconnectedCallback(); - } + super.disconnectedCallback(); this.__teardownEventsForLionForm(); } get _formNode() { - return this.querySelector('form'); + return /** @type {HTMLFormElement} */ (this.querySelector('form')); } submit() { - this._formNode.submit(); + if (this._formNode) { + this._formNode.submit(); + } else { + throwFormNodeError(); + } } reset() { - this._formNode.reset(); + if (this._formNode) { + this._formNode.reset(); + } else { + throwFormNodeError(); + } } __registerEventsForLionForm() { - this._submit = ev => { - ev.preventDefault(); - ev.stopPropagation(); - this.submitGroup(); - this.dispatchEvent(new Event('submit', { bubbles: true })); - }; this._formNode.addEventListener('submit', this._submit); - - this._reset = ev => { - ev.preventDefault(); - ev.stopPropagation(); - this.resetGroup(); - this.dispatchEvent(new Event('reset', { bubbles: true })); - }; this._formNode.addEventListener('reset', this._reset); } diff --git a/packages/form/test/lion-form.test.js b/packages/form/test/lion-form.test.js index f27214f94..1b20915d0 100644 --- a/packages/form/test/lion-form.test.js +++ b/packages/form/test/lion-form.test.js @@ -1,6 +1,6 @@ import { expect, - fixture, + fixture as _fixture, html, oneEvent, aTimeout, @@ -15,6 +15,13 @@ import '@lion/fieldset/lion-fieldset.js'; import '../lion-form.js'; +/** + + * @typedef {import('../src/LionForm').LionForm} LionForm + * @typedef {import('lit-html').TemplateResult} TemplateResult + */ +const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture); + const childTagString = defineCE( class extends LionField { get slots() { @@ -25,40 +32,38 @@ const childTagString = defineCE( }, ); const childTag = unsafeStatic(childTagString); -const formTagString = 'lion-form'; -const formTag = unsafeStatic(formTagString); describe('', () => { it('is an instance of LionFieldSet', async () => { const el = await fixture(html` - <${formTag}> - - - + +
+
`); expect(el).to.be.instanceOf(LionFieldset); }); it('relies on the native form for its accessible role', async () => { const el = await fixture(html` - <${formTag}> -
-
- + +
+
`); expect(el.getAttribute('role')).to.be.null; }); it('has a custom reset that gets triggered by native reset', async () => { const withDefaults = await fixture(html` - <${formTag}> +
<${childTag} name="firstName" .modelValue="${'Foo'}">
- +
`); - const resetButton = 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({ @@ -84,11 +89,11 @@ describe('', () => { it('dispatches reset events', async () => { const el = await fixture(html` - <${formTag}> +
<${childTag} name="firstName" .modelValue="${'Foo'}">
- +
`); setTimeout(() => el.reset()); @@ -103,27 +108,27 @@ describe('', () => { it('works with the native submit event (triggered via a button)', async () => { const submitSpy = spy(); const el = await fixture(html` - <${formTag} @submit=${submitSpy}> +
- +
`); - const button = el.querySelector('button'); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); button.click(); expect(submitSpy.callCount).to.equal(1); }); it('dispatches submit events', async () => { const el = await fixture(html` - <${formTag}> +
- +
`); - const button = el.querySelector('button'); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); setTimeout(() => button.click()); const submitEv = await oneEvent(el, 'submit'); expect(submitEv).to.be.instanceOf(Event); @@ -135,29 +140,29 @@ describe('', () => { it('handles internal submit handler before dispatch', async () => { const el = await fixture(html` - <${formTag}> +
- +
`); - const button = el.querySelector('button'); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); const internalHandlerSpy = spy(el, 'submitGroup'); const dispatchSpy = spy(el, 'dispatchEvent'); - await aTimeout(); + await aTimeout(0); button.click(); expect(internalHandlerSpy).to.be.calledBefore(dispatchSpy); }); it('handles internal submit handler before dispatch', async () => { const el = await fixture(html` - <${formTag}> +
- +
`); - const button = el.querySelector('button'); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); const internalHandlerSpy = spy(el, 'submitGroup'); const dispatchSpy = spy(el, 'dispatchEvent'); button.click(); @@ -167,13 +172,13 @@ describe('', () => { it('handles internal reset handler before dispatch', async () => { const el = await fixture(html` - <${formTag}> +
- +
`); - const button = el.querySelector('button'); + const button = /** @type {HTMLButtonElement} */ (el.querySelector('button')); const internalHandlerSpy = spy(el, 'resetGroup'); const dispatchSpy = spy(el, 'dispatchEvent'); button.click(); diff --git a/tsconfig.json b/tsconfig.json index eeaeb5502..fb65d1d3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,8 @@ "packages/button/src/**/*.js", "packages/checkbox-group/**/*.js", "packages/core/**/*.js", + "packages/fieldset/**/*.js", + "packages/form/**/*.js", "packages/form-core/**/*.js", "packages/input/**/*.js", "packages/input-amount/**/*.js",