From 3aebd45703138320bbe146f52a624126b29b0cdf Mon Sep 17 00:00:00 2001 From: Serhii Kulykov Date: Thu, 28 Sep 2023 13:51:28 +0300 Subject: [PATCH] experiment: add LitElement based version of vaadin-tooltip (#6536) --- packages/tooltip/package.json | 3 + .../tooltip/src/vaadin-lit-tooltip-overlay.js | 45 +++++ packages/tooltip/src/vaadin-lit-tooltip.d.ts | 6 + packages/tooltip/src/vaadin-lit-tooltip.js | 78 +++++++++ packages/tooltip/src/vaadin-tooltip-mixin.js | 4 + packages/tooltip/test/position-styles.js | 11 ++ packages/tooltip/test/tooltip-lit.test.js | 3 + .../tooltip/test/tooltip-offset-lit.test.js | 3 + .../test/tooltip-offset-polymer.test.js | 3 + ...ffset.test.js => tooltip-offset.common.js} | 4 +- packages/tooltip/test/tooltip-polymer.test.js | 3 + .../tooltip/test/tooltip-position-lit.test.js | 4 + .../test/tooltip-position-polymer.test.js | 4 + ...ion.test.js => tooltip-position.common.js} | 16 +- .../tooltip/test/tooltip-timers-lit.test.js | 3 + .../test/tooltip-timers-polymer.test.js | 3 + ...imers.test.js => tooltip-timers.common.js} | 104 +++++------ .../{tooltip.test.js => tooltip.common.js} | 162 +++++++++++++----- 18 files changed, 349 insertions(+), 110 deletions(-) create mode 100644 packages/tooltip/src/vaadin-lit-tooltip-overlay.js create mode 100644 packages/tooltip/src/vaadin-lit-tooltip.d.ts create mode 100644 packages/tooltip/src/vaadin-lit-tooltip.js create mode 100644 packages/tooltip/test/position-styles.js create mode 100644 packages/tooltip/test/tooltip-lit.test.js create mode 100644 packages/tooltip/test/tooltip-offset-lit.test.js create mode 100644 packages/tooltip/test/tooltip-offset-polymer.test.js rename packages/tooltip/test/{tooltip-offset.test.js => tooltip-offset.common.js} (98%) create mode 100644 packages/tooltip/test/tooltip-polymer.test.js create mode 100644 packages/tooltip/test/tooltip-position-lit.test.js create mode 100644 packages/tooltip/test/tooltip-position-polymer.test.js rename packages/tooltip/test/{tooltip-position.test.js => tooltip-position.common.js} (97%) create mode 100644 packages/tooltip/test/tooltip-timers-lit.test.js create mode 100644 packages/tooltip/test/tooltip-timers-polymer.test.js rename packages/tooltip/test/{tooltip-timers.test.js => tooltip-timers.common.js} (89%) rename packages/tooltip/test/{tooltip.test.js => tooltip.common.js} (87%) diff --git a/packages/tooltip/package.json b/packages/tooltip/package.json index 4daed49756..e137c5dbda 100644 --- a/packages/tooltip/package.json +++ b/packages/tooltip/package.json @@ -21,6 +21,9 @@ "type": "module", "files": [ "src", + "!src/vaadin-lit-tooltip-overlay.js", + "!src/vaadin-lit-tooltip.d.ts", + "!src/vaadin-lit-tooltip.js", "theme", "vaadin-*.d.ts", "vaadin-*.js", diff --git a/packages/tooltip/src/vaadin-lit-tooltip-overlay.js b/packages/tooltip/src/vaadin-lit-tooltip-overlay.js new file mode 100644 index 0000000000..b2f42d9444 --- /dev/null +++ b/packages/tooltip/src/vaadin-lit-tooltip-overlay.js @@ -0,0 +1,45 @@ +/** + * @license + * Copyright (c) 2022 - 2023 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { html, LitElement } from 'lit'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { overlayStyles } from '@vaadin/overlay/src/vaadin-overlay-styles.js'; +import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; +import { TooltipOverlayMixin } from './vaadin-tooltip-overlay-mixin.js'; +import { tooltipOverlayStyles } from './vaadin-tooltip-overlay-styles.js'; + +/** + * An element used internally by ``. Not intended to be used separately. + * + * @customElement + * @extends HTMLElement + * @mixes DirMixin + * @mixes ThemableMixin + * @mixes TooltipOverlayMixin + * @private + */ +class TooltipOverlay extends TooltipOverlayMixin(DirMixin(ThemableMixin(PolylitMixin(LitElement)))) { + static get is() { + return 'vaadin-tooltip-overlay'; + } + + static get styles() { + return [overlayStyles, tooltipOverlayStyles]; + } + + /** @protected */ + render() { + return html` + +
+
+
+ `; + } +} + +defineCustomElement(TooltipOverlay); diff --git a/packages/tooltip/src/vaadin-lit-tooltip.d.ts b/packages/tooltip/src/vaadin-lit-tooltip.d.ts new file mode 100644 index 0000000000..f7114316dc --- /dev/null +++ b/packages/tooltip/src/vaadin-lit-tooltip.d.ts @@ -0,0 +1,6 @@ +/** + * @license + * Copyright (c) 2022 - 2023 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +export * from './vaadin-tooltip.js'; diff --git a/packages/tooltip/src/vaadin-lit-tooltip.js b/packages/tooltip/src/vaadin-lit-tooltip.js new file mode 100644 index 0000000000..89ff06b030 --- /dev/null +++ b/packages/tooltip/src/vaadin-lit-tooltip.js @@ -0,0 +1,78 @@ +/** + * @license + * Copyright (c) 2022 - 2023 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import './vaadin-lit-tooltip-overlay.js'; +import { css, html, LitElement } from 'lit'; +import { ifDefined } from 'lit/directives/if-defined.js'; +import { defineCustomElement } from '@vaadin/component-base/src/define.js'; +import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { ThemePropertyMixin } from '@vaadin/vaadin-themable-mixin/vaadin-theme-property-mixin.js'; +import { TooltipMixin } from './vaadin-tooltip-mixin.js'; + +/** + * LitElement based version of `` web component. + * + * ## Disclaimer + * + * This component is an experiment not intended for publishing to npm. + * There is no ETA regarding specific Vaadin version where it'll land. + * Feel free to try this code in your apps as per Apache 2.0 license. + * + * @customElement + * @extends HTMLElement + * @mixes ElementMixin + * @mixes ThemePropertyMixin + * @mixes TooltipMixin + */ +class Tooltip extends TooltipMixin(ThemePropertyMixin(ElementMixin(PolylitMixin(LitElement)))) { + static get is() { + return 'vaadin-tooltip'; + } + + static get styles() { + return css` + :host { + display: none; + } + `; + } + + /** @protected */ + render() { + const effectivePosition = this.__effectivePosition; + + return html` + + + + `; + } + + /** @protected */ + ready() { + super.ready(); + + this._overlayElement = this.shadowRoot.querySelector('vaadin-tooltip-overlay'); + } +} + +defineCustomElement(Tooltip); + +export { Tooltip }; diff --git a/packages/tooltip/src/vaadin-tooltip-mixin.js b/packages/tooltip/src/vaadin-tooltip-mixin.js index ee4e21bf45..898af8fd1b 100644 --- a/packages/tooltip/src/vaadin-tooltip-mixin.js +++ b/packages/tooltip/src/vaadin-tooltip-mixin.js @@ -297,6 +297,7 @@ export const TooltipMixin = (superClass) => manual: { type: Boolean, value: false, + sync: true, }, /** @@ -306,6 +307,7 @@ export const TooltipMixin = (superClass) => opened: { type: Boolean, value: false, + sync: true, }, /** @@ -358,6 +360,7 @@ export const TooltipMixin = (superClass) => _autoOpened: { type: Boolean, observer: '__autoOpenedChanged', + sync: true, }, /** @@ -386,6 +389,7 @@ export const TooltipMixin = (superClass) => /** @private */ _isConnected: { type: Boolean, + sync: true, }, /** diff --git a/packages/tooltip/test/position-styles.js b/packages/tooltip/test/position-styles.js new file mode 100644 index 0000000000..067af89a6d --- /dev/null +++ b/packages/tooltip/test/position-styles.js @@ -0,0 +1,11 @@ +import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; + +registerStyles( + 'vaadin-tooltip-overlay', + css` + [part='overlay'] { + width: 50px; + border: 1px solid blue; + } + `, +); diff --git a/packages/tooltip/test/tooltip-lit.test.js b/packages/tooltip/test/tooltip-lit.test.js new file mode 100644 index 0000000000..7252478db6 --- /dev/null +++ b/packages/tooltip/test/tooltip-lit.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-lit-tooltip.js'; +import './tooltip.common.js'; diff --git a/packages/tooltip/test/tooltip-offset-lit.test.js b/packages/tooltip/test/tooltip-offset-lit.test.js new file mode 100644 index 0000000000..76b7e14a34 --- /dev/null +++ b/packages/tooltip/test/tooltip-offset-lit.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-lit-tooltip.js'; +import './tooltip-offset.common.js'; diff --git a/packages/tooltip/test/tooltip-offset-polymer.test.js b/packages/tooltip/test/tooltip-offset-polymer.test.js new file mode 100644 index 0000000000..0ff1a72fbf --- /dev/null +++ b/packages/tooltip/test/tooltip-offset-polymer.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-tooltip.js'; +import './tooltip-offset.common.js'; diff --git a/packages/tooltip/test/tooltip-offset.test.js b/packages/tooltip/test/tooltip-offset.common.js similarity index 98% rename from packages/tooltip/test/tooltip-offset.test.js rename to packages/tooltip/test/tooltip-offset.common.js index ef72e3e728..ce5778965b 100644 --- a/packages/tooltip/test/tooltip-offset.test.js +++ b/packages/tooltip/test/tooltip-offset.common.js @@ -1,10 +1,11 @@ import { expect } from '@esm-bundle/chai'; import { fire, fixtureSync, nextRender, nextUpdate, oneEvent } from '@vaadin/testing-helpers'; -import { Tooltip } from '../src/vaadin-tooltip.js'; describe('offset', () => { let tooltip, target, overlay; + const Tooltip = customElements.get('vaadin-tooltip'); + before(() => { Tooltip.setDefaultFocusDelay(0); Tooltip.setDefaultHoverDelay(0); @@ -13,6 +14,7 @@ describe('offset', () => { beforeEach(async () => { tooltip = fixtureSync(''); + await nextRender(); target = fixtureSync('
'); tooltip.target = target; await nextRender(); diff --git a/packages/tooltip/test/tooltip-polymer.test.js b/packages/tooltip/test/tooltip-polymer.test.js new file mode 100644 index 0000000000..1bd8daf8dc --- /dev/null +++ b/packages/tooltip/test/tooltip-polymer.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-tooltip.js'; +import './tooltip.common.js'; diff --git a/packages/tooltip/test/tooltip-position-lit.test.js b/packages/tooltip/test/tooltip-position-lit.test.js new file mode 100644 index 0000000000..cb9964e06c --- /dev/null +++ b/packages/tooltip/test/tooltip-position-lit.test.js @@ -0,0 +1,4 @@ +import './not-animated-styles.js'; +import './position-styles.js'; +import '../src/vaadin-lit-tooltip.js'; +import './tooltip-position.common.js'; diff --git a/packages/tooltip/test/tooltip-position-polymer.test.js b/packages/tooltip/test/tooltip-position-polymer.test.js new file mode 100644 index 0000000000..1be89a5e76 --- /dev/null +++ b/packages/tooltip/test/tooltip-position-polymer.test.js @@ -0,0 +1,4 @@ +import './not-animated-styles.js'; +import './position-styles.js'; +import '../src/vaadin-tooltip.js'; +import './tooltip-position.common.js'; diff --git a/packages/tooltip/test/tooltip-position.test.js b/packages/tooltip/test/tooltip-position.common.js similarity index 97% rename from packages/tooltip/test/tooltip-position.test.js rename to packages/tooltip/test/tooltip-position.common.js index 17a7da7490..ba5d880c84 100644 --- a/packages/tooltip/test/tooltip-position.test.js +++ b/packages/tooltip/test/tooltip-position.common.js @@ -1,21 +1,11 @@ import { expect } from '@esm-bundle/chai'; import { fire, fixtureSync, nextRender, nextUpdate, oneEvent } from '@vaadin/testing-helpers'; -import { css, registerStyles } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; -import { Tooltip } from '../src/vaadin-tooltip.js'; - -registerStyles( - 'vaadin-tooltip-overlay', - css` - [part='overlay'] { - width: 50px; - border: 1px solid blue; - } - `, -); describe('position', () => { let tooltip, target, overlay; + const Tooltip = customElements.get('vaadin-tooltip'); + before(() => { Tooltip.setDefaultFocusDelay(0); Tooltip.setDefaultHoverDelay(0); @@ -24,9 +14,9 @@ describe('position', () => { beforeEach(async () => { tooltip = fixtureSync(''); + await nextRender(); target = fixtureSync('
'); tooltip.target = target; - await nextRender(); overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); }); diff --git a/packages/tooltip/test/tooltip-timers-lit.test.js b/packages/tooltip/test/tooltip-timers-lit.test.js new file mode 100644 index 0000000000..50cb5005e0 --- /dev/null +++ b/packages/tooltip/test/tooltip-timers-lit.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-lit-tooltip.js'; +import './tooltip-timers.common.js'; diff --git a/packages/tooltip/test/tooltip-timers-polymer.test.js b/packages/tooltip/test/tooltip-timers-polymer.test.js new file mode 100644 index 0000000000..b8c2815b54 --- /dev/null +++ b/packages/tooltip/test/tooltip-timers-polymer.test.js @@ -0,0 +1,3 @@ +import './not-animated-styles.js'; +import '../src/vaadin-tooltip.js'; +import './tooltip-timers.common.js'; diff --git a/packages/tooltip/test/tooltip-timers.test.js b/packages/tooltip/test/tooltip-timers.common.js similarity index 89% rename from packages/tooltip/test/tooltip-timers.test.js rename to packages/tooltip/test/tooltip-timers.common.js index 9c86e61392..18436979fd 100644 --- a/packages/tooltip/test/tooltip-timers.test.js +++ b/packages/tooltip/test/tooltip-timers.common.js @@ -7,15 +7,18 @@ import { focusout, mousedown, nextRender, + nextUpdate, tabKeyDown, } from '@vaadin/testing-helpers'; import sinon from 'sinon'; -import './not-animated-styles.js'; import { resetGlobalTooltipState } from '../src/vaadin-tooltip-mixin.js'; -import { Tooltip } from '../vaadin-tooltip.js'; import { mouseenter, mouseleave } from './helpers.js'; describe('timers', () => { + let clock; + + const Tooltip = customElements.get('vaadin-tooltip'); + // Used as a fallback delay const DEFAULT_DELAY = 500; @@ -25,9 +28,18 @@ describe('timers', () => { Tooltip.setDefaultHideDelay(0); }); - function createTooltip(target) { + async function createTooltip(target) { const tooltip = fixtureSync(''); tooltip.target = target; + + // We use fake timers in reset tests, so native timers won't work. + // Trigger a timeout to ensure LitElement tooltip initial render. + if (clock) { + await clock.tickAsync(1); + } else { + await nextUpdate(tooltip); + } + return tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); } @@ -38,8 +50,8 @@ describe('timers', () => { tooltip = fixtureSync(''); target = fixtureSync(''); tooltip.target = target; - overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); await nextRender(); + overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); }); afterEach(() => { @@ -68,8 +80,8 @@ describe('timers', () => { tooltip = fixtureSync(''); target = fixtureSync(''); tooltip.target = target; - overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); await nextRender(); + overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); }); afterEach(() => { @@ -98,8 +110,8 @@ describe('timers', () => { tooltip = fixtureSync(''); target = fixtureSync(''); tooltip.target = target; - overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); await nextRender(); + overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); }); afterEach(() => { @@ -164,7 +176,7 @@ describe('timers', () => { it('should change default delay for newly created tooltip', async () => { Tooltip.setDefaultHoverDelay(2); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); await aTimeout(2); @@ -173,7 +185,7 @@ describe('timers', () => { }); it('should change default hover delay for existing tooltip', async () => { - overlay = createTooltip(target); + overlay = await createTooltip(target); Tooltip.setDefaultHoverDelay(2); @@ -184,8 +196,6 @@ describe('timers', () => { }); describe('reset hover delay', () => { - let clock; - beforeEach(() => { clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, @@ -201,35 +211,35 @@ describe('timers', () => { clock.restore(); }); - it('should reset hover delay when providing a negative number', () => { + it('should reset hover delay when providing a negative number', async () => { Tooltip.setDefaultHoverDelay(-1); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); - it('should reset hover delay when providing null instead of number', () => { + it('should reset hover delay when providing null instead of number', async () => { Tooltip.setDefaultHoverDelay(null); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); - it('should reset hover delay when providing undefined instead of number', () => { + it('should reset hover delay when providing undefined instead of number', async () => { Tooltip.setDefaultHoverDelay(undefined); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); @@ -250,7 +260,7 @@ describe('timers', () => { it('should change default delay for newly created tooltip', async () => { Tooltip.setDefaultFocusDelay(2); - overlay = createTooltip(target); + overlay = await createTooltip(target); tabKeyDown(document.body); focusin(target); @@ -260,7 +270,7 @@ describe('timers', () => { }); it('should change default focus delay for existing tooltip', async () => { - overlay = createTooltip(target); + overlay = await createTooltip(target); Tooltip.setDefaultFocusDelay(2); @@ -272,8 +282,6 @@ describe('timers', () => { }); describe('reset focus delay', () => { - let clock; - beforeEach(() => { clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, @@ -289,38 +297,38 @@ describe('timers', () => { clock.restore(); }); - it('should reset focus delay when providing a negative number', () => { + it('should reset focus delay when providing a negative number', async () => { Tooltip.setDefaultFocusDelay(-1); - overlay = createTooltip(target); + overlay = await createTooltip(target); tabKeyDown(document.body); focusin(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); - it('should reset focus delay when providing null instead of number', () => { + it('should reset focus delay when providing null instead of number', async () => { Tooltip.setDefaultFocusDelay(null); - overlay = createTooltip(target); + overlay = await createTooltip(target); tabKeyDown(document.body); focusin(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); - it('should reset focus delay when providing undefined instead of number', () => { + it('should reset focus delay when providing undefined instead of number', async () => { Tooltip.setDefaultFocusDelay(undefined); - overlay = createTooltip(target); + overlay = await createTooltip(target); tabKeyDown(document.body); focusin(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.true; }); @@ -341,7 +349,7 @@ describe('timers', () => { it('should change default hide delay for newly created tooltip', async () => { Tooltip.setDefaultHideDelay(2); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); @@ -352,7 +360,7 @@ describe('timers', () => { }); it('should change default hide delay for existing tooltip', async () => { - overlay = createTooltip(target); + overlay = await createTooltip(target); Tooltip.setDefaultHideDelay(2); @@ -365,8 +373,6 @@ describe('timers', () => { }); describe('reset hide delay', () => { - let clock; - beforeEach(() => { clock = sinon.useFakeTimers({ shouldClearNativeTimers: true, @@ -377,44 +383,44 @@ describe('timers', () => { clock.restore(); }); - it('should reset hide delay when providing a negative number', () => { + it('should reset hide delay when providing a negative number', async () => { Tooltip.setDefaultHideDelay(-1); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); mouseleave(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.false; }); - it('should reset hide delay when providing null instead of number', () => { + it('should reset hide delay when providing null instead of number', async () => { Tooltip.setDefaultHideDelay(null); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); mouseleave(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.false; }); - it('should reset hide delay when providing undefined instead of number', () => { + it('should reset hide delay when providing undefined instead of number', async () => { Tooltip.setDefaultHideDelay(undefined); - overlay = createTooltip(target); + overlay = await createTooltip(target); mouseenter(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); mouseleave(target); - clock.tick(DEFAULT_DELAY); + await clock.tickAsync(DEFAULT_DELAY); expect(overlay.opened).to.be.false; }); @@ -435,8 +441,8 @@ describe('timers', () => { `); tooltips = Array.from(wrapper.querySelectorAll('vaadin-tooltip')); targets = wrapper.querySelectorAll('input'); - overlays = tooltips.map((el) => el.shadowRoot.querySelector('vaadin-tooltip-overlay')); await nextRender(); + overlays = tooltips.map((el) => el.shadowRoot.querySelector('vaadin-tooltip-overlay')); }); afterEach(() => { diff --git a/packages/tooltip/test/tooltip.test.js b/packages/tooltip/test/tooltip.common.js similarity index 87% rename from packages/tooltip/test/tooltip.test.js rename to packages/tooltip/test/tooltip.common.js index 6af14edbfc..b9b5e83785 100644 --- a/packages/tooltip/test/tooltip.test.js +++ b/packages/tooltip/test/tooltip.common.js @@ -8,16 +8,17 @@ import { mousedown, nextFrame, nextRender, + nextUpdate, tabKeyDown, } from '@vaadin/testing-helpers'; import sinon from 'sinon'; import '@vaadin/overlay/vaadin-overlay.js'; -import './not-animated-styles.js'; import { html, render } from 'lit'; -import { Tooltip } from '../vaadin-tooltip.js'; import { mouseenter, mouseleave, waitForIntersectionObserver } from './helpers.js'; describe('vaadin-tooltip', () => { + const Tooltip = customElements.get('vaadin-tooltip'); + before(() => { Tooltip.setDefaultFocusDelay(0); Tooltip.setDefaultHoverDelay(0); @@ -26,8 +27,9 @@ describe('vaadin-tooltip', () => { let tooltip, overlay, srLabel; - beforeEach(() => { + beforeEach(async () => { tooltip = fixtureSync(''); + await nextRender(); overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay'); srLabel = tooltip.querySelector('[slot="sr-label"]'); }); @@ -57,74 +59,92 @@ describe('vaadin-tooltip', () => { expect(overlay.$.overlay.hasAttribute('tabindex')).to.be.false; }); - it('should propagate theme attribute to overlay', () => { + it('should propagate theme attribute to overlay', async () => { tooltip.setAttribute('theme', 'foo'); + await nextUpdate(tooltip); expect(overlay.getAttribute('theme')).to.equal('foo'); }); }); describe('text', () => { - it('should use text property as overlay text content', () => { + it('should use text property as overlay text content', async () => { tooltip.text = 'Foo'; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal('Foo'); }); - it('should use text property as screen reader label content', () => { + it('should use text property as screen reader label content', async () => { tooltip.text = 'Foo'; + await nextUpdate(tooltip); expect(srLabel.textContent.trim()).to.equal('Foo'); }); - it('should clear overlay content when text is set to null', () => { + it('should clear overlay content when text is set to null', async () => { tooltip.text = 'Foo'; + await nextUpdate(tooltip); + tooltip.text = null; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal(''); }); - it('should set hidden on the overlay when text is cleared', () => { + it('should set hidden on the overlay when text is cleared', async () => { tooltip.text = 'Foo'; + await nextUpdate(tooltip); expect(overlay.hasAttribute('hidden')).to.be.false; tooltip.text = null; + await nextUpdate(tooltip); expect(overlay.hasAttribute('hidden')).to.be.true; }); }); describe('generator', () => { - it('should use generator property to generate text content', () => { + it('should use generator property to generate text content', async () => { tooltip.generator = () => 'Foo'; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal('Foo'); }); - it('should set screen reader label content using generator', () => { + it('should set screen reader label content using generator', async () => { tooltip.generator = () => 'Foo'; + await nextUpdate(tooltip); expect(srLabel.textContent.trim()).to.equal('Foo'); }); - it('should override text property when generator is set', () => { + it('should override text property when generator is set', async () => { tooltip.text = 'Foo'; + await nextUpdate(tooltip); + tooltip.generator = () => 'Bar'; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal('Bar'); }); - it('should use context property in generator when provided', () => { + it('should use context property in generator when provided', async () => { tooltip.context = { text: 'Foo' }; tooltip.generator = (context) => context.text; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal('Foo'); }); - it('should update text content when context property changes', () => { + it('should update text content when context property changes', async () => { tooltip.context = { text: 'Foo' }; tooltip.generator = (context) => context.text; + await nextUpdate(tooltip); tooltip.context = { text: 'Bar' }; + await nextUpdate(tooltip); expect(overlay.textContent.trim()).to.equal('Bar'); }); - it('should set hidden on the overlay when generator clears text', () => { + it('should set hidden on the overlay when generator clears text', async () => { tooltip.generator = () => 'Bar'; + await nextUpdate(tooltip); expect(overlay.hasAttribute('hidden')).to.be.false; tooltip.generator = () => ''; + await nextUpdate(tooltip); expect(overlay.hasAttribute('hidden')).to.be.true; }); }); @@ -142,29 +162,34 @@ describe('vaadin-tooltip', () => { document.body.removeChild(target); }); - it('should set target as overlay positionTarget', () => { + it('should set target as overlay positionTarget', async () => { tooltip.target = target; + await nextUpdate(tooltip); expect(overlay.positionTarget).to.eql(target); }); - it('should set aria-describedby on the target element', () => { + it('should set aria-describedby on the target element', async () => { tooltip.target = target; + await nextUpdate(tooltip); expect(target.getAttribute('aria-describedby')).to.equal(srLabel.id); }); - it('should retain existing aria-describedby attribute', () => { + it('should retain existing aria-describedby attribute', async () => { target.setAttribute('aria-describedby', 'foo'); tooltip.target = target; + await nextUpdate(tooltip); expect(target.getAttribute('aria-describedby')).to.contain('foo'); expect(target.getAttribute('aria-describedby')).to.contain(srLabel.id); }); - it('should restore aria-describedby when clearing target', () => { + it('should restore aria-describedby when clearing target', async () => { target.setAttribute('aria-describedby', 'foo'); tooltip.target = target; + await nextUpdate(tooltip); tooltip.target = null; + await nextUpdate(tooltip); expect(target.getAttribute('aria-describedby')).to.equal('foo'); }); }); @@ -185,42 +210,48 @@ describe('vaadin-tooltip', () => { document.body.removeChild(target); }); - it('should set aria-describedby on the ariaTarget element', () => { + it('should set aria-describedby on the ariaTarget element', async () => { tooltip.target = target; tooltip.ariaTarget = ariaTarget; + await nextUpdate(tooltip); expect(ariaTarget.getAttribute('aria-describedby')).to.equal(srLabel.id); }); - it('should remove aria-describedby when the ariaTarget is cleared', () => { + it('should remove aria-describedby when the ariaTarget is cleared', async () => { tooltip.target = target; tooltip.ariaTarget = ariaTarget; + await nextUpdate(tooltip); tooltip.ariaTarget = null; + await nextUpdate(tooltip); expect(ariaTarget.hasAttribute('aria-describedby')).to.be.false; expect(target.getAttribute('aria-describedby')).to.equal(srLabel.id); }); - it('should set aria-describedby when providing multiple elements', () => { + it('should set aria-describedby when providing multiple elements', async () => { const ariaTarget2 = document.createElement('button'); target.appendChild(ariaTarget2); tooltip.target = target; tooltip.ariaTarget = [ariaTarget, ariaTarget2]; + await nextUpdate(tooltip); expect(ariaTarget.getAttribute('aria-describedby')).to.equal(srLabel.id); expect(ariaTarget2.getAttribute('aria-describedby')).to.equal(srLabel.id); }); - it('should clear aria-describedby when providing empty array', () => { + it('should clear aria-describedby when providing empty array', async () => { const ariaTarget2 = document.createElement('button'); target.appendChild(ariaTarget2); tooltip.target = target; tooltip.ariaTarget = [ariaTarget, ariaTarget2]; + await nextUpdate(tooltip); tooltip.ariaTarget = []; + await nextUpdate(tooltip); expect(ariaTarget.hasAttribute('aria-describedby')).to.be.false; expect(ariaTarget2.hasAttribute('aria-describedby')).to.be.false; @@ -300,9 +331,10 @@ describe('vaadin-tooltip', () => { describe('auto open', () => { let target; - beforeEach(() => { + beforeEach(async () => { target = fixtureSync('
'); tooltip.target = target; + await nextUpdate(tooltip); }); it('should open overlay on target keyboard focus', () => { @@ -415,13 +447,15 @@ describe('vaadin-tooltip', () => { expect(spy.called).to.be.false; }); - it('should close both opened tooltips on Esc keydown', () => { + it('should close both opened tooltips on Esc keydown', async () => { tabKeyDown(target); target.focus(); const tooltip2 = fixtureSync(''); - const overlay2 = tooltip2.shadowRoot.querySelector('vaadin-tooltip-overlay'); tooltip2.target = target; + await nextRender(); + const overlay2 = tooltip2.shadowRoot.querySelector('vaadin-tooltip-overlay'); + mouseenter(target); escKeyDown(document.body); @@ -429,46 +463,56 @@ describe('vaadin-tooltip', () => { expect(overlay2.opened).to.be.false; }); - it('should not open overlay on mouseenter when target is reset', () => { + it('should not open overlay on mouseenter when target is reset', async () => { mouseenter(target); mouseleave(target); tooltip.target = null; + await nextUpdate(tooltip); + mouseenter(target); expect(overlay.opened).to.be.false; }); - it('should not close overlay on mouseleave when target is reset', () => { + it('should not close overlay on mouseleave when target is reset', async () => { mouseenter(target); tooltip.target = null; + await nextUpdate(tooltip); + mouseleave(target); expect(overlay.opened).to.be.true; }); - it('should not open overlay on keyboard focus when target is reset', () => { + it('should not open overlay on keyboard focus when target is reset', async () => { mouseenter(target); mouseleave(target); tooltip.target = null; + await nextUpdate(tooltip); + tabKeyDown(target); target.focus(); expect(overlay.opened).to.be.false; }); - it('should not close overlay on mousedown when target is reset', () => { + it('should not close overlay on mousedown when target is reset', async () => { mouseenter(target); tooltip.target = null; + await nextUpdate(tooltip); + mousedown(target); expect(overlay.opened).to.be.true; }); - it('should not close overlay on focusout when target is reset', () => { + it('should not close overlay on focusout when target is reset', async () => { tabKeyDown(target); target.focus(); tooltip.target = null; + await nextUpdate(tooltip); + focusout(target); expect(overlay.opened).to.be.true; }); @@ -527,6 +571,8 @@ describe('vaadin-tooltip', () => { it('should close overlay when another overlay opens', async () => { tabKeyDown(target); target.focus(); + await nextUpdate(tooltip); + expect(overlay.opened).to.be.true; const otherOverlay = fixtureSync(''); @@ -560,6 +606,7 @@ describe('vaadin-tooltip', () => { // Partially visible tabKeyDown(target); target.focus(); + await nextUpdate(tooltip); expect(overlay.opened).to.be.true; // Fully visible @@ -576,6 +623,7 @@ describe('vaadin-tooltip', () => { it('should open overlay when hovered target is fully visible and hide otherwise', async () => { // Partially visible mouseenter(target); + await nextUpdate(tooltip); expect(overlay.opened).to.be.true; // Fully visible @@ -598,48 +646,58 @@ describe('vaadin-tooltip', () => { tooltip.target = target; }); - it('should pass tooltip target as a first parameter to shouldShow on mouseenter', () => { + it('should pass tooltip target as a first parameter to shouldShow on mouseenter', async () => { const spy = sinon.spy(); tooltip.shouldShow = spy; + await nextUpdate(tooltip); mouseenter(target); expect(spy.firstCall.args[0]).to.equal(target); }); - it('should pass tooltip context as a second parameter to shouldShow on mouseenter', () => { + it('should pass tooltip context as a second parameter to shouldShow on mouseenter', async () => { const context = { foo: 'bar ' }; tooltip.context = context; + await nextUpdate(tooltip); const spy = sinon.spy(); tooltip.shouldShow = spy; + await nextUpdate(tooltip); + mouseenter(target); expect(spy.firstCall.args[1]).to.equal(context); }); - it('should pass tooltip target as a first parameter to shouldShow on keyboard focus', () => { + it('should pass tooltip target as a first parameter to shouldShow on keyboard focus', async () => { const spy = sinon.spy(); tooltip.shouldShow = spy; + await nextUpdate(tooltip); tabKeyDown(target); target.focus(); + expect(spy.firstCall.args[0]).to.equal(target); }); - it('should pass tooltip context as a second parameter to shouldShow on keyboard focus', () => { + it('should pass tooltip context as a second parameter to shouldShow on keyboard focus', async () => { const context = { foo: 'bar ' }; tooltip.context = context; + await nextUpdate(tooltip); const spy = sinon.spy(); tooltip.shouldShow = spy; + await nextUpdate(tooltip); + tabKeyDown(target); target.focus(); expect(spy.firstCall.args[1]).to.equal(context); }); - it('should not open overlay on mouseenter when shouldShow returns false', () => { + it('should not open overlay on mouseenter when shouldShow returns false', async () => { tooltip.shouldShow = (target) => !target.readOnly; + await nextUpdate(tooltip); target.readOnly = true; mouseenter(target); @@ -647,8 +705,9 @@ describe('vaadin-tooltip', () => { expect(overlay.opened).to.be.not.ok; }); - it('should not open overlay on keyboard focus when shouldShow returns false', () => { + it('should not open overlay on keyboard focus when shouldShow returns false', async () => { tooltip.shouldShow = (target) => !target.readOnly; + await nextUpdate(tooltip); target.readOnly = true; tabKeyDown(target); @@ -657,8 +716,10 @@ describe('vaadin-tooltip', () => { expect(overlay.opened).to.be.not.ok; }); - it('should open overlay on mouseenter when shouldShow returns true', () => { + it('should open overlay on mouseenter when shouldShow returns true', async () => { tooltip.shouldShow = (target) => target.readOnly; + await nextUpdate(tooltip); + target.readOnly = true; mouseenter(target); @@ -666,8 +727,10 @@ describe('vaadin-tooltip', () => { expect(overlay.opened).to.be.true; }); - it('should open overlay on keyboard focus when shouldShow returns true', () => { + it('should open overlay on keyboard focus when shouldShow returns true', async () => { tooltip.shouldShow = (target) => target.readOnly; + await nextUpdate(tooltip); + target.readOnly = true; tabKeyDown(target); @@ -676,16 +739,18 @@ describe('vaadin-tooltip', () => { expect(overlay.opened).to.be.true; }); - it('should open overlay on mouseenter when shouldShow is set to null', () => { + it('should open overlay on mouseenter when shouldShow is set to null', async () => { tooltip.shouldShow = null; + await nextUpdate(tooltip); mouseenter(target); expect(overlay.opened).to.be.true; }); - it('should not open overlay on keyboard focus when shouldShow is set to null', () => { + it('should not open overlay on keyboard focus when shouldShow is set to null', async () => { tooltip.shouldShow = null; + await nextUpdate(tooltip); tabKeyDown(target); target.focus(); @@ -697,10 +762,11 @@ describe('vaadin-tooltip', () => { describe('manual', () => { let target; - beforeEach(() => { + beforeEach(async () => { target = fixtureSync(''); tooltip.target = target; tooltip.manual = true; + await nextUpdate(tooltip); }); it('should not open overlay on target keyboard focus', () => { @@ -779,11 +845,11 @@ describe('vaadin-tooltip', () => { beforeEach(async () => { container = fixtureSync(` -
-
First
-
Second
-
- `); +
+
First
+
Second
+
+ `); target = container.querySelector('#second'); tooltip.target = target; await nextFrame(); @@ -794,6 +860,7 @@ describe('vaadin-tooltip', () => { firstElement.before(target); await waitForIntersectionObserver(); mouseenter(target); + await nextUpdate(tooltip); expect(overlay.opened).to.be.true; }); }); @@ -802,8 +869,9 @@ describe('vaadin-tooltip', () => { describe('manual opened', () => { let tooltip, overlay; - beforeEach(() => { + beforeEach(async () => { tooltip = fixtureSync(''); + await nextRender(); overlay = tooltip._overlayElement; });