From d57bd8d906ec8f943285798269bbbfd84d2da3eb Mon Sep 17 00:00:00 2001 From: Reid Barber Date: Thu, 26 Sep 2024 18:24:16 -0500 Subject: [PATCH] fix+tests: fix Disclosure bugs and add tests (#7096) * add disclosure tests * lint/cleanup * Pass isExpanded to useDisclosure as well --------- Co-authored-by: Robert Snow Co-authored-by: Devon Govett --- .../disclosure/src/useDisclosure.ts | 10 +- .../disclosure/test/useDisclosure.test.ts | 184 ++++++++ .../test/useDisclosureGroupState.test.ts | 106 +++++ .../test/useDisclosureState.test.ts | 81 ++++ .../react-aria-components/src/Disclosure.tsx | 4 +- .../test/Disclosure.test.js | 421 ++++++++++++++++++ 6 files changed, 799 insertions(+), 7 deletions(-) create mode 100644 packages/@react-aria/disclosure/test/useDisclosure.test.ts create mode 100644 packages/@react-stately/disclosure/test/useDisclosureGroupState.test.ts create mode 100644 packages/@react-stately/disclosure/test/useDisclosureState.test.ts create mode 100644 packages/react-aria-components/test/Disclosure.test.js diff --git a/packages/@react-aria/disclosure/src/useDisclosure.ts b/packages/@react-aria/disclosure/src/useDisclosure.ts index 81d3d7e3694..20dce9d366e 100644 --- a/packages/@react-aria/disclosure/src/useDisclosure.ts +++ b/packages/@react-aria/disclosure/src/useDisclosure.ts @@ -51,17 +51,15 @@ export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState let supportsBeforeMatch = !isSSR && 'onbeforematch' in document.body; // @ts-ignore https://github.com/facebook/react/pull/24741 - useEvent(ref, 'beforematch', supportsBeforeMatch ? () => state.expand() : null); + useEvent(ref, 'beforematch', supportsBeforeMatch && !isControlled ? () => state.expand() : null); useEffect(() => { // Until React supports hidden="until-found": https://github.com/facebook/react/pull/24741 if (supportsBeforeMatch && ref?.current && !isControlled && !isDisabled) { if (state.isExpanded) { - // @ts-ignore - ref.current.hidden = undefined; + ref.current.removeAttribute('hidden'); } else { - // @ts-ignore - ref.current.hidden = 'until-found'; + ref.current.setAttribute('hidden', 'until-found'); } } }, [isControlled, ref, props.isExpanded, state, supportsBeforeMatch, isDisabled]); @@ -72,7 +70,7 @@ export function useDisclosure(props: AriaDisclosureProps, state: DisclosureState 'aria-expanded': state.isExpanded, 'aria-controls': contentId, onPress: (e) => { - if (e.pointerType !== 'keyboard') { + if (!isDisabled && e.pointerType !== 'keyboard') { state.toggle(); } }, diff --git a/packages/@react-aria/disclosure/test/useDisclosure.test.ts b/packages/@react-aria/disclosure/test/useDisclosure.test.ts new file mode 100644 index 00000000000..a497d1529a1 --- /dev/null +++ b/packages/@react-aria/disclosure/test/useDisclosure.test.ts @@ -0,0 +1,184 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ +import {actHook as act, renderHook} from '@react-spectrum/test-utils-internal'; +import {KeyboardEvent, PressEvent} from '@react-types/shared'; +import {useDisclosure} from '../src/useDisclosure'; +import {useDisclosureState} from '@react-stately/disclosure'; + +describe('useDisclosure', () => { + let defaultProps = {}; + let ref = {current: document.createElement('div')}; + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should return correct aria attributes when collapsed', () => { + let {result} = renderHook(() => { + let state = useDisclosureState(defaultProps); + return useDisclosure({}, state, ref); + }); + + let {buttonProps, panelProps} = result.current; + + expect(buttonProps['aria-expanded']).toBe(false); + expect(panelProps.hidden).toBe(true); + }); + + it('should return correct aria attributes when expanded', () => { + let {result} = renderHook(() => { + let state = useDisclosureState({defaultExpanded: true}); + return useDisclosure({}, state, ref); + }); + + let {buttonProps, panelProps} = result.current; + + expect(buttonProps['aria-expanded']).toBe(true); + expect(panelProps.hidden).toBe(false); + }); + + it('should handle expanding on press event', () => { + let {result} = renderHook(() => { + let state = useDisclosureState({}); + let disclosure = useDisclosure({}, state, ref); + return {state, disclosure}; + }); + + act(() => { + result.current.disclosure.buttonProps.onPress?.({pointerType: 'mouse'} as PressEvent); + }); + + expect(result.current.state.isExpanded).toBe(true); + }); + + it('should handle expanding on keydown event', () => { + let {result} = renderHook(() => { + let state = useDisclosureState({}); + let disclosure = useDisclosure({}, state, ref); + return {state, disclosure}; + }); + + let preventDefault = jest.fn(); + let event = (e: Partial) => ({...e, preventDefault} as KeyboardEvent); + + act(() => { + result.current.disclosure.buttonProps.onKeyDown?.(event({key: 'Enter', preventDefault}) as KeyboardEvent); + }); + + expect(preventDefault).toHaveBeenCalledTimes(1); + + expect(result.current.state.isExpanded).toBe(true); + }); + + it('should not toggle when disabled', () => { + let {result} = renderHook(() => { + let state = useDisclosureState({}); + let disclosure = useDisclosure({isDisabled: true}, state, ref); + return {state, disclosure}; + }); + + act(() => { + result.current.disclosure.buttonProps.onPress?.({pointerType: 'mouse'} as PressEvent); + }); + + expect(result.current.state.isExpanded).toBe(false); + }); + + it('should set correct IDs for accessibility', () => { + let {result} = renderHook(() => { + let state = useDisclosureState({}); + return useDisclosure({}, state, ref); + }); + + let {buttonProps, panelProps} = result.current; + + expect(buttonProps['aria-controls']).toBe(panelProps.id); + expect(panelProps['aria-labelledby']).toBe(buttonProps.id); + }); + + it('should expand when beforematch event occurs', () => { + // Mock 'onbeforematch' support on document.body + // @ts-ignore + const originalOnBeforeMatch = document.body.onbeforematch; + Object.defineProperty(document.body, 'onbeforematch', { + value: null, + writable: true, + configurable: true + }); + + const ref = {current: document.createElement('div')}; + + const {result} = renderHook(() => { + const state = useDisclosureState({}); + const disclosure = useDisclosure({}, state, ref); + return {state, disclosure}; + }); + + expect(result.current.state.isExpanded).toBe(false); + expect(ref.current.getAttribute('hidden')).toBe('until-found'); + + // Simulate the 'beforematch' event + act(() => { + const event = new Event('beforematch', {bubbles: true}); + ref.current.dispatchEvent(event); + }); + + expect(result.current.state.isExpanded).toBe(true); + expect(ref.current.hasAttribute('hidden')).toBe(false); + + Object.defineProperty(document.body, 'onbeforematch', { + value: originalOnBeforeMatch, + writable: true, + configurable: true + }); + }); + + it('should not expand when beforematch event occurs if controlled and closed', () => { + // Mock 'onbeforematch' support on document.body + // @ts-ignore + const originalOnBeforeMatch = document.body.onbeforematch; + Object.defineProperty(document.body, 'onbeforematch', { + value: null, + writable: true, + configurable: true + }); + + const ref = {current: document.createElement('div')}; + + const onExpandedChange = jest.fn(); + + const {result} = renderHook(() => { + const state = useDisclosureState({isExpanded: false, onExpandedChange}); + const disclosure = useDisclosure({isExpanded: false}, state, ref); + return {state, disclosure}; + }); + + expect(result.current.state.isExpanded).toBe(false); + expect(ref.current.getAttribute('hidden')).toBeNull(); + + // Simulate the 'beforematch' event + act(() => { + const event = new Event('beforematch', {bubbles: true}); + ref.current.dispatchEvent(event); + }); + + expect(result.current.state.isExpanded).toBe(false); + expect(ref.current.getAttribute('hidden')).toBeNull(); + expect(onExpandedChange).not.toHaveBeenCalled(); + + Object.defineProperty(document.body, 'onbeforematch', { + value: originalOnBeforeMatch, + writable: true, + configurable: true + }); + }); +}); diff --git a/packages/@react-stately/disclosure/test/useDisclosureGroupState.test.ts b/packages/@react-stately/disclosure/test/useDisclosureGroupState.test.ts new file mode 100644 index 00000000000..23c9291efbc --- /dev/null +++ b/packages/@react-stately/disclosure/test/useDisclosureGroupState.test.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {actHook as act, renderHook} from '@react-spectrum/test-utils-internal'; +import {useDisclosureGroupState} from '../src/useDisclosureGroupState'; + +describe('useDisclosureGroupState', () => { + it('should initialize with empty expandedKeys when not provided', () => { + const {result} = renderHook(() => useDisclosureGroupState({})); + expect(result.current.expandedKeys.size).toBe(0); + }); + + it('should initialize with defaultExpandedKeys when provided', () => { + const {result} = renderHook(() => + useDisclosureGroupState({defaultExpandedKeys: ['item1']}) + ); + expect(result.current.expandedKeys.has('item1')).toBe(true); + expect(result.current.expandedKeys.has('item2')).toBe(false); + }); + + it('should initialize with multiple defaultExpandedKeys when provided, and if allowsMultipleExpanded is true', () => { + const {result} = renderHook(() => + useDisclosureGroupState({defaultExpandedKeys: ['item1', 'item2'], allowsMultipleExpanded: true}) + ); + expect(result.current.expandedKeys.has('item1')).toBe(true); + expect(result.current.expandedKeys.has('item2')).toBe(true); + }); + + it('should allow controlled expandedKeys prop', () => { + const {result, rerender} = renderHook( + ({expandedKeys}) => useDisclosureGroupState({expandedKeys}), + {initialProps: {expandedKeys: ['item1']}} + ); + expect(result.current.expandedKeys.has('item1')).toBe(true); + + rerender({expandedKeys: ['item2']}); + expect(result.current.expandedKeys.has('item1')).toBe(false); + expect(result.current.expandedKeys.has('item2')).toBe(true); + }); + + it('should toggle key correctly when allowsMultipleExpanded is false', () => { + const {result} = renderHook(() => useDisclosureGroupState({})); + act(() => { + result.current.toggleKey('item1'); + }); + expect(result.current.expandedKeys.has('item1')).toBe(true); + + act(() => { + result.current.toggleKey('item2'); + }); + expect(result.current.expandedKeys.has('item1')).toBe(false); + expect(result.current.expandedKeys.has('item2')).toBe(true); + }); + + it('should toggle key correctly when allowsMultipleExpanded is true', () => { + const {result} = renderHook(() => + useDisclosureGroupState({allowsMultipleExpanded: true}) + ); + act(() => { + result.current.toggleKey('item1'); + }); + expect(result.current.expandedKeys.has('item1')).toBe(true); + + act(() => { + result.current.toggleKey('item2'); + }); + expect(result.current.expandedKeys.has('item1')).toBe(true); + expect(result.current.expandedKeys.has('item2')).toBe(true); + }); + + it('should call onExpandedChange when expanded keys change', () => { + const onExpandedChange = jest.fn(); + const {result} = renderHook(() => + useDisclosureGroupState({onExpandedChange}) + ); + + act(() => { + result.current.toggleKey('item1'); + }); + expect(onExpandedChange).toHaveBeenCalledWith(new Set(['item1'])); + }); + + it('should not expand more than one key when allowsMultipleExpanded is false', () => { + const {result} = renderHook(() => useDisclosureGroupState({})); + act(() => { + result.current.toggleKey('item1'); + result.current.toggleKey('item2'); + }); + expect(result.current.expandedKeys.size).toBe(1); + expect(result.current.expandedKeys.has('item2')).toBe(true); + }); + + it('should respect isDisabled prop', () => { + const {result} = renderHook(() => useDisclosureGroupState({isDisabled: true})); + expect(result.current.isDisabled).toBe(true); + }); +}); diff --git a/packages/@react-stately/disclosure/test/useDisclosureState.test.ts b/packages/@react-stately/disclosure/test/useDisclosureState.test.ts new file mode 100644 index 00000000000..d6a74523079 --- /dev/null +++ b/packages/@react-stately/disclosure/test/useDisclosureState.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {actHook as act, renderHook} from '@react-spectrum/test-utils-internal'; +import {useDisclosureState} from '../src/useDisclosureState'; + +describe('useDisclosureState', () => { + it('should initialize as not expanded by default', () => { + const {result} = renderHook(() => useDisclosureState({})); + expect(result.current.isExpanded).toBe(false); + }); + + it('should initialize with defaultExpanded when provided', () => { + const {result} = renderHook(() => useDisclosureState({defaultExpanded: true})); + expect(result.current.isExpanded).toBe(true); + }); + + it('should allow controlled isExpanded prop', () => { + const {result, rerender} = renderHook( + ({isExpanded}) => useDisclosureState({isExpanded}), + {initialProps: {isExpanded: true}} + ); + expect(result.current.isExpanded).toBe(true); + + rerender({isExpanded: false}); + expect(result.current.isExpanded).toBe(false); + }); + + it('should call onExpandedChange when expanded state changes', () => { + const onExpandedChange = jest.fn(); + const {result} = renderHook(() => + useDisclosureState({onExpandedChange}) + ); + + act(() => { + result.current.expand(); + }); + expect(onExpandedChange).toHaveBeenCalledWith(true); + + act(() => { + result.current.collapse(); + }); + expect(onExpandedChange).toHaveBeenCalledWith(false); + }); + + it('should toggle expanded state correctly', () => { + const {result} = renderHook(() => useDisclosureState({})); + + act(() => { + result.current.toggle(); + }); + expect(result.current.isExpanded).toBe(true); + + act(() => { + result.current.toggle(); + }); + expect(result.current.isExpanded).toBe(false); + }); + + it('should not change state when controlled', () => { + const onExpandedChange = jest.fn(); + const {result} = renderHook(() => + useDisclosureState({isExpanded: false, onExpandedChange}) + ); + + act(() => { + result.current.expand(); + }); + expect(result.current.isExpanded).toBe(false); + expect(onExpandedChange).toHaveBeenCalledWith(true); + }); +}); diff --git a/packages/react-aria-components/src/Disclosure.tsx b/packages/react-aria-components/src/Disclosure.tsx index 8fc4a461277..1c37b4c27ba 100644 --- a/packages/react-aria-components/src/Disclosure.tsx +++ b/packages/react-aria-components/src/Disclosure.tsx @@ -118,9 +118,10 @@ function Disclosure(props: DisclosureProps, ref: ForwardedRef) { let defaultId = useId(); id ||= defaultId; + let isExpanded = groupState ? groupState.expandedKeys.has(id) : props.isExpanded; let state = useDisclosureState({ ...props, - isExpanded: groupState ? groupState.expandedKeys.has(id) : props.isExpanded, + isExpanded, onExpandedChange(isExpanded) { if (groupState) { groupState.toggleKey(id); @@ -134,6 +135,7 @@ function Disclosure(props: DisclosureProps, ref: ForwardedRef) { let isDisabled = props.isDisabled || groupState?.isDisabled || false; let {buttonProps, panelProps} = useDisclosure({ ...props, + isExpanded, isDisabled }, state, panelRef); let { diff --git a/packages/react-aria-components/test/Disclosure.test.js b/packages/react-aria-components/test/Disclosure.test.js new file mode 100644 index 00000000000..5bd9ac72a09 --- /dev/null +++ b/packages/react-aria-components/test/Disclosure.test.js @@ -0,0 +1,421 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { + Button, + UNSTABLE_Disclosure as Disclosure, + UNSTABLE_DisclosureGroup as DisclosureGroup, + UNSTABLE_DisclosurePanel as DisclosurePanel, + Heading +} from 'react-aria-components'; +import React from 'react'; +import {render} from '@react-spectrum/test-utils-internal'; +import userEvent from '@testing-library/user-event'; + +describe('Disclosure', () => { + let user; + beforeAll(() => { + user = userEvent.setup({delay: null}); + jest.useFakeTimers(); + }); + + it('should render a Disclosure with default class', () => { + const {container} = render( + + + + + +

Content

+
+
+ ); + const disclosure = container.querySelector('.react-aria-Disclosure'); + expect(disclosure).toBeInTheDocument(); + }); + + it('should render a Disclosure with custom class', () => { + const {getByTestId} = render( + + + + + +

Content

+
+
+ ); + const disclosure = getByTestId('disclosure'); + expect(disclosure).toHaveClass('test-class'); + }); + + it('should support DOM props', () => { + const {getByTestId} = render( + + + + + +

Content

+
+
+ ); + const disclosure = getByTestId('disclosure'); + expect(disclosure).toHaveAttribute('data-foo', 'bar'); + }); + + it('should support disabled state', () => { + const {getByTestId, getByRole} = render( + + + + + +

Content

+
+
+ ); + const disclosure = getByTestId('disclosure'); + expect(disclosure).toHaveAttribute('data-disabled', 'true'); + + const button = getByRole('button'); + expect(button).toHaveAttribute('disabled'); + expect(button).toHaveAttribute('data-disabled', 'true'); + }); + + it('should support controlled isExpanded prop', async () => { + const onExpandedChange = jest.fn(); + const {getByTestId, getByRole, queryByText} = render( + + + + + +

Content

+
+
+ ); + const disclosure = getByTestId('disclosure'); + const panel = queryByText('Content'); + expect(disclosure).toHaveAttribute('data-expanded', 'true'); + expect(panel).toBeVisible(); + + const button = getByRole('button'); + await user.click(button); + + expect(onExpandedChange).toHaveBeenCalledWith(false); + expect(disclosure).toHaveAttribute('data-expanded', 'true'); + expect(panel).toBeVisible(); + }); + + it('should toggle expanded state when trigger is clicked', async () => { + const {getByTestId, getByRole, queryByText} = render( + + + + + +

Content

+
+
+ ); + const disclosure = getByTestId('disclosure'); + const panel = queryByText('Content'); + expect(disclosure).not.toHaveAttribute('data-expanded'); + expect(panel).not.toBeVisible(); + + const button = getByRole('button'); + await user.click(button); + + expect(disclosure).toHaveAttribute('data-expanded', 'true'); + expect(panel).toBeVisible(); + + await user.click(button); + + expect(disclosure).not.toHaveAttribute('data-expanded'); + expect(panel).not.toBeVisible(); + }); + + it('should support render props', async () => { + const {getByRole, getByText} = render( + + {({isExpanded}) => ( + <> + + + + +

Content

+
+ + )} +
+ ); + const button = getByRole('button'); + const panel = getByText('Content'); + expect(button).toHaveTextContent('Expand'); + + await user.click(button); + expect(button).toHaveTextContent('Collapse'); + expect(panel).toBeVisible(); + }); + + it('should support focus ring', async () => { + const {getByTestId, getByRole} = render( + + isFocusVisibleWithin ? 'focus' : '' + }> + + + + +

Content

+
+
+ ); + + const disclosure = getByTestId('disclosure'); + const button = getByRole('button'); + + expect(disclosure).not.toHaveAttribute('data-focus-visible-within'); + expect(disclosure).not.toHaveClass('focus'); + + await user.tab(); + expect(document.activeElement).toBe(button); + + expect(disclosure).toHaveAttribute('data-focus-visible-within', 'true'); + expect(disclosure).toHaveClass('focus'); + + await user.tab(); + expect(disclosure).not.toHaveAttribute('data-focus-visible-within'); + expect(disclosure).not.toHaveClass('focus'); + }); +}); + +describe('DisclosureGroup', () => { + let user; + beforeAll(() => { + user = userEvent.setup({delay: null}); + jest.useFakeTimers(); + }); + + it('should render a DisclosureGroup with default class', () => { + const {container} = render( + + + + + + +

Content 1

+
+
+ + + + + +

Content 2

+
+
+
+ ); + const group = container.querySelector('.react-aria-DisclosureGroup'); + expect(group).toBeInTheDocument(); + }); + + it('should only allow one Disclosure to be expanded at a time by default', async () => { + const {getByText, queryByText} = render( + + + + + + +

Content 1

+
+
+ + + + + +

Content 2

+
+
+
+ ); + + const trigger1 = getByText('Trigger 1'); + const trigger2 = getByText('Trigger 2'); + let panel1 = queryByText('Content 1'); + let panel2 = queryByText('Content 2'); + + expect(panel1).not.toBeVisible(); + expect(panel2).not.toBeVisible(); + + await user.click(trigger1); + expect(panel1).toBeVisible(); + expect(panel2).not.toBeVisible(); + + await user.click(trigger2); + expect(panel1).not.toBeVisible(); + expect(panel2).toBeVisible(); + + await user.click(trigger2); + expect(panel1).not.toBeVisible(); + expect(panel2).not.toBeVisible(); + }); + + it('should allow multiple Disclosures to be expanded when allowsMultipleExpanded is true', async () => { + const {getByText, queryByText} = render( + + + + + + +

Content 1

+
+
+ + + + + +

Content 2

+
+
+
+ ); + + const trigger1 = getByText('Trigger 1'); + const trigger2 = getByText('Trigger 2'); + let panel1 = queryByText('Content 1'); + let panel2 = queryByText('Content 2'); + + expect(panel1).not.toBeVisible(); + expect(panel2).not.toBeVisible(); + + await user.click(trigger1); + expect(panel1).toBeVisible(); + expect(panel2).not.toBeVisible(); + + await user.click(trigger2); + expect(panel1).toBeVisible(); + expect(panel2).toBeVisible(); + }); + + it('should support controlled expandedKeys prop', async () => { + function ControlledDisclosureGroup() { + const [expandedKeys, setExpandedKeys] = React.useState(['item1']); + return ( + <> + + + + + + + +

Content 1

+
+
+ + + + + +

Content 2

+
+
+
+ + ); + } + + const {getByText, queryByText} = render(); + + let panel1 = queryByText('Content 1'); + let panel2 = queryByText('Content 2'); + + expect(panel1).toBeVisible(); + expect(panel2).not.toBeVisible(); + + await user.click(getByText('Expand item2')); + + expect(panel1).not.toBeVisible(); + expect(panel2).toBeVisible(); + }); + + it('should call onExpandedChange when a Disclosure is toggled', async () => { + const onExpandedChange = jest.fn(); + const {getByText} = render( + + + + + + +

Content 1

+
+
+
+ ); + + const trigger1 = getByText('Trigger 1'); + + await user.click(trigger1); + + expect(onExpandedChange).toHaveBeenCalledTimes(1); + const expandedKeys = onExpandedChange.mock.calls[0][0]; + expect(expandedKeys.has('item1')).toBe(true); + }); + + it('should disable all Disclosures when DisclosureGroup is disabled', () => { + const {getAllByRole} = render( + + + + + + +

Content 1

+
+
+ + + + + +

Content 2

+
+
+
+ ); + + const buttons = getAllByRole('button'); + buttons.forEach((button) => { + expect(button).toHaveAttribute('data-disabled', 'true'); + expect(button).toHaveAttribute('disabled'); + }); + }); +});