diff --git a/.changeset/fast-cats-play.md b/.changeset/fast-cats-play.md new file mode 100644 index 0000000000..425b4758db --- /dev/null +++ b/.changeset/fast-cats-play.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-react": patch +--- + +**Tooltip**: deprecate `type` prop, as it no longer does anything diff --git a/.changeset/fuzzy-cooks-rush.md b/.changeset/fuzzy-cooks-rush.md new file mode 100644 index 0000000000..6a998cb844 --- /dev/null +++ b/.changeset/fuzzy-cooks-rush.md @@ -0,0 +1,5 @@ +--- +"@digdir/designsystemet-react": patch +--- + +**Tooltip**: the React component will no longer override existing accessible text. It now correctly sets `aria-description` in that case. If there is no accessible text, `aria-label` will be used as before. diff --git a/packages/react/src/components/tooltip/tooltip.stories.tsx b/packages/react/src/components/tooltip/tooltip.stories.tsx index 6557b94658..844fff2096 100644 --- a/packages/react/src/components/tooltip/tooltip.stories.tsx +++ b/packages/react/src/components/tooltip/tooltip.stories.tsx @@ -1,11 +1,17 @@ import { FilesIcon } from '@navikt/aksel-icons'; import type { Meta, StoryFn, StoryObj } from '@storybook/react-vite'; import { useEffect, useRef, useState } from 'react'; -import { expect, within } from 'storybook/test'; -import { Button } from '../../'; +import { expect, fireEvent, userEvent, waitFor } from 'storybook/test'; +import { Button, Link } from '../../'; import { Tooltip } from './tooltip'; type Story = StoryObj; +type FnStory = StoryFn; + +function isInViewport(el: Element) { + const { height, width } = el.getBoundingClientRect(); + return height > 1 && width > 1; +} export default { title: 'Komponenter/Tooltip', @@ -17,17 +23,34 @@ export default { }, }, play: async (ctx) => { - document.querySelector('.ds-tooltip')?.remove(); // Reset to run next test without waiting for tooltip to disappear // <== Må "nullstille"/fjerne tooltip mellom hver test - const button = - ctx.canvasElement.querySelector('[data-tooltip]'); - - await new Promise((resolve) => { - document.addEventListener('animationend', resolve, true); // <== Merk at vi binder event-listener før vi gjør hover - button?.focus(); - }); - - const tooltip = await within(document.body).findByText(ctx.args.content); // <== trenger ikke sjekke toBeInDocument siden denne testen krever det - expect(tooltip).toBeVisible(); + const tooltips = + ctx.canvasElement.querySelectorAll('[data-tooltip]'); + const fakeFocus = (e: HTMLElement) => { + fireEvent.focus(e); // shows up in interaction log in Storybook + e.focus({ focusVisible: true } as Record); // necessary to get focusVisible styling, but doesn't show up in interaction log + }; + for (const event of [userEvent.hover, fakeFocus]) + for (const tooltipTrigger of tooltips) { + await event(tooltipTrigger); + await waitFor(async () => { + const text = tooltipTrigger.getAttribute('data-tooltip'); + if (!text) { + throw new Error('Tooltip trigger has no data-tooltip attribute'); + } + const tooltipRenderer = document.body.querySelector('.ds-tooltip'); + await expect(tooltipRenderer).toBeVisible(); + await expect(tooltipRenderer).toSatisfy(isInViewport); // toBeVisible() doesn't check if the element is in the viewport + await expect(tooltipRenderer).toHaveTextContent(text); + if (tooltipTrigger.textContent.trim()) { + await expect(tooltipTrigger).toHaveAttribute( + 'aria-description', + text, + ); + } else { + await expect(tooltipTrigger).toHaveAttribute('aria-label', text); + } + }); + } }, } satisfies Meta; @@ -44,6 +67,30 @@ Preview.args = { placement: 'top', }; +export const WithLink: FnStory = () => { + return ( + + En lenke + + ); +}; + +export const WithSpan: FnStory = () => { + return ( + + Tekst med tooltip + + ); +}; + +export const WithPlainText: FnStory = () => { + return ( + + Tekst med tooltip + + ); +}; + export const WithString: Story = { args: { content: 'Organisasjonsnummer', @@ -64,12 +111,18 @@ export const Placement: Story = { }, }; -export const Aria: StoryFn = () => { +export const Aria: FnStory = () => { return ( <> - + + + +