Skip to content

Commit e797764

Browse files
Add onClose regression tests
1 parent 552533d commit e797764

1 file changed

Lines changed: 84 additions & 0 deletions

File tree

src/components/Drawer.test.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,88 @@ describe('Drawer', () => {
126126
expect(onOpenChange).toHaveBeenCalledWith(false)
127127
})
128128
})
129+
130+
describe('onClose transition semantics', () => {
131+
it('does not call onClose when initially mounted with open=false', () => {
132+
const onClose = vi.fn()
133+
render(
134+
<Drawer open={false} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
135+
<Drawer.Content>Body</Drawer.Content>
136+
</Drawer>,
137+
)
138+
expect(onClose).not.toHaveBeenCalled()
139+
})
140+
141+
it('calls onClose exactly once when controlled open transitions true → false', async () => {
142+
const onClose = vi.fn()
143+
const { rerender } = render(
144+
<Drawer open={true} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
145+
<Drawer.Content>Body</Drawer.Content>
146+
</Drawer>,
147+
)
148+
await screen.findByRole('dialog')
149+
expect(onClose).not.toHaveBeenCalled()
150+
151+
rerender(
152+
<Drawer open={false} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
153+
<Drawer.Content>Body</Drawer.Content>
154+
</Drawer>,
155+
)
156+
157+
await waitFor(() => {
158+
expect(onClose).toHaveBeenCalledTimes(1)
159+
})
160+
})
161+
162+
it('does not call onClose on subsequent renders when already closed', async () => {
163+
const onClose = vi.fn()
164+
const { rerender } = render(
165+
<Drawer open={true} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
166+
<Drawer.Content>Body</Drawer.Content>
167+
</Drawer>,
168+
)
169+
await screen.findByRole('dialog')
170+
171+
rerender(
172+
<Drawer open={false} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
173+
<Drawer.Content>Body</Drawer.Content>
174+
</Drawer>,
175+
)
176+
await waitFor(() => {
177+
expect(onClose).toHaveBeenCalledTimes(1)
178+
})
179+
180+
// Re-render again while still closed — onClose must not fire again
181+
rerender(
182+
<Drawer open={false} onOpenChange={vi.fn()} onClose={onClose} snapPoints={['full']}>
183+
<Drawer.Content>Body</Drawer.Content>
184+
</Drawer>,
185+
)
186+
expect(onClose).toHaveBeenCalledTimes(1)
187+
})
188+
189+
it('calls onClose exactly once in the uncontrolled flow when closed via overlay click', async () => {
190+
const onClose = vi.fn()
191+
const onOpenChange = vi.fn()
192+
const user = userEvent.setup()
193+
194+
render(
195+
<Drawer defaultOpen onOpenChange={onOpenChange} onClose={onClose} snapPoints={['full']}>
196+
<Drawer.Content>Body</Drawer.Content>
197+
</Drawer>,
198+
)
199+
await screen.findByRole('dialog')
200+
expect(onClose).not.toHaveBeenCalled()
201+
202+
const [overlay] = screen.getAllByRole('button', { hidden: true })
203+
if (!overlay) expect.fail('expected modal overlay button')
204+
await user.click(overlay)
205+
206+
await waitFor(() => {
207+
expect(onClose).toHaveBeenCalledTimes(1)
208+
})
209+
// Ensure no extra calls after the transition settles
210+
expect(onClose).toHaveBeenCalledTimes(1)
211+
})
212+
})
129213
})

0 commit comments

Comments
 (0)