MobileSheetNav.test.tsx199 lines · main
1import { act, render, screen, waitFor } from '@testing-library/react'
2import { useEffect, useState } from 'react'
3import { beforeEach, describe, expect, it, vi } from 'vitest'
4
5import { MobileSheetNav } from './MobileSheetNav'
6
7const mockRouter = vi.hoisted(() => ({ asPath: '/initial' }))
8const mockWindowSize = vi.hoisted(() => ({ width: 400 }))
9
10vi.mock('next/router', () => ({
11 useRouter: () => mockRouter,
12}))
13
14vi.mock('react-use', () => ({
15 useWindowSize: () => mockWindowSize,
16}))
17
18function MobileSheetNavWithState({
19 shouldCloseOnRouteChange = true,
20 shouldCloseOnViewportResize = true,
21}: {
22 shouldCloseOnRouteChange?: boolean
23 shouldCloseOnViewportResize?: boolean
24}) {
25 const [open, setOpen] = useState(false)
26 useEffect(() => {
27 setOpen(true)
28 }, [])
29 return (
30 <>
31 <span data-testid="sheet-open">{String(open)}</span>
32 <MobileSheetNav
33 open={open}
34 onOpenChange={setOpen}
35 shouldCloseOnRouteChange={shouldCloseOnRouteChange}
36 shouldCloseOnViewportResize={shouldCloseOnViewportResize}
37 >
38 <div>Nav content</div>
39 </MobileSheetNav>
40 </>
41 )
42}
43
44describe('MobileSheetNav', () => {
45 const defaultProps = {
46 onOpenChange: vi.fn(),
47 children: <div>Nav content</div>,
48 }
49
50 beforeEach(() => {
51 vi.clearAllMocks()
52 mockRouter.asPath = '/initial'
53 mockWindowSize.width = 400
54 })
55
56 describe('shouldCloseOnRouteChange', () => {
57 it('calls onOpenChange(false) when route changes and shouldCloseOnRouteChange is true (default)', () => {
58 const onOpenChange = vi.fn()
59 const { rerender } = render(<MobileSheetNav {...defaultProps} onOpenChange={onOpenChange} />)
60
61 onOpenChange.mockClear()
62 mockRouter.asPath = '/other-page'
63 act(() => {
64 rerender(<MobileSheetNav {...defaultProps} onOpenChange={onOpenChange} />)
65 })
66
67 expect(onOpenChange).toHaveBeenCalledWith(false)
68 })
69
70 it('effectively closes the sheet on route change when shouldCloseOnRouteChange is true', async () => {
71 const { rerender } = render(<MobileSheetNavWithState />)
72
73 await waitFor(() => {
74 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
75 })
76
77 mockRouter.asPath = '/other-page'
78 act(() => {
79 rerender(<MobileSheetNavWithState />)
80 })
81
82 await waitFor(() => {
83 expect(screen.getByTestId('sheet-open')).toHaveTextContent('false')
84 })
85 })
86
87 it('effectively does NOT close the sheet on route change when shouldCloseOnRouteChange is false', async () => {
88 const { rerender } = render(<MobileSheetNavWithState shouldCloseOnRouteChange={false} />)
89
90 await waitFor(() => {
91 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
92 })
93
94 mockRouter.asPath = '/other-page'
95 act(() => {
96 rerender(<MobileSheetNavWithState shouldCloseOnRouteChange={false} />)
97 })
98
99 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
100 })
101
102 it('does not call onOpenChange when route changes if shouldCloseOnRouteChange is false', () => {
103 const onOpenChange = vi.fn()
104 const { rerender } = render(
105 <MobileSheetNav
106 {...defaultProps}
107 onOpenChange={onOpenChange}
108 shouldCloseOnRouteChange={false}
109 />
110 )
111
112 onOpenChange.mockClear()
113 mockRouter.asPath = '/other-page'
114 act(() => {
115 rerender(
116 <MobileSheetNav
117 {...defaultProps}
118 onOpenChange={onOpenChange}
119 shouldCloseOnRouteChange={false}
120 />
121 )
122 })
123
124 expect(onOpenChange).not.toHaveBeenCalled()
125 })
126 })
127
128 describe('shouldCloseOnViewportResize', () => {
129 it('calls onOpenChange(false) when width changes and shouldCloseOnViewportResize is true (default)', () => {
130 const onOpenChange = vi.fn()
131 const { rerender } = render(<MobileSheetNav {...defaultProps} onOpenChange={onOpenChange} />)
132
133 onOpenChange.mockClear()
134 mockWindowSize.width = 800
135 act(() => {
136 rerender(<MobileSheetNav {...defaultProps} onOpenChange={onOpenChange} />)
137 })
138
139 expect(onOpenChange).toHaveBeenCalledWith(false)
140 })
141
142 it('effectively closes the sheet on viewport resize when shouldCloseOnViewportResize is true', async () => {
143 const { rerender } = render(<MobileSheetNavWithState />)
144
145 await waitFor(() => {
146 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
147 })
148
149 mockWindowSize.width = 800
150 act(() => {
151 rerender(<MobileSheetNavWithState />)
152 })
153
154 await waitFor(() => {
155 expect(screen.getByTestId('sheet-open')).toHaveTextContent('false')
156 })
157 })
158
159 it('effectively does NOT close the sheet on viewport resize when shouldCloseOnViewportResize is false', async () => {
160 const { rerender } = render(<MobileSheetNavWithState shouldCloseOnViewportResize={false} />)
161
162 await waitFor(() => {
163 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
164 })
165
166 mockWindowSize.width = 800
167 act(() => {
168 rerender(<MobileSheetNavWithState shouldCloseOnViewportResize={false} />)
169 })
170
171 expect(screen.getByTestId('sheet-open')).toHaveTextContent('true')
172 })
173
174 it('does not call onOpenChange when width changes if shouldCloseOnViewportResize is false', () => {
175 const onOpenChange = vi.fn()
176 const { rerender } = render(
177 <MobileSheetNav
178 {...defaultProps}
179 onOpenChange={onOpenChange}
180 shouldCloseOnViewportResize={false}
181 />
182 )
183
184 onOpenChange.mockClear()
185 mockWindowSize.width = 800
186 act(() => {
187 rerender(
188 <MobileSheetNav
189 {...defaultProps}
190 onOpenChange={onOpenChange}
191 shouldCloseOnViewportResize={false}
192 />
193 )
194 })
195
196 expect(onOpenChange).not.toHaveBeenCalled()
197 })
198 })
199})