From a3ac76f174a30a411fcb3a762778d77e40225a4c Mon Sep 17 00:00:00 2001 From: Joren Broekema Date: Wed, 4 Nov 2020 17:28:02 +0100 Subject: [PATCH] fix(icon): fix types for icon src and test --- .changeset/olive-news-suffer.md | 5 +++++ packages/icon/src/IconManager.js | 9 ++++++-- packages/icon/src/LionIcon.js | 28 ++++++++++++++++++------- packages/icon/src/icons.js | 1 + packages/icon/test/IconManager.test.js | 15 ++++++++++++- packages/icon/test/hammer.svg.js | 2 +- packages/icon/test/heart.svg.js | 2 +- packages/icon/test/lion-icon.test.js | 29 +++++++++++++++++--------- tsconfig.json | 1 - 9 files changed, 69 insertions(+), 23 deletions(-) create mode 100644 .changeset/olive-news-suffer.md diff --git a/.changeset/olive-news-suffer.md b/.changeset/olive-news-suffer.md new file mode 100644 index 000000000..2ec64fb52 --- /dev/null +++ b/.changeset/olive-news-suffer.md @@ -0,0 +1,5 @@ +--- +'@lion/icon': patch +--- + +Fix lion icon types diff --git a/packages/icon/src/IconManager.js b/packages/icon/src/IconManager.js index b3413c04c..8a495e6fb 100644 --- a/packages/icon/src/IconManager.js +++ b/packages/icon/src/IconManager.js @@ -1,3 +1,8 @@ +/** + * @typedef {import('lit-html').TemplateResult} TemplateResult + * @typedef {import('lit-html').nothing} nothing + */ + export class IconManager { constructor() { this.__iconResolvers = new Map(); @@ -9,7 +14,7 @@ export class IconManager { * icon as a TemplateResult. This function can be sync or async. * * @param {string} namespace - * @param {(iconset: string, icon: string) => TemplateResult | Promise} iconResolver + * @param {(iconset: string, icon: string) => TemplateResult | Promise | nothing | Promise } iconResolver */ addIconResolver(namespace, iconResolver) { if (this.__iconResolvers.has(namespace)) { @@ -54,6 +59,6 @@ export class IconManager { throw new Error(`Incorrect iconId: ${iconId}. Format: ::`); } - return this.resolveIcon(...splitIconId); + return this.resolveIcon(splitIconId[0], splitIconId[1], splitIconId[2]); } } diff --git a/packages/icon/src/LionIcon.js b/packages/icon/src/LionIcon.js index 101badef3..71ab595d4 100644 --- a/packages/icon/src/LionIcon.js +++ b/packages/icon/src/LionIcon.js @@ -1,12 +1,18 @@ import { css, html, LitElement, nothing, render, TemplateResult } from '@lion/core'; import { icons } from './icons.js'; +/** + * @param {?} wrappedSvgObject + */ function unwrapSvg(wrappedSvgObject) { const svgObject = wrappedSvgObject && wrappedSvgObject.default ? wrappedSvgObject.default : wrappedSvgObject; return typeof svgObject === 'function' ? svgObject(html) : svgObject; } +/** + * @param {TemplateResult|nothing} svg + */ function validateSvg(svg) { if (!(svg === nothing || svg instanceof TemplateResult)) { throw new Error( @@ -24,15 +30,13 @@ export class LionIcon extends LitElement { /** * @desc When icons are not loaded as part of an iconset defined on iconManager, * it's possible to directly load an svg. - * @type {TemplateResult|function} */ svg: { - type: Object, + attribute: false, }, /** * @desc The iconId allows to access icons that are registered to the IconManager * For instance, "lion:space:alienSpaceship" - * @type {string} */ ariaLabel: { type: String, @@ -42,7 +46,6 @@ export class LionIcon extends LitElement { /** * @desc The iconId allows to access icons that are registered to the IconManager * For instance, "lion:space:alienSpaceship" - * @type {string} */ iconId: { type: String, @@ -93,16 +96,20 @@ export class LionIcon extends LitElement { constructor() { super(); this.role = 'img'; + this.ariaLabel = ''; + this.iconId = ''; + this.__svg = nothing; } + /** @param {import('lit-element').PropertyValues} changedProperties */ update(changedProperties) { super.update(changedProperties); if (changedProperties.has('ariaLabel')) { - this._onLabelChanged(changedProperties); + this._onLabelChanged(); } if (changedProperties.has('iconId')) { - this._onIconIdChanged(changedProperties.get('iconId')); + this._onIconIdChanged(/** @type {string} */ (changedProperties.get('iconId'))); } } @@ -119,6 +126,7 @@ export class LionIcon extends LitElement { /** * On IE11, svgs without focusable false appear in the tab order * so make sure to have in svg files + * @param {TemplateResult|nothing} svg */ set svg(svg) { this.__svg = svg; @@ -142,6 +150,9 @@ export class LionIcon extends LitElement { } } + /** + * @param {TemplateResult | nothing} svgObject + */ _renderSvg(svgObject) { validateSvg(svgObject); render(svgObject, this); @@ -150,11 +161,14 @@ export class LionIcon extends LitElement { } } + /** + * @param {string} prevIconId + */ async _onIconIdChanged(prevIconId) { if (!this.iconId) { // clear if switching from iconId to no iconId if (prevIconId) { - this.svg = null; + this.svg = nothing; } } else { const iconIdBeforeResolve = this.iconId; diff --git a/packages/icon/src/icons.js b/packages/icon/src/icons.js index c1b42554e..2af5c3da6 100644 --- a/packages/icon/src/icons.js +++ b/packages/icon/src/icons.js @@ -4,6 +4,7 @@ import { IconManager } from './IconManager.js'; // eslint-disable-next-line import/no-mutable-exports export let icons = singletonManager.get('@lion/icon::icons::0.5.x') || new IconManager(); +// @ts-ignore since we don't know which singleton icon manager version will be used, we cannot type it. export function setIcons(newIcons) { icons = newIcons; } diff --git a/packages/icon/test/IconManager.test.js b/packages/icon/test/IconManager.test.js index 09c6ee1e1..b51128c31 100644 --- a/packages/icon/test/IconManager.test.js +++ b/packages/icon/test/IconManager.test.js @@ -1,7 +1,12 @@ +import { nothing } from '@lion/core'; import { expect } from '@open-wc/testing'; import { stub } from 'sinon'; import { IconManager } from '../src/IconManager.js'; +/** + * @typedef {import("lit-html").TemplateResult} TemplateResult + */ + describe('IconManager', () => { it('starts off with an empty map of resolvers', () => { const manager = new IconManager(); @@ -10,7 +15,15 @@ describe('IconManager', () => { it('allows adding an icon resolver', () => { const manager = new IconManager(); - const resolver = () => {}; + /** + * @param {string} iconset + * @param {string} icon + * @return {TemplateResult | Promise | nothing | Promise} + */ + // eslint-disable-next-line no-unused-vars + const resolver = (iconset, icon) => { + return nothing; + }; manager.addIconResolver('foo', resolver); expect(manager.__iconResolvers.get('foo')).to.equal(resolver); diff --git a/packages/icon/test/hammer.svg.js b/packages/icon/test/hammer.svg.js index 1d909b91e..3e743b776 100644 --- a/packages/icon/test/hammer.svg.js +++ b/packages/icon/test/hammer.svg.js @@ -1,2 +1,2 @@ -export default tag => +export default /** @param {(strings: TemplateStringsArray, ... expr: string[]) => string} tag */ tag => tag``; diff --git a/packages/icon/test/heart.svg.js b/packages/icon/test/heart.svg.js index 0272044f0..afe55340f 100644 --- a/packages/icon/test/heart.svg.js +++ b/packages/icon/test/heart.svg.js @@ -1,2 +1,2 @@ -export default tag => +export default /** @param {(strings: TemplateStringsArray, ... expr: string[]) => string} tag */ tag => tag``; diff --git a/packages/icon/test/lion-icon.test.js b/packages/icon/test/lion-icon.test.js index bf1c80a1e..2c77ac274 100644 --- a/packages/icon/test/lion-icon.test.js +++ b/packages/icon/test/lion-icon.test.js @@ -1,19 +1,28 @@ -import { until } from '@lion/core'; -import { aTimeout, expect, fixture, fixtureSync, html } from '@open-wc/testing'; +import { nothing, until } from '@lion/core'; +import { aTimeout, expect, fixture as _fixture, fixtureSync, html } from '@open-wc/testing'; import '../lion-icon.js'; import { icons } from '../src/icons.js'; import hammerSvg from './hammer.svg.js'; import heartSvg from './heart.svg.js'; +/** + * @typedef {(strings: TemplateStringsArray, ... expr: string[]) => string} TaggedTemplateLiteral + * @typedef {import('../src/LionIcon').LionIcon} LionIcon + * @typedef {import('lit-html').TemplateResult} TemplateResult + */ +const fixture = /** @type {(arg: TemplateResult|string) => Promise} */ (_fixture); + describe('lion-icon', () => { it('supports svg icon as a function which recieves a tag function as an argument and returns a tagged template literal', async () => { - const iconFunction = tag => tag``; + const iconFunction = /** @param {TaggedTemplateLiteral} tag */ tag => + tag``; const el = await fixture(html``); expect(el.children[0].getAttribute('data-test-id')).to.equal('svg'); }); it('is hidden when attribute hidden is true', async () => { - const iconFunction = tag => tag``; + const iconFunction = /** @param {TaggedTemplateLiteral} tag */ tag => + tag``; const el = await fixture(html``); expect(el).not.to.be.displayed; }); @@ -126,7 +135,7 @@ describe('lion-icon', () => { await svgLoading; // We need to await the until directive is resolved and rendered to the dom // You can not use updateComplete as until renders on it's own - await aTimeout(); + await aTimeout(0); expect(el.children[0].getAttribute('data-test-id')).to.equal('svg-heart'); }); @@ -134,7 +143,7 @@ describe('lion-icon', () => { it('does not render "undefined" if changed from valid input to undefined', async () => { const el = await fixture(html``); await el.updateComplete; - el.svg = undefined; + el.svg = nothing; await el.updateComplete; expect(el.innerHTML).to.equal(''); // don't use lightDom.to.equal(''), it gives false positives }); @@ -142,7 +151,7 @@ describe('lion-icon', () => { it('does not render "null" if changed from valid input to null', async () => { const el = await fixture(html``); await el.updateComplete; - el.svg = null; + el.svg = nothing; await el.updateComplete; expect(el.innerHTML).to.equal(''); // don't use lightDom.to.equal(''), it gives false positives }); @@ -152,7 +161,7 @@ describe('lion-icon', () => { icons.addIconResolver('foo', () => heartSvg); const el = await fixture(html``); - expect(el.children[0].dataset.testId).to.equal('svg-heart'); + expect(/** @type {HTMLElement} */ (el.children[0]).dataset.testId).to.equal('svg-heart'); } finally { icons.removeIconResolver('foo'); } @@ -189,10 +198,10 @@ describe('lion-icon', () => { await aTimeout(4); // heart is still loading at this point, but hammer came later so that should be later - expect(el.children[0].dataset.testId).to.equal('svg-hammer'); + expect(/** @type {HTMLElement} */ (el.children[0]).dataset.testId).to.equal('svg-hammer'); await aTimeout(10); // heart finished loading, but it should not be rendered because hammer came later - expect(el.children[0].dataset.testId).to.equal('svg-hammer'); + expect(/** @type {HTMLElement} */ (el.children[0]).dataset.testId).to.equal('svg-hammer'); } finally { icons.removeIconResolver('foo'); icons.removeIconResolver('bar'); diff --git a/tsconfig.json b/tsconfig.json index 27ab3eb83..e186db970 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,6 @@ "packages/babel-plugin-extend-docs/**/*.js", "packages/providence-analytics/**/*.js", "packages/remark-extend/**/*.js", - "packages/icon/**/*.js", // FIXME: Needs to get typed! "packages/select-rich/test/**/*.js", // TODO: Needs to get typed! "packages/overlays/test/utils-tests/**/*.js", // TODO: Needs to get typed! "packages/helpers/**/*.js", // FIXME: Needs to get typed!