Skip to content

Commit

Permalink
♻️ [open-formulieren/open-forms#5016] Replace react-select-event with…
Browse files Browse the repository at this point in the history
… custom helper

selectEvent from react-select-event was causing issues when used with a storybook production build
  • Loading branch information
stevenbal committed Feb 4, 2025
1 parent c94af28 commit aec40e4
Show file tree
Hide file tree
Showing 11 changed files with 56 additions and 126 deletions.
19 changes: 0 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-select-event": "^5.5.1",
"sass": "^1.75.0",
"sass-loader": "^14.2.0",
"storybook": "^8.3.5",
Expand Down
3 changes: 1 addition & 2 deletions src/components/formio/radio.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import clsx from 'clsx';
import {Field, useFormikContext} from 'formik';
import {ExtendedComponentSchema} from 'formiojs';
import {ReactNode} from 'react';
import {FormattedMessage} from 'react-intl';

import {useValidationErrors} from '@/utils/errors';
Expand Down Expand Up @@ -35,7 +34,7 @@ export const RadioInput: React.FC<RadioInputProps> = ({name, value, label, descr

export interface Option {
value: string;
label: ReactNode;
label: React.ReactNode;
description?: string;
}

Expand Down
3 changes: 1 addition & 2 deletions src/components/formio/select.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {useField} from 'formik';
import React from 'react';
import {ReactNode} from 'react';
import ReactSelect from 'react-select';
import type {
GroupBase,
Expand Down Expand Up @@ -38,7 +37,7 @@ export interface SelectProps<

export interface Option {
value: string;
label: ReactNode;
label: React.ReactNode;
description?: string;
}

Expand Down
4 changes: 1 addition & 3 deletions src/components/formio/selectboxes.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import {ReactNode} from 'react';

import {CheckboxInput} from './checkbox';
import Component from './component';
import Description from './description';

export interface Option {
value: string;
label: ReactNode;
label: React.ReactNode;
description?: string;
}

Expand Down
24 changes: 12 additions & 12 deletions src/registry/radio/radio-referentielijsten.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ export const StoreValuesInComponent: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

Expand All @@ -83,15 +83,15 @@ export const SwitchToVariableResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'From variable');
await rsSelect(canvas, dataSourceInput, 'From variable');

const itemsExpressionInput = canvas.getByTestId('jsonEdit');
await userEvent.clear(itemsExpressionInput);
Expand Down Expand Up @@ -120,15 +120,15 @@ export const SwitchToManualResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'Manually fill in');
await rsSelect(canvas, dataSourceInput, 'Manually fill in');

const labelInput = canvas.getByTestId('input-values[0].label');
await userEvent.type(labelInput, 'Foo');
Expand Down Expand Up @@ -178,7 +178,7 @@ export const AutoSelectIfOnlyOneReferentielijstenService: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');

Expand Down
22 changes: 11 additions & 11 deletions src/registry/select/select-referentielijsten.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,13 @@ export const StoreValuesInComponent: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

Expand All @@ -83,15 +83,15 @@ export const SwitchToVariableResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'From variable');
await rsSelect(canvas, dataSourceInput, 'From variable');

const itemsExpressionInput = canvas.getByTestId('jsonEdit');
await userEvent.clear(itemsExpressionInput);
Expand Down Expand Up @@ -120,15 +120,15 @@ export const SwitchToManualResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'Manually fill in');
await rsSelect(canvas, dataSourceInput, 'Manually fill in');

const labelInput = canvas.getByTestId('input-data.values[0].label');
await userEvent.type(labelInput, 'Foo');
Expand Down
22 changes: 11 additions & 11 deletions src/registry/selectboxes/selectboxes-referentielijsten.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ export const StoreValuesInComponent: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await userEvent.click(canvas.getByRole('button', {name: 'Save'}));

Expand All @@ -80,15 +80,15 @@ export const SwitchToVariableResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'From variable');
await rsSelect(canvas, dataSourceInput, 'From variable');

const itemsExpressionInput = canvas.getByTestId('jsonEdit');
await userEvent.clear(itemsExpressionInput);
Expand Down Expand Up @@ -117,15 +117,15 @@ export const SwitchToManualResetOptions: Story = {

await step('Fill in options', async () => {
const dataSourceInput = canvas.getByLabelText('Data source');
await rsSelect(dataSourceInput, 'Referentielijsten API');
await rsSelect(canvas, dataSourceInput, 'Referentielijsten API');

const serviceInput = canvas.getByLabelText('Referentielijsten service');
await rsSelect(serviceInput, 'Referentielijsten');
await rsSelect(canvas, serviceInput, 'Referentielijsten');

const codeInput = canvas.getByLabelText('Referentielijsten table code');
await rsSelect(codeInput, 'Tabel 2 (niet meer geldig)');
await rsSelect(canvas, codeInput, 'Tabel 2 (niet meer geldig)');

await rsSelect(dataSourceInput, 'Manually fill in');
await rsSelect(canvas, dataSourceInput, 'Manually fill in');

const labelInput = canvas.getByTestId('input-values[0].label');
await userEvent.type(labelInput, 'Foo');
Expand Down
31 changes: 7 additions & 24 deletions src/utils/storybookTestHelpers.d.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,17 @@
declare module '@/utils/storybookTestHelpers' {
import {Screen} from '@testing-library/react';
import {within} from '@storybook/test';

/**
* Wrapper around selectEvent.select to ensure the portal option is used.
*
* @param canvas - The canvas where the input is present.
* @param input - The input element associated with the react-select component.
* @param optionOrOptions - The option or options to select.
* @returns A promise that resolves when the select event is triggered.
*/
export function rsSelect(input: HTMLElement, optionOrOptions: string | string[]): Promise<void>;

/**
* From the input field (retrieved by accessible queries), find the react-select container.
*
* Usage:
*
* const dropdown = canvas.getByLabelText('My dropdown');
* const container = getReactSelectContainer(dropdown);
* const options = within(container).queryByRole('option');
*
* @param comboboxInput - The combobox input element.
* @returns The react-select container element or null if not found.
*/
export function getReactSelectContainer(comboboxInput: HTMLElement): HTMLElement | null;

/**
* Get the (portaled) opened react-select menu.
*
* @param canvas - The canvas object from Storybook's testing-library.
* @returns A promise that resolves to the listbox element.
*/
export function findReactSelectMenu(canvas: Screen): Promise<HTMLElement>;
export function rsSelect(
canvas: ReturnType<typeof within>,
input: HTMLElement,
optionOrOptions: string | string[]
): Promise<void>;
}
52 changes: 11 additions & 41 deletions src/utils/storybookTestHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,19 @@
import {ByRoleOptions, Screen} from '@testing-library/react';
import selectEvent from 'react-select-event';

const SB_ROOT: HTMLElement | null = document.getElementById('storybook-root');
import {userEvent, within} from '@storybook/test';

/**
* Wrapper around selectEvent.select to ensure the portal option is used.
* Wrapper to select an option in a react-select component
*
* @param canvas - The canvas where the input is present.
* @param input - The input element associated with the react-select component.
* @param optionOrOptions - The option or options to select.
*/
const rsSelect = async (input: HTMLElement, optionOrOptions: string | string[]): Promise<void> => {
if (!SB_ROOT) {
throw new Error('storybook-root element not found in the DOM.');
}
await selectEvent.select(input, optionOrOptions, {container: SB_ROOT});
};

/**
* From the input field (retrieved by accessible queries), find the react-select container.
*
* Usage:
*
* const dropdown = canvas.getByLabelText('My dropdown');
* const container = getReactSelectContainer(dropdown);
* const options = within(container).queryByRole('option');
*
* @param comboboxInput - The combobox input element.
* @returns The react-select container element or null if not found.
*/
const getReactSelectContainer = (comboboxInput: HTMLElement): HTMLElement | null => {
return comboboxInput.closest('.admin-react-select');
};

/**
* Get the (portaled) opened react-select menu.
*
* @param canvas - The canvas object from Storybook's testing-library.
* @param options - Optional options for querying the listbox.
* @returns A promise that resolves to the listbox element.
*/
const findReactSelectMenu = async (
canvas: Screen,
options?: ByRoleOptions
): Promise<HTMLElement> => {
return await canvas.findByRole('listbox', options);
const rsSelect = async (
canvas: ReturnType<typeof within>,
input: HTMLElement,
optionOrOptions: string | string[]
): Promise<void> => {
await userEvent.click(input);
await userEvent.click(await canvas.findByText(optionOrOptions));
};

export {rsSelect, getReactSelectContainer, findReactSelectMenu};
export {rsSelect};
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"noImplicitAny": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"noErrorTruncation": true,
"paths": {
Expand Down

0 comments on commit aec40e4

Please sign in to comment.