diff --git a/packages/dnb-eufemia/src/components/tooltip/TooltipPortal.tsx b/packages/dnb-eufemia/src/components/tooltip/TooltipPortal.tsx index a5f94e621b1..259f25819f5 100644 --- a/packages/dnb-eufemia/src/components/tooltip/TooltipPortal.tsx +++ b/packages/dnb-eufemia/src/components/tooltip/TooltipPortal.tsx @@ -83,13 +83,21 @@ export default class TooltipPortal extends React.PureComponent< } }) } else if (!active && prevProps.active) { - tooltipPortal[group].timeout = setTimeout(() => { + const run = () => { this.setState({ isActive: false }, () => { if (!this.isMainGorup()) { this.renderPortal() } }) - }, parseFloat(String(hide_delay))) + } + if (this.props.no_animation || globalThis.IS_TEST) { + run() + } else { + tooltipPortal[group].timeout = setTimeout( + run, + parseFloat(String(hide_delay)) + ) + } } } } diff --git a/packages/dnb-eufemia/src/components/tooltip/TooltipWithEvents.tsx b/packages/dnb-eufemia/src/components/tooltip/TooltipWithEvents.tsx index 9c1e187cee9..e1bd2ec9d23 100644 --- a/packages/dnb-eufemia/src/components/tooltip/TooltipWithEvents.tsx +++ b/packages/dnb-eufemia/src/components/tooltip/TooltipWithEvents.tsx @@ -150,17 +150,19 @@ export default class TooltipWithEvents extends React.PureComponent< warn(e) } - clearTimeout(this._onEnterTimeout) - this._onEnterTimeout = setTimeout( - () => { - this.setState({ isActive: true }) + const run = () => { + this.setState({ isActive: true, clientX: e.clientX }) + } - this.setState({ isActive: true, clientX: e.clientX }) - }, - typeof globalThis !== 'undefined' && !globalThis.IS_TEST - ? parseFloat(String(this.props.show_delay)) || 1 - : 1 - ) // have min 1 to make sure we are after onMouseLeave + if (this.props.no_animation || globalThis.IS_TEST) { + run() + } else { + clearTimeout(this._onEnterTimeout) + this._onEnterTimeout = setTimeout( + run, + parseFloat(String(this.props.show_delay)) || 1 + ) // have min 1 to make sure we are after onMouseLeave + } } onMouseLeave = (e: MouseEvent) => { diff --git a/packages/dnb-eufemia/src/components/tooltip/__tests__/Tooltip.test.tsx b/packages/dnb-eufemia/src/components/tooltip/__tests__/Tooltip.test.tsx index 536a969c4f1..54b44b1e0aa 100644 --- a/packages/dnb-eufemia/src/components/tooltip/__tests__/Tooltip.test.tsx +++ b/packages/dnb-eufemia/src/components/tooltip/__tests__/Tooltip.test.tsx @@ -4,14 +4,10 @@ */ import React from 'react' -import { - mount, - toJson, - axeComponent, - loadScss, - attachToBody, -} from '../../../core/jest/jestSetup' -import Tooltip from '../Tooltip' +import { axeComponent, loadScss } from '../../../core/jest/jestSetup' +import { fireEvent, render } from '@testing-library/react' +import { wait } from '@testing-library/user-event/dist/utils' +import OriginalTooltip from '../Tooltip' import Anchor from '../../../elements/Anchor' import { TooltipProps } from '../types' @@ -41,20 +37,39 @@ beforeEach(() => { }) describe('Tooltip', () => { - describe('with target', () => { - const Component = (props: TooltipProps = {}) => ( + describe('with target_selector', () => { + const Tooltip = (props: TooltipProps = {}) => ( <> - + Text - + ) + it('should validate with ARIA rules as a tooltip', async () => { + const Component = render() + expect(await axeComponent(Component)).toHaveNoViolations() + }) + }) + + describe('with target_element', () => { + const Tooltip = (props: TooltipProps = {}) => ( + Button} + > + Text + + ) + it('creates a React Portal', () => { - mount(, { - attachTo: attachToBody(), - }) + render() expect( document.body.querySelectorAll('.dnb-tooltip__portal') @@ -65,48 +80,21 @@ describe('Tooltip', () => { }) it('will skip React Portal when skip_portal is true', () => { - const Comp = mount(, { - attachTo: attachToBody(), - }) + render() expect( document.body.querySelectorAll('.dnb-tooltip__portal') ).toHaveLength(0) - - expect(toJson(Comp.find('.dnb-tooltip'))).toMatchSnapshot() }) - it('should validate with ARIA rules as a tooltip', async () => { - const Comp = mount() - expect(await axeComponent(Comp)).toHaveNoViolations() - }) - }) - - describe('with target_element', () => { - const Component = (props: TooltipProps = {}) => ( - Button} - > - Text - - ) - - it('have to match default tooltip snapshot', () => { - const Comp = mount() - expect(toJson(Comp)).toMatchSnapshot() - }) - - it('should show when active prop is true', async () => { - const Component = () => { + it('should show when active prop is true', () => { + const Tooltip = () => { const [active, setActive] = React.useState(false) return ( - { @@ -121,112 +109,152 @@ describe('Tooltip', () => { } > Tooltip - + ) } - const Comp = mount() + render() const mainElem = document.body.querySelector('.dnb-tooltip') + const buttonElem = document.querySelector('button') - Comp.find('button').simulate('mouseenter') + fireEvent.mouseEnter(buttonElem) expect(mainElem.classList.contains('dnb-tooltip--active')).toBe(true) - Comp.find('button').simulate('mouseleave') - Comp.find('button').simulate('mouseenter') - - await wait(2) + fireEvent.mouseLeave(buttonElem) + fireEvent.mouseEnter(buttonElem) expect(mainElem.classList.contains('dnb-tooltip--active')).toBe(true) - Comp.find('button').simulate('mouseleave') - - await wait(2) + fireEvent.mouseLeave(buttonElem) expect(mainElem.classList.contains('dnb-tooltip--hide')).toBe(true) }) - it('should validate with ARIA rules as a tooltip', async () => { - const Comp = mount() - expect(await axeComponent(Comp)).toHaveNoViolations() + it('should set animate_position class', () => { + render() + + const mainElem = document.body.querySelector('.dnb-tooltip') + + expect(Array.from(mainElem.classList)).toEqual( + expect.arrayContaining([ + 'dnb-tooltip', + 'dnb-tooltip--active', + 'dnb-tooltip--animate_position', + ]) + ) }) - it('should show when active prop is true', async () => { - const Component = () => { - const [active, setActive] = React.useState(false) + it('should set fixed_position class', () => { + render() + + const mainElem = document.body.querySelector('.dnb-tooltip') + + expect(Array.from(mainElem.classList)).toEqual( + expect.arrayContaining([ + 'dnb-tooltip', + 'dnb-tooltip--active', + 'dnb-tooltip--fixed', + ]) + ) + }) + + describe('and group', () => { + const commonProps: TooltipProps = { + group: 'unique-name', + no_animation: true, + } + const GroupTooltip = (props) => { return ( - { - setActive(true) - }} - onMouseLeave={() => { - setActive(false) - }} - > - Text - - } - > - Tooltip - + <> + Button A} + {...commonProps} + {...props} + > + Tooltip A + + + Button B} + {...commonProps} + {...props} + > + Tooltip B + + ) } - const Comp = mount() + it('should only have one tooltip', () => { + render() - const mainElem = document.body.querySelector('.dnb-tooltip') + const allElements = document.body.querySelectorAll('.dnb-tooltip') + const mainElem = allElements[0] + const buttonA = document.querySelector('button#a') + const buttonB = document.querySelector('button#b') - Comp.find('button').simulate('mouseenter') + expect(allElements).toHaveLength(1) - expect(mainElem.classList.contains('dnb-tooltip--active')).toBe(true) + fireEvent.mouseEnter(buttonA) - Comp.find('button').simulate('mouseleave') - Comp.find('button').simulate('mouseenter') + expect(mainElem.textContent).toBe('Tooltip A') + expect(mainElem.classList.contains('dnb-tooltip--active')).toBe( + true + ) - await wait(2) + fireEvent.mouseEnter(buttonB) - expect(mainElem.classList.contains('dnb-tooltip--active')).toBe(true) + expect(mainElem.textContent).toBe('Tooltip B') + expect(mainElem.classList.contains('dnb-tooltip--active')).toBe( + true + ) - Comp.find('button').simulate('mouseleave') + fireEvent.mouseLeave(buttonB) - await wait(2) + expect(mainElem.classList.contains('dnb-tooltip--hide')).toBe(true) + }) + }) - expect(mainElem.classList.contains('dnb-tooltip--hide')).toBe(true) + it('should validate with ARIA rules as a tooltip', async () => { + const Component = render() + expect(await axeComponent(Component)).toHaveNoViolations() }) }) describe('Anchor with tooltip', () => { it('has to be in the DOM so aria-describedby is valid', () => { - const Comp = mount( + render( text ) - const id = Comp.find('a').instance().getAttribute('aria-describedby') + const id = document + .querySelector('a') + .getAttribute('aria-describedby') expect(document.body.querySelectorAll('#' + id).length).toBe(1) }) it('has to be visible on hover', async () => { - const Comp = mount( + render( text ) // hover - Comp.find('a').instance().dispatchEvent(new MouseEvent('mouseenter')) + document + .querySelector('a') + .dispatchEvent(new MouseEvent('mouseenter')) await wait(100) - const id = Comp.find('a').instance().getAttribute('aria-describedby') + const id = document + .querySelector('a') + .getAttribute('aria-describedby') expect( document.body .querySelector('#' + id) @@ -234,7 +262,9 @@ describe('Tooltip', () => { ).toBe(true) // leave hover - Comp.find('a').instance().dispatchEvent(new MouseEvent('mouseleave')) + document + .querySelector('a') + .dispatchEvent(new MouseEvent('mouseleave')) await wait(600) @@ -246,14 +276,14 @@ describe('Tooltip', () => { }) it('has to be visible on focus event dispatch', async () => { - const Comp = mount( + render( text ) document.documentElement.setAttribute('data-whatintent', 'keyboard') - const inst = Comp.find('a').instance() + const inst = document.querySelector('a') inst.dispatchEvent(new Event('focus')) await wait(400) // because of visibility delay @@ -281,5 +311,3 @@ describe('Tooltip scss', () => { expect(scss).toMatchSnapshot() }) }) - -const wait = (t: number) => new Promise((r) => setTimeout(r, t)) diff --git a/packages/dnb-eufemia/src/components/tooltip/__tests__/__snapshots__/Tooltip.test.tsx.snap b/packages/dnb-eufemia/src/components/tooltip/__tests__/__snapshots__/Tooltip.test.tsx.snap index 1ade9000ec0..3105299324b 100644 --- a/packages/dnb-eufemia/src/components/tooltip/__tests__/__snapshots__/Tooltip.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/tooltip/__tests__/__snapshots__/Tooltip.test.tsx.snap @@ -181,123 +181,3 @@ exports[`Tooltip scss have to match snapshot 1`] = ` visibility: hidden; } } " `; - -exports[`Tooltip with target will skip React Portal when skip_portal is true 1`] = ` - - - - Text - - -`; - -exports[`Tooltip with target_element have to match default tooltip snapshot 1`] = ` - - - Button - - } - > - - Button - - } - target_element={ - - } - target_selector={null} - tooltip={null} - > - - - Button - - } - target_element={ - - } - target_selector={null} - tooltip={null} - /> - - - -`; diff --git a/packages/dnb-eufemia/src/components/tooltip/stories/Tooltip.stories.tsx b/packages/dnb-eufemia/src/components/tooltip/stories/Tooltip.stories.tsx index 06d60787e19..ef2cee3e01b 100644 --- a/packages/dnb-eufemia/src/components/tooltip/stories/Tooltip.stories.tsx +++ b/packages/dnb-eufemia/src/components/tooltip/stories/Tooltip.stories.tsx @@ -119,7 +119,11 @@ export const TooltipSandbox = () => { Tooltip Tooltip for this NumberFormat} + tooltip={ + + Tooltip for this NumberFormat + + } > 1234