From 06123918d072f4753aaee8544fe8404502467acf Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Wed, 30 Sep 2020 17:45:27 +0200 Subject: [PATCH] feat(switch): add types --- .changeset/popular-pianos-warn.md | 5 ++++ packages/switch/src/LionSwitch.js | 20 ++++++++++++- packages/switch/src/LionSwitchButton.js | 9 ++++++ .../switch/test/lion-switch-button.test.js | 28 ++++++++++++------- packages/switch/test/lion-switch.test.js | 17 +++++++---- 5 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 .changeset/popular-pianos-warn.md diff --git a/.changeset/popular-pianos-warn.md b/.changeset/popular-pianos-warn.md new file mode 100644 index 000000000..b803bf8e3 --- /dev/null +++ b/.changeset/popular-pianos-warn.md @@ -0,0 +1,5 @@ +--- +'@lion/switch': minor +--- + +Add types for switch package. diff --git a/packages/switch/src/LionSwitch.js b/packages/switch/src/LionSwitch.js index d2d778561..bb3353194 100644 --- a/packages/switch/src/LionSwitch.js +++ b/packages/switch/src/LionSwitch.js @@ -2,6 +2,7 @@ import { css, html, ScopedElementsMixin } from '@lion/core'; import { ChoiceInputMixin, LionField } from '@lion/form-core'; import { LionSwitchButton } from './LionSwitchButton.js'; +// @ts-expect-error https://github.com/microsoft/TypeScript/issues/40110 export class LionSwitch extends ScopedElementsMixin(ChoiceInputMixin(LionField)) { static get styles() { return [ @@ -25,10 +26,26 @@ export class LionSwitch extends ScopedElementsMixin(ChoiceInputMixin(LionField)) }; } + /** + * Input node here is the lion-switch-button, which is not compatible with LionField _inputNode --> HTMLInputElement + * Therefore we do a full override and typecast to an intersection type that includes LionSwitchButton + * @returns {HTMLInputElement & LionSwitchButton} + */ + get _inputNode() { + return /** @type {HTMLInputElement & LionSwitchButton} */ (Array.from(this.children).find( + el => el.slot === 'input', + )); + } + get slots() { return { ...super.slots, - input: () => document.createElement(this.constructor.getScopedTagName('lion-switch-button')), + input: () => + document.createElement( + /** @type {typeof LionSwitch} */ (this.constructor).getScopedTagName( + 'lion-switch-button', + ), + ), }; } @@ -82,6 +99,7 @@ export class LionSwitch extends ScopedElementsMixin(ChoiceInputMixin(LionField)) } } + /** @param {import('lit-element').PropertyValues } changedProperties */ updated(changedProperties) { super.updated(changedProperties); this._syncButtonSwitch(); diff --git a/packages/switch/src/LionSwitchButton.js b/packages/switch/src/LionSwitchButton.js index 3c89ed260..ee6b73c68 100644 --- a/packages/switch/src/LionSwitchButton.js +++ b/packages/switch/src/LionSwitchButton.js @@ -113,6 +113,9 @@ export class LionSwitchButton extends DisabledWithTabIndexMixin(LitElement) { this.setAttribute('aria-checked', `${this.checked}`); } + /** + * @param {KeyboardEvent} e + */ // eslint-disable-next-line class-methods-use-this __handleKeydown(e) { // prevent "space" scrolling on "macOS" @@ -121,12 +124,16 @@ export class LionSwitchButton extends DisabledWithTabIndexMixin(LitElement) { } } + /** + * @param {KeyboardEvent} e + */ __handleKeyup(e) { if ([32 /* space */, 13 /* enter */].indexOf(e.keyCode) !== -1) { this.__toggleChecked(); } } + /** @param {import('lit-element').PropertyValues } changedProperties */ updated(changedProperties) { if (changedProperties.has('disabled')) { this.setAttribute('aria-disabled', `${this.disabled}`); // create mixin if we need it in more places @@ -136,6 +143,8 @@ export class LionSwitchButton extends DisabledWithTabIndexMixin(LitElement) { /** * We synchronously update aria-checked to support voice over on safari. * + * @param {PropertyKey} name + * @param {?} oldValue * @override */ requestUpdateInternal(name, oldValue) { diff --git a/packages/switch/test/lion-switch-button.test.js b/packages/switch/test/lion-switch-button.test.js index 859a39ec8..0f44fe584 100644 --- a/packages/switch/test/lion-switch-button.test.js +++ b/packages/switch/test/lion-switch-button.test.js @@ -1,8 +1,16 @@ -import { expect, fixture, html } from '@open-wc/testing'; +import { expect, fixture as _fixture, html } from '@open-wc/testing'; import sinon from 'sinon'; import '../lion-switch-button.js'; +/** + * @typedef {import('../src/LionSwitchButton').LionSwitchButton} LionSwitchButton + * @typedef {import('lit-html').TemplateResult} TemplateResult + */ + +const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture); + describe('lion-switch-button', () => { + /** @type {LionSwitchButton} */ let el; beforeEach(async () => { el = await fixture(html``); @@ -62,15 +70,15 @@ describe('lion-switch-button', () => { el.click(); el.click(); expect(handlerSpy.callCount).to.equal(2); - const checkCall = call => { - expect(call.args).to.have.a.lengthOf(1); + const checkCall = /** @param {import('sinon').SinonSpyCall} call */ call => { + expect(call.args).to.have.lengthOf(1); const e = call.args[0]; expect(e).to.be.an.instanceof(Event); expect(e.bubbles).to.be.true; expect(e.composed).to.be.true; }; - checkCall(handlerSpy.getCall(0), true); - checkCall(handlerSpy.getCall(1), false); + checkCall(handlerSpy.getCall(0)); + checkCall(handlerSpy.getCall(1)); }); it('should dispatch "checked-changed" event when checked changed', () => { @@ -79,15 +87,15 @@ describe('lion-switch-button', () => { el.checked = true; el.checked = false; expect(handlerSpy.callCount).to.equal(2); - const checkCall = call => { - expect(call.args).to.have.a.lengthOf(1); + const checkCall = /** @param {import('sinon').SinonSpyCall} call */ call => { + expect(call.args).to.have.lengthOf(1); const e = call.args[0]; expect(e).to.be.an.instanceof(Event); expect(e.bubbles).to.be.true; expect(e.composed).to.be.true; }; - checkCall(handlerSpy.getCall(0), true); - checkCall(handlerSpy.getCall(1), false); + checkCall(handlerSpy.getCall(0)); + checkCall(handlerSpy.getCall(1)); }); it('should not dispatch "checked-changed" event if disabled', () => { @@ -117,7 +125,7 @@ describe('lion-switch-button', () => { await el.updateComplete; expect(el.getAttribute('aria-checked')).to.equal('false'); - el.setAttribute('checked', true); + el.setAttribute('checked', ''); await el.updateComplete; expect(el.getAttribute('aria-checked')).to.equal('true'); el.removeAttribute('checked'); diff --git a/packages/switch/test/lion-switch.test.js b/packages/switch/test/lion-switch.test.js index c11156ced..68b38b5aa 100644 --- a/packages/switch/test/lion-switch.test.js +++ b/packages/switch/test/lion-switch.test.js @@ -1,7 +1,14 @@ -import { expect, fixture, html } from '@open-wc/testing'; +import { expect, fixture as _fixture, html } from '@open-wc/testing'; import sinon from 'sinon'; import '../lion-switch.js'; +/** + * @typedef {import('../src/LionSwitch').LionSwitch} LionSwitch + * @typedef {import('lit-html').TemplateResult} TemplateResult + */ + +const fixture = /** @type {(arg: TemplateResult) => Promise} */ (_fixture); + describe('lion-switch', () => { it('should have default "input" element', async () => { const el = await fixture(html``); @@ -83,15 +90,15 @@ describe('lion-switch', () => { el._labelNode.click(); await el.updateComplete; expect(handlerSpy.callCount).to.equal(2); - const checkCall = call => { - expect(call.args).to.have.a.lengthOf(1); + const checkCall = /** @param {import('sinon').SinonSpyCall} call */ call => { + expect(call.args).to.have.lengthOf(1); const e = call.args[0]; expect(e).to.be.an.instanceof(Event); expect(e.bubbles).to.be.true; expect(e.composed).to.be.true; }; - checkCall(handlerSpy.getCall(0), true); - checkCall(handlerSpy.getCall(1), false); + checkCall(handlerSpy.getCall(0)); + checkCall(handlerSpy.getCall(1)); }); it('should dispatch "checked-changed" event when checked changed', async () => {