From 9836b2319a58b44d49810c580a1597a53b948fdd Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Tue, 28 Jan 2025 12:00:57 +0100 Subject: [PATCH] :white_check_mark: [open-formulieren/open-forms#5016] Stories for referentielijsten options --- .storybook/decorators.tsx | 6 + .../ComponentConfiguration.stories.tsx | 4 + .../builder/values/values-config.stories.tsx | 57 +++++++ .../radio/radio-referentielijsten.stories.ts | 158 +++++++++++++++++ .../select-referentielijsten.stories.ts | 160 ++++++++++++++++++ .../selectboxes-referentielijsten.stories.ts | 155 +++++++++++++++++ src/tests/sharedUtils.tsx | 18 ++ src/utils/storybookTestHelpers.d.ts | 34 ++++ src/utils/storybookTestHelpers.ts | 49 ++++++ 9 files changed, 641 insertions(+) create mode 100644 src/registry/radio/radio-referentielijsten.stories.ts create mode 100644 src/registry/select/select-referentielijsten.stories.ts create mode 100644 src/registry/selectboxes/selectboxes-referentielijsten.stories.ts create mode 100644 src/utils/storybookTestHelpers.d.ts create mode 100644 src/utils/storybookTestHelpers.ts diff --git a/.storybook/decorators.tsx b/.storybook/decorators.tsx index 2ad69c94..dbadbcd9 100644 --- a/.storybook/decorators.tsx +++ b/.storybook/decorators.tsx @@ -14,6 +14,7 @@ import { DEFAULT_PREFILL_ATTRIBUTES, DEFAULT_PREFILL_PLUGINS, DEFAULT_REGISTRATION_ATTRIBUTES, + DEFAULT_SERVICES, DEFAULT_VALIDATOR_PLUGINS, sleep, } from '@/tests/sharedUtils'; @@ -61,6 +62,7 @@ export const BuilderContextDecorator: Decorator = (Story, context) => { context.parameters.builder?.defaultValidatorPlugins || DEFAULT_VALIDATOR_PLUGINS; const defaultRegistrationAttributes = context.parameters.builder?.defaultRegistrationAttributes || DEFAULT_REGISTRATION_ATTRIBUTES; + const defaultServices = context.parameters.builder?.defaultServices || DEFAULT_SERVICES; const defaultPrefillPlugins = context.parameters.builder?.defaultPrefillPlugins || DEFAULT_PREFILL_PLUGINS; const defaultPrefillAttributes = @@ -86,6 +88,10 @@ export const BuilderContextDecorator: Decorator = (Story, context) => { await sleep(context.parameters?.builder?.registrationAttributesDelay || 0); return context?.args?.registrationAttributes || defaultRegistrationAttributes; }, + getServices: async () => { + await sleep(context.parameters?.builder?.servicesDelay || 0); + return context?.args?.services || defaultServices; + }, getPrefillPlugins: async () => { await sleep(context.parameters?.builder?.prefillPluginsDelay || 0); return context?.args?.prefillPlugins || defaultPrefillPlugins; diff --git a/src/components/ComponentConfiguration.stories.tsx b/src/components/ComponentConfiguration.stories.tsx index 2810edb1..a5bd2da8 100644 --- a/src/components/ComponentConfiguration.stories.tsx +++ b/src/components/ComponentConfiguration.stories.tsx @@ -7,6 +7,7 @@ import {Meta, StoryFn, StoryObj} from '@storybook/react'; import {expect, fireEvent, fn, userEvent, waitFor, within} from '@storybook/test'; import React from 'react'; +import {ReferentielijstenServiceOption} from '@/components/builder/values/referentielijsten/service'; import { CONFIDENTIALITY_LEVELS, DEFAULT_AUTH_PLUGINS, @@ -75,6 +76,7 @@ interface TemplateArgs { validatorPlugins: ValidatorOption[]; registrationAttributes: RegistrationAttributeOption[]; prefillPlugins: PrefillPluginOption[]; + services: ReferentielijstenServiceOption[]; prefillAttributes: Record; fileTypes: Array<{value: string; label: string}>; isNew: boolean; @@ -91,6 +93,7 @@ const Template: StoryFn = ({ registrationAttributes, prefillPlugins, prefillAttributes, + services, supportedLanguageCodes, fileTypes, isNew, @@ -107,6 +110,7 @@ const Template: StoryFn = ({ getFormComponents={() => otherComponents} getValidatorPlugins={async () => validatorPlugins} getRegistrationAttributes={async () => registrationAttributes} + getServices={async () => services} getPrefillPlugins={async () => prefillPlugins} getPrefillAttributes={async (plugin: string) => prefillAttributes[plugin]} getFileTypes={async () => fileTypes} diff --git a/src/components/builder/values/values-config.stories.tsx b/src/components/builder/values/values-config.stories.tsx index 2f43bc23..e11601f5 100644 --- a/src/components/builder/values/values-config.stories.tsx +++ b/src/components/builder/values/values-config.stories.tsx @@ -7,7 +7,10 @@ import {Meta, StoryObj} from '@storybook/react'; import {expect, fireEvent, fn, userEvent, waitFor, within} from '@storybook/test'; import {Form, Formik} from 'formik'; +import selectboxes from '@/registry/selectboxes'; +import {BuilderContextDecorator} from '@/sb-decorators'; import {withFormik} from '@/sb-decorators'; +import {DEFAULT_SERVICES} from '@/tests/sharedUtils'; import ValuesConfig from './values-config'; @@ -300,3 +303,57 @@ export const SelectVariable: SelectStory = { }, }, }; + +export const SelectReferentielijsten: SelectStory = { + ...Select, + + decorators: [withFormik, BuilderContextDecorator], + parameters: { + formik: { + initialValues: { + openForms: { + dataSrc: 'referentielijsten', + itemsExpression: {code: 'table1', service: 'referentielijsten-api'}, + }, + data: {}, + }, + }, + builder: {enableContext: true}, + }, +}; + +export const SelectboxesReferentielijsten: SelectboxesStory = { + ...SelectBoxes, + + decorators: [withFormik, BuilderContextDecorator], + parameters: { + formik: { + initialValues: { + openForms: { + dataSrc: 'referentielijsten', + itemsExpression: {code: 'table1', service: 'referentielijsten-api'}, + }, + data: {}, + }, + }, + builder: {enableContext: true}, + }, +}; + +export const RadioReferentielijsten: RadioStory = { + ...Radio, + + decorators: [withFormik, BuilderContextDecorator], + parameters: { + formik: { + initialValues: { + openForms: { + dataSrc: 'referentielijsten', + itemsExpression: {code: 'table1', service: 'referentielijsten-api'}, + }, + data: {}, + }, + }, + builder: {enableContext: true}, + }, +}; diff --git a/src/registry/radio/radio-referentielijsten.stories.ts b/src/registry/radio/radio-referentielijsten.stories.ts new file mode 100644 index 00000000..096202fa --- /dev/null +++ b/src/registry/radio/radio-referentielijsten.stories.ts @@ -0,0 +1,158 @@ +import {Meta, StoryObj} from '@storybook/react'; +import {expect, fn, userEvent, within} from '@storybook/test'; + +import ComponentEditForm from '@/components/ComponentEditForm'; +import {BuilderContextDecorator} from '@/sb-decorators'; +import {rsSelect} from '@/utils/storybookTestHelpers'; + +export default { + title: 'Builder components/Radio/Referentielijsten', + component: ComponentEditForm, + decorators: [BuilderContextDecorator], + parameters: { + builder: {enableContext: true}, + }, + args: { + isNew: true, + component: { + id: 'wqimsadk', + type: 'radio', + key: 'radio', + label: 'A radio field', + dataSrc: 'values', + dataType: 'string', + openForms: { + dataSrc: 'manual', + translations: {}, + }, + data: {values: [{value: '', label: ''}]}, + values: [{value: '', label: ''}], + defaultValue: '', + }, + onCancel: fn(), + onRemove: fn(), + onSubmit: fn(), + builderInfo: { + title: 'Select', + icon: 'plus-square', + group: 'basic', + weight: 60, + schema: {}, + }, + }, +} as Meta; + +type Story = StoryObj; + +export const StoreValuesInComponent: Story = { + name: 'On save: store proper values in the component', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + code: 'tabel1', + dataSrc: 'referentielijsten', + service: 'referentielijsten', + translations: {}, + }, + type: 'radio', + }) + ); + }); + }, +}; + +export const SwitchToVariableResetOptions: Story = { + name: 'On switch from referentielijsten to variable: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'From variable'); + + const itemsExpressionInput = canvas.getByTestId('jsonEdit'); + await userEvent.clear(itemsExpressionInput); + await userEvent.type(itemsExpressionInput, '"foo"'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'variable', + itemsExpression: 'foo', + translations: {}, + }, + type: 'radio', + }) + ); + }); + }, +}; + +export const SwitchToManualResetOptions: Story = { + name: 'On switch from referentielijsten to manual: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'Manually fill in'); + + const labelInput = canvas.getByTestId('input-values[0].label'); + await userEvent.type(labelInput, 'Foo'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'manual', + translations: {}, + }, + type: 'radio', + values: [ + { + label: 'Foo', + value: 'foo', + openForms: { + translations: {}, + }, + }, + ], + }) + ); + }); + }, +}; diff --git a/src/registry/select/select-referentielijsten.stories.ts b/src/registry/select/select-referentielijsten.stories.ts new file mode 100644 index 00000000..bb3b3ce3 --- /dev/null +++ b/src/registry/select/select-referentielijsten.stories.ts @@ -0,0 +1,160 @@ +import {Meta, StoryObj} from '@storybook/react'; +import {expect, fn, userEvent, within} from '@storybook/test'; + +import ComponentEditForm from '@/components/ComponentEditForm'; +import {BuilderContextDecorator} from '@/sb-decorators'; +import {rsSelect} from '@/utils/storybookTestHelpers'; + +export default { + title: 'Builder components/Select/Referentielijsten', + component: ComponentEditForm, + decorators: [BuilderContextDecorator], + parameters: { + builder: {enableContext: true}, + }, + args: { + isNew: true, + component: { + id: 'wqimsadk', + type: 'select', + key: 'select', + label: 'A select field', + dataSrc: 'values', + dataType: 'string', + openForms: { + dataSrc: 'manual', + translations: {}, + }, + data: {values: [{value: '', label: ''}]}, + values: [{value: '', label: ''}], + defaultValue: '', + }, + onCancel: fn(), + onRemove: fn(), + onSubmit: fn(), + builderInfo: { + title: 'Select', + icon: 'plus-square', + group: 'basic', + weight: 60, + schema: {}, + }, + }, +} as Meta; + +type Story = StoryObj; + +export const StoreValuesInComponent: Story = { + name: 'On save: store proper values in the component', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + code: 'tabel1', + dataSrc: 'referentielijsten', + service: 'referentielijsten', + translations: {}, + }, + type: 'select', + }) + ); + }); + }, +}; + +export const SwitchToVariableResetOptions: Story = { + name: 'On switch from referentielijsten to variable: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'From variable'); + + const itemsExpressionInput = canvas.getByTestId('jsonEdit'); + await userEvent.clear(itemsExpressionInput); + await userEvent.type(itemsExpressionInput, '"foo"'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'variable', + itemsExpression: 'foo', + translations: {}, + }, + type: 'select', + }) + ); + }); + }, +}; + +export const SwitchToManualResetOptions: Story = { + name: 'On switch from referentielijsten to manual: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'Manually fill in'); + + const labelInput = canvas.getByTestId('input-data.values[0].label'); + await userEvent.type(labelInput, 'Foo'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'manual', + translations: {}, + }, + type: 'select', + data: { + values: [ + { + label: 'Foo', + value: 'foo', + openForms: { + translations: {}, + }, + }, + ], + }, + }) + ); + }); + }, +}; diff --git a/src/registry/selectboxes/selectboxes-referentielijsten.stories.ts b/src/registry/selectboxes/selectboxes-referentielijsten.stories.ts new file mode 100644 index 00000000..f59f8d5a --- /dev/null +++ b/src/registry/selectboxes/selectboxes-referentielijsten.stories.ts @@ -0,0 +1,155 @@ +import {Meta, StoryObj} from '@storybook/react'; +import {expect, fn, userEvent, within} from '@storybook/test'; + +import ComponentEditForm from '@/components/ComponentEditForm'; +import {BuilderContextDecorator} from '@/sb-decorators'; +import {rsSelect} from '@/utils/storybookTestHelpers'; + +export default { + title: 'Builder components/Selectboxes/Referentielijsten', + component: ComponentEditForm, + decorators: [BuilderContextDecorator], + parameters: { + builder: {enableContext: true}, + }, + args: { + isNew: true, + component: { + id: 'wqimsadk', + type: 'selectboxes', + key: 'selectboxes', + label: 'A selectboxes field', + openForms: { + dataSrc: 'manual', + translations: {}, + }, + values: [{value: '', label: ''}], + defaultValue: {}, + }, + onCancel: fn(), + onRemove: fn(), + onSubmit: fn(), + builderInfo: { + title: 'Select', + icon: 'plus-square', + group: 'basic', + weight: 60, + schema: {}, + }, + }, +} as Meta; + +type Story = StoryObj; + +export const StoreValuesInComponent: Story = { + name: 'On save: store proper values in the component', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + code: 'tabel1', + dataSrc: 'referentielijsten', + service: 'referentielijsten', + translations: {}, + }, + type: 'selectboxes', + }) + ); + }); + }, +}; + +export const SwitchToVariableResetOptions: Story = { + name: 'On switch from referentielijsten to variable: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'From variable'); + + const itemsExpressionInput = canvas.getByTestId('jsonEdit'); + await userEvent.clear(itemsExpressionInput); + await userEvent.type(itemsExpressionInput, '"foo"'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'variable', + itemsExpression: 'foo', + translations: {}, + }, + type: 'selectboxes', + }) + ); + }); + }, +}; + +export const SwitchToManualResetOptions: Story = { + name: 'On switch from referentielijsten to manual: reset the options', + play: async ({canvasElement, step, args}) => { + const canvas = within(canvasElement); + + await step('Fill in options', async () => { + const dataSourceInput = canvas.getByLabelText('Data source'); + await rsSelect(dataSourceInput, 'Referentielijsten API'); + + const serviceInput = canvas.getByLabelText('Referentielijsten service'); + await rsSelect(serviceInput, 'Referentielijsten'); + + const codeInput = canvas.getByLabelText('Referentielijsten table code'); + await userEvent.type(codeInput, 'tabel1'); + + await rsSelect(dataSourceInput, 'Manually fill in'); + + const labelInput = canvas.getByTestId('input-values[0].label'); + await userEvent.type(labelInput, 'Foo'); + + await userEvent.click(canvas.getByRole('button', {name: 'Save'})); + + expect(args.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + openForms: { + dataSrc: 'manual', + translations: {}, + }, + type: 'selectboxes', + values: [ + { + label: 'Foo', + value: 'foo', + openForms: { + translations: {}, + }, + }, + ], + }) + ); + }); + }, +}; diff --git a/src/tests/sharedUtils.tsx b/src/tests/sharedUtils.tsx index 9ca5b713..97f0df29 100644 --- a/src/tests/sharedUtils.tsx +++ b/src/tests/sharedUtils.tsx @@ -1,4 +1,5 @@ // This module contains shared utilities and constants between Jest and Storybook. +import {ReferentielijstenServiceOption} from '@/components/builder/values/referentielijsten/service'; import type {DocumentTypeOption, MapTileLayer, SelectOption} from '@/context'; import {ColorOption} from '@/registry/content/rich-text'; import {AnyComponentSchema} from '@/types'; @@ -33,6 +34,23 @@ export const DEFAULT_REGISTRATION_ATTRIBUTES: RegistrationAttributeOption[] = [ {id: 'attribute-2', label: 'Attribute 2'}, ]; +export const DEFAULT_SERVICES: ReferentielijstenServiceOption[] = [ + { + url: 'http://localhost:8000/api/v2/services/70', + slug: 'referentielijsten', + label: 'Referentielijsten', + apiRoot: 'http://localhost:8004/api/v1/', + apiType: 'orc', + }, + { + url: 'http://localhost:8000/api/v2/services/6', + slug: 'documenten', + label: 'Documenten', + apiRoot: 'http://localhost:8003/documenten/api/v1/', + apiType: 'drc', + }, +]; + export const DEFAULT_PREFILL_PLUGINS: PrefillPluginOption[] = [ {id: 'plugin-1', label: 'Plugin 1'}, {id: 'plugin-2', label: 'Plugin 2'}, diff --git a/src/utils/storybookTestHelpers.d.ts b/src/utils/storybookTestHelpers.d.ts new file mode 100644 index 00000000..3ef4dbeb --- /dev/null +++ b/src/utils/storybookTestHelpers.d.ts @@ -0,0 +1,34 @@ +declare module '@/utils/storybookTestHelpers' { + import {Screen} from '@testing-library/react'; + + /** + * Wrapper around selectEvent.select to ensure the portal option is used. + * + * @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; + + /** + * 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; +} diff --git a/src/utils/storybookTestHelpers.ts b/src/utils/storybookTestHelpers.ts new file mode 100644 index 00000000..2844db3f --- /dev/null +++ b/src/utils/storybookTestHelpers.ts @@ -0,0 +1,49 @@ +import {ByRoleOptions, Screen} from '@testing-library/react'; +import selectEvent from 'react-select-event'; + +const SB_ROOT: HTMLElement | null = document.getElementById('storybook-root'); + +/** + * Wrapper around selectEvent.select to ensure the portal option is used. + * + * @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 => { + 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 => { + return await canvas.findByRole('listbox', options); +}; + +export {rsSelect, getReactSelectContainer, findReactSelectMenu};