@@ -23,6 +23,38 @@ const CompControlled = () => {
2323 ) ;
2424} ;
2525
26+ type ControlledWithTriggerProps = {
27+ isInitiallyOpen : boolean ;
28+ onOpen ?: ( ) => void ;
29+ onClose ?: ( ) => void ;
30+ } ;
31+
32+ const CompControlledWithTrigger = ( props : ControlledWithTriggerProps ) => {
33+ const [ open , setOpen ] = useState ( props . isInitiallyOpen ) ;
34+
35+ return (
36+ < Popover . TriggerContext >
37+ < Popover . Trigger >
38+ Dropdown
39+ { open ? < ChevronDownIcon aria-hidden /> : < ChevronUpIcon aria-hidden /> }
40+ </ Popover . Trigger >
41+ < Popover
42+ open = { open }
43+ onOpen = { ( ) => {
44+ props . onOpen ?.( ) ;
45+ setOpen ( true ) ;
46+ } }
47+ onClose = { ( ) => {
48+ props . onClose ?.( ) ;
49+ setOpen ( false ) ;
50+ } }
51+ >
52+ { contentText }
53+ </ Popover >
54+ </ Popover . TriggerContext >
55+ ) ;
56+ } ;
57+
2658const contentText = 'popover content' ;
2759
2860const render = async ( props : PopoverProps = { } ) => {
@@ -41,6 +73,19 @@ const render = async (props: PopoverProps = {}) => {
4173 } ;
4274} ;
4375
76+ const renderControlledWithTrigger = async (
77+ props : ControlledWithTriggerProps ,
78+ ) => {
79+ /* Flush microtasks */
80+ await act ( async ( ) => { } ) ;
81+ const user = userEvent . setup ( ) ;
82+
83+ return {
84+ user,
85+ ...renderRtl ( < CompControlledWithTrigger { ...props } /> ) ,
86+ } ;
87+ } ;
88+
4489describe ( 'Popover' , ( ) => {
4590 it ( 'should render popover on trigger-click when closed' , async ( ) => {
4691 const { user } = await render ( ) ;
@@ -177,4 +222,48 @@ describe('Popover', () => {
177222 await act ( async ( ) => await user . click ( document . body ) ) ;
178223 expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
179224 } ) ;
225+
226+ it ( 'should not call onOpen when the popover is open' , async ( ) => {
227+ const onOpen = vi . fn ( ) ;
228+ const { user } = await render ( { onOpen } ) ;
229+ const popoverTrigger = screen . getByRole ( 'button' ) ;
230+
231+ await act ( async ( ) => await user . click ( document . body ) ) ;
232+ expect ( onOpen ) . toHaveBeenCalledTimes ( 0 ) ;
233+
234+ await act ( async ( ) => await user . click ( popoverTrigger ) ) ;
235+ expect ( screen . queryByText ( contentText ) ) . toBeVisible ( ) ;
236+ expect ( onOpen ) . toHaveBeenCalledTimes ( 1 ) ;
237+ await act ( async ( ) => await user . click ( popoverTrigger ) ) ;
238+ expect ( onOpen ) . toHaveBeenCalledTimes ( 1 ) ;
239+ } ) ;
240+
241+ describe ( 'with controlled state' , ( ) => {
242+ it ( 'should not call onClose when the popover is closed' , async ( ) => {
243+ const onClose = vi . fn ( ) ;
244+ const { user } = await renderControlledWithTrigger ( {
245+ isInitiallyOpen : false ,
246+ onClose,
247+ } ) ;
248+ const popoverTrigger = screen . getByRole ( 'button' ) ;
249+
250+ await act ( async ( ) => await user . click ( popoverTrigger ) ) ;
251+ expect ( onClose ) . toHaveBeenCalledTimes ( 0 ) ;
252+ await act ( async ( ) => await user . click ( popoverTrigger ) ) ;
253+ expect ( onClose ) . toHaveBeenCalledTimes ( 1 ) ;
254+ } ) ;
255+
256+ it ( 'should not call onOpen when the popover is open' , async ( ) => {
257+ const onOpen = vi . fn ( ) ;
258+ const { user } = await renderControlledWithTrigger ( {
259+ isInitiallyOpen : true ,
260+ onOpen,
261+ } ) ;
262+ const popoverTrigger = screen . getByRole ( 'button' ) ;
263+
264+ expect ( screen . queryByText ( contentText ) ) . toBeVisible ( ) ;
265+ await act ( async ( ) => await user . click ( popoverTrigger ) ) ;
266+ expect ( onOpen ) . toHaveBeenCalledTimes ( 0 ) ;
267+ } ) ;
268+ } ) ;
180269} ) ;
0 commit comments