From bfa80a4f4adbdea4856e23a7d6898e904a898491 Mon Sep 17 00:00:00 2001 From: Robbert Broersma Date: Sun, 19 May 2024 19:12:43 +0200 Subject: [PATCH 1/4] test: small tweaks to React component tests --- .../src/Article.test.tsx | 12 ++++++++++++ .../src/BadgeCounter.test.tsx | 3 ++- .../src/Checkbox.test.tsx | 17 +++++++++++++---- .../component-library-react/src/IBANData.tsx | 2 +- .../src/Listbox.test.tsx | 10 +++++++--- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/component-library-react/src/Article.test.tsx b/packages/component-library-react/src/Article.test.tsx index edb97961b3c..30c35b2ad31 100644 --- a/packages/component-library-react/src/Article.test.tsx +++ b/packages/component-library-react/src/Article.test.tsx @@ -13,6 +13,18 @@ describe('Article', () => { expect(article).toBeVisible(); }); + it('can render an article role with a name', () => { + render( +
+

Heading

+
, + ); + + const article = screen.getByRole('article', { name: 'Heading' }); + + expect(article).toBeInTheDocument(); + }); + it('renders an article HTML element', () => { const { container } = render(
); diff --git a/packages/component-library-react/src/BadgeCounter.test.tsx b/packages/component-library-react/src/BadgeCounter.test.tsx index d071c83e1f0..56f17fcc6ad 100644 --- a/packages/component-library-react/src/BadgeCounter.test.tsx +++ b/packages/component-library-react/src/BadgeCounter.test.tsx @@ -3,7 +3,7 @@ import { createRef } from 'react'; import { BadgeCounter } from './BadgeCounter'; import '@testing-library/jest-dom'; -describe('Data badge', () => { +describe('Badge counter', () => { it('renders an HTML span element', () => { const { container } = render({'42'}); @@ -29,6 +29,7 @@ describe('Data badge', () => { expect(badge).toHaveClass('utrecht-badge-counter'); }); + it('can have a additional class name', () => { const { container } = render(); diff --git a/packages/component-library-react/src/Checkbox.test.tsx b/packages/component-library-react/src/Checkbox.test.tsx index 5b0dde3baa1..3ff0dc3ad60 100644 --- a/packages/component-library-react/src/Checkbox.test.tsx +++ b/packages/component-library-react/src/Checkbox.test.tsx @@ -36,6 +36,7 @@ describe('Checkbox', () => { expect(link).toHaveClass('utrecht-checkbox'); }); + it('can have a additional class name', () => { const { container } = render(); @@ -47,9 +48,9 @@ describe('Checkbox', () => { }); describe('checked variant', () => { it('is not checked by default', () => { - const { container } = render(); + render(); - const checkbox = container.querySelector(':only-child'); + const checkbox = screen.getByRole('checkbox'); expect(checkbox).not.toBeChecked(); }); @@ -67,9 +68,17 @@ describe('Checkbox', () => { it('can have a checked state', () => { const handleChange = () => {}; - const { container } = render(); + render(); - const checkbox = container.querySelector(':only-child'); + const checkbox = screen.getByRole('checkbox'); + + expect(checkbox).toBeChecked(); + }); + + it('can have a defaultChecked state (in React)', () => { + render(); + + const checkbox = screen.getByRole('checkbox'); expect(checkbox).toBeChecked(); }); diff --git a/packages/component-library-react/src/IBANData.tsx b/packages/component-library-react/src/IBANData.tsx index 63ed55a0bab..762ac3190dc 100644 --- a/packages/component-library-react/src/IBANData.tsx +++ b/packages/component-library-react/src/IBANData.tsx @@ -27,7 +27,7 @@ export const IBANData = forwardRef( ({ children, value, className, ...restProps }: IBANDataProps, ref: ForwardedRef) => { const normalized = normalizeIBAN(value); const formatted = formatIBAN(normalized); - console.log({ value, normalized, formatted }); + return ( {children || formatted} diff --git a/packages/component-library-react/src/Listbox.test.tsx b/packages/component-library-react/src/Listbox.test.tsx index 12cf7afbaae..3d9c1e69b5b 100644 --- a/packages/component-library-react/src/Listbox.test.tsx +++ b/packages/component-library-react/src/Listbox.test.tsx @@ -39,13 +39,17 @@ describe('Listbox', () => { }); describe('disabled state', () => { - // `aria-disabled` is somehow not recognized as disabled state - it.skip('has a disabled listbox', () => { + // `aria-disabled` is somehow not recognized as disabled state on a listbox by Testing Library + it.todo('has a disabled listbox in the accessibility tree'); + + // Temporary alternative to the accessibility tree test + it('has a disabled listbox in the DOM', () => { render(); const listbox = screen.getByRole('listbox'); - expect(listbox).toBeDisabled(); + // Look at the DOM instead of the accessibility tree + expect(listbox).toHaveAttribute('aria-disabled', 'true'); }); }); From b0431221a25fd2ba21b36892cd5907eab56041f8 Mon Sep 17 00:00:00 2001 From: Robbert Broersma Date: Sun, 19 May 2024 19:13:19 +0200 Subject: [PATCH 2/4] docs: guidance on writing complete React component unit tests --- packages/component-library-react/TESTING.md | 207 ++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 packages/component-library-react/TESTING.md diff --git a/packages/component-library-react/TESTING.md b/packages/component-library-react/TESTING.md new file mode 100644 index 00000000000..3496aa06d1f --- /dev/null +++ b/packages/component-library-react/TESTING.md @@ -0,0 +1,207 @@ + + +# Testing components + +## Test for extensibility + +### Class names + +Front-end developers rely on the BEM class names to add their own CSS. When the component renames or removes a class name, there is a breaking change. Unit tests must check each class name, so they are reliable APIs. + +You will find many tests like this: + +```jsx +it("renders a design system BEM class name: my-component", () => { + const { container } = render(); + + const field = container.querySelector("div"); + + expect(field).toHaveClass(".my-component"); +}); +``` + +### So I put some HTML in your HTML + +Text in components can sometimes be improved with markup: language metadata, code, emphasis or images. Each property that ends up in the HTML should be tested to be extensible with rich text content. + +```jsx +it("renders rich text content", () => { + const { container } = render( + + The French national motto: Liberté, égalité, fraternité + + ); + + const richText = container.querySelector("span"); + + expect(richText).toBeInTheDocument(); +}); +``` + +Testing properties is perhaps even more important, because `children` usually already allows HTML content: + +```jsx +it('renders rich text content', () => { + const { container } = render( + E-mail address + }>, + ); + + const richText = container.querySelector('svg'); + + expect(richText).toBeInTheDocument(); +}); +``` + +## Don't break native HTML + +### Global attributes + +[Global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes) can be used on all HTML elements, so components that render HTML must support them too. In React this is easy to support using `...restProps`. The following code examples use global attributes: + +- `` +- `` +- `