55 * LICENSE file in the root directory of this source tree.
66 */
77
8- import { render , screen } from '@testing-library/react' ;
8+ import { render , screen , fireEvent } from '@testing-library/react' ;
99import React from 'react' ;
1010import SideNav from '../SideNav' ;
1111
@@ -15,8 +15,8 @@ Object.defineProperty(window, 'matchMedia', {
1515 matches : false ,
1616 media : query ,
1717 onchange : null ,
18- addListener : jest . fn ( ) , // deprecated
19- removeListener : jest . fn ( ) , // deprecated
18+ addListener : jest . fn ( ) ,
19+ removeListener : jest . fn ( ) ,
2020 addEventListener : jest . fn ( ) ,
2121 removeEventListener : jest . fn ( ) ,
2222 dispatchEvent : jest . fn ( ) ,
@@ -36,6 +36,11 @@ describe('SideNav', () => {
3636 expect ( container . childNodes . length ) . toBe ( 2 ) ;
3737 } ) ;
3838
39+ it ( 'should not render an overlay if `isFixedNav` is true' , ( ) => {
40+ const { container } = render ( < SideNav aria-label = "test" isFixedNav /> ) ;
41+ expect ( container . childNodes . length ) . toBe ( 1 ) ;
42+ } ) ;
43+
3944 it ( 'should toggle the overlay-active class when `expanded` is true' , ( ) => {
4045 const { container } = render ( < SideNav aria-label = "test" expanded /> ) ;
4146 expect ( container . firstChild ) . toHaveClass ( 'cds--side-nav__overlay-active' ) ;
@@ -64,4 +69,269 @@ describe('SideNav', () => {
6469 render ( < SideNav aria-label = "test" ref = { ref } /> ) ;
6570 expect ( ref ) . toHaveBeenCalledWith ( screen . getByRole ( 'navigation' ) ) ;
6671 } ) ;
72+
73+ it ( 'should call onOverlayClick when overlay is clicked' , ( ) => {
74+ const onOverlayClick = jest . fn ( ) ;
75+ const { container } = render (
76+ < SideNav aria-label = "test" expanded onOverlayClick = { onOverlayClick } />
77+ ) ;
78+ const overlay = container . firstChild ;
79+ fireEvent . click ( overlay ) ;
80+ expect ( onOverlayClick ) . toHaveBeenCalledTimes ( 1 ) ;
81+ } ) ;
82+
83+ it ( 'should not add focus or mouse listeners when disabled' , ( ) => {
84+ const onToggle = jest . fn ( ) ;
85+ render (
86+ < SideNav
87+ aria-label = "test"
88+ addFocusListeners = { false }
89+ addMouseListeners = { false }
90+ onToggle = { onToggle }
91+ />
92+ ) ;
93+ const sideNav = screen . getByRole ( 'navigation' ) ;
94+ fireEvent . focus ( sideNav ) ;
95+ fireEvent . mouseEnter ( sideNav ) ;
96+ expect ( onToggle ) . not . toHaveBeenCalled ( ) ;
97+ } ) ;
98+
99+ it ( 'should handle keyboard events like Escape' , ( ) => {
100+ const onToggle = jest . fn ( ) ;
101+ render (
102+ < SideNav
103+ aria-label = "test"
104+ expanded
105+ onToggle = { onToggle }
106+ href = "#main-content"
107+ />
108+ ) ;
109+ const sideNav = screen . getByRole ( 'navigation' ) ;
110+ fireEvent . keyDown ( sideNav , { key : 'Escape' , keyCode : 27 } ) ;
111+ expect ( onToggle ) . toHaveBeenCalledWith ( expect . anything ( ) , false ) ;
112+ } ) ;
113+
114+ it ( 'should correctly handle `isRail` when true' , ( ) => {
115+ render ( < SideNav aria-label = "test" isRail /> ) ;
116+ const sideNav = screen . getByRole ( 'navigation' ) ;
117+ expect ( sideNav ) . toHaveClass ( 'cds--side-nav--rail' ) ;
118+ } ) ;
119+
120+ it ( 'should correctly handle `isRail` when false' , ( ) => {
121+ render ( < SideNav aria-label = "test" isRail = { false } /> ) ;
122+ const sideNav = screen . getByRole ( 'navigation' ) ;
123+ expect ( sideNav ) . not . toHaveClass ( 'cds--side-nav--rail' ) ;
124+ } ) ;
125+
126+ it ( 'should toggle the expanded state when uncontrolled' , ( ) => {
127+ const { container } = render ( < SideNav aria-label = "test" /> ) ;
128+ const sideNav = screen . getByRole ( 'navigation' ) ;
129+ fireEvent . focus ( sideNav ) ;
130+ expect ( container . firstChild ) . toHaveClass ( 'cds--side-nav__overlay' ) ;
131+ } ) ;
132+
133+ it ( 'should handle children without throwing error' , ( ) => {
134+ const { container } = render (
135+ < SideNav aria-label = "test" >
136+ < div > No Errors Here!</ div >
137+ </ SideNav >
138+ ) ;
139+ expect ( container ) . toBeInTheDocument ( ) ;
140+ } ) ;
141+
142+ it ( 'should pass isSideNavExpanded to Carbon SideNav children' , ( ) => {
143+ const TestChild = React . forwardRef ( ( { isSideNavExpanded } , ref ) => (
144+ < div data-testid = "child" ref = { ref } >
145+ { isSideNavExpanded ? 'Expanded' : 'Collapsed' }
146+ </ div >
147+ ) ) ;
148+ render (
149+ < SideNav aria-label = "test" expanded >
150+ < TestChild />
151+ </ SideNav >
152+ ) ;
153+ expect ( screen . getByTestId ( 'child' ) ) . toHaveTextContent ( 'Collapsed' ) ;
154+ } ) ;
155+
156+ it ( 'should not pass isSideNavExpanded to non-CarbsideNav children' , ( ) => {
157+ const NonCarbonChild = ( ) => < div data-testid = "non-carbon-child" /> ;
158+ render (
159+ < SideNav aria-label = "test" expanded >
160+ < NonCarbonChild />
161+ </ SideNav >
162+ ) ;
163+ expect ( screen . getByTestId ( 'non-carbon-child' ) ) . toBeInTheDocument ( ) ;
164+ } ) ;
165+
166+ it ( 'should pass isSideNavExpanded correctly based on controlled state' , ( ) => {
167+ const TestChild = React . forwardRef ( ( { isSideNavExpanded } , ref ) => (
168+ < div data-testid = "child" ref = { ref } >
169+ { isSideNavExpanded ? 'Expanded' : 'Collapsed' }
170+ </ div >
171+ ) ) ;
172+ const { rerender } = render (
173+ < SideNav aria-label = "test" expanded >
174+ < TestChild />
175+ </ SideNav >
176+ ) ;
177+ expect ( screen . getByTestId ( 'child' ) ) . toHaveTextContent ( 'Collapsed' ) ;
178+ rerender (
179+ < SideNav aria-label = "test" >
180+ < TestChild />
181+ </ SideNav >
182+ ) ;
183+ expect ( screen . getByTestId ( 'child' ) ) . toHaveTextContent ( 'Collapsed' ) ;
184+ } ) ;
185+
186+ it ( 'should call handleToggle and onSideNavBlur when blurred' , ( ) => {
187+ const onSideNavBlurMock = jest . fn ( ) ;
188+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
189+ const { getByRole } = render (
190+ < SideNav aria-label = "test" onSideNavBlur = { onSideNavBlurMock } >
191+ < TestChild />
192+ </ SideNav >
193+ ) ;
194+ const sideNav = getByRole ( 'navigation' ) ;
195+ fireEvent . focus ( sideNav ) ;
196+ fireEvent . blur ( sideNav ) ;
197+ expect ( onSideNavBlurMock ) . not . toHaveBeenCalled ( ) ;
198+ } ) ;
199+
200+ it ( 'should not call onSideNavBlur if not expanded and isFixedNav is true' , ( ) => {
201+ const onSideNavBlurMock = jest . fn ( ) ;
202+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
203+ const { getByRole } = render (
204+ < SideNav aria-label = "test" isFixedNav onSideNavBlur = { onSideNavBlurMock } >
205+ < TestChild />
206+ </ SideNav >
207+ ) ;
208+ const sideNav = getByRole ( 'navigation' ) ;
209+ fireEvent . focus ( sideNav ) ;
210+ fireEvent . blur ( sideNav ) ;
211+ expect ( onSideNavBlurMock ) . not . toHaveBeenCalled ( ) ;
212+ } ) ;
213+
214+ it ( 'should call onSideNavBlur when blurred, is not fixed, and is expanded' , ( ) => {
215+ const onSideNavBlurMock = jest . fn ( ) ;
216+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
217+ const { getByRole } = render (
218+ < SideNav
219+ aria-label = "test"
220+ onSideNavBlur = { onSideNavBlurMock }
221+ defaultExpanded >
222+ < TestChild />
223+ </ SideNav >
224+ ) ;
225+ const sideNav = getByRole ( 'navigation' ) ;
226+ fireEvent . focus ( sideNav ) ;
227+ const unrelatedElement = document . createElement ( 'div' ) ;
228+ document . body . appendChild ( unrelatedElement ) ;
229+ fireEvent . blur ( sideNav , { relatedTarget : unrelatedElement } ) ;
230+ expect ( onSideNavBlurMock ) . toHaveBeenCalled ( ) ;
231+ document . body . removeChild ( unrelatedElement ) ;
232+ } ) ;
233+
234+ it ( 'should not call onSideNavBlur when isFixedNav is true' , ( ) => {
235+ const onSideNavBlurMock = jest . fn ( ) ;
236+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
237+ const { getByRole } = render (
238+ < SideNav
239+ aria-label = "test"
240+ isFixedNav
241+ onSideNavBlur = { onSideNavBlurMock }
242+ defaultExpanded >
243+ < TestChild />
244+ </ SideNav >
245+ ) ;
246+ const sideNav = getByRole ( 'navigation' ) ;
247+ fireEvent . focus ( sideNav ) ;
248+ const unrelatedElement = document . createElement ( 'div' ) ;
249+ document . body . appendChild ( unrelatedElement ) ;
250+ fireEvent . blur ( sideNav , { relatedTarget : unrelatedElement } ) ;
251+ expect ( onSideNavBlurMock ) . not . toHaveBeenCalled ( ) ;
252+ document . body . removeChild ( unrelatedElement ) ;
253+ } ) ;
254+
255+ it ( 'should set expanded state to false on mouse leave' , ( ) => {
256+ const onToggleMock = jest . fn ( ) ;
257+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
258+ const { getByRole } = render (
259+ < SideNav aria-label = "test" defaultExpanded onToggle = { onToggleMock } >
260+ < TestChild />
261+ </ SideNav >
262+ ) ;
263+ const sideNav = getByRole ( 'navigation' ) ;
264+ expect ( sideNav ) . toHaveClass ( 'cds--side-nav__navigation' ) ;
265+ fireEvent . mouseLeave ( sideNav ) ;
266+ expect ( sideNav ) . toHaveClass ( 'cds--side-nav__navigation' ) ;
267+ } ) ;
268+
269+ it ( 'should handleToggle if isRail is true' , ( ) => {
270+ const onToggleMock = jest . fn ( ) ;
271+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
272+ const { getByRole } = render (
273+ < SideNav aria-label = "test" onToggle = { onToggleMock } isRail >
274+ < TestChild />
275+ </ SideNav >
276+ ) ;
277+ const sideNav = getByRole ( 'navigation' ) ;
278+ fireEvent . focus ( sideNav ) ;
279+ expect ( onToggleMock ) . toHaveBeenCalled ( ) ;
280+ fireEvent . mouseLeave ( sideNav ) ;
281+ expect ( onToggleMock ) . toHaveBeenCalledWith ( false , false ) ;
282+ fireEvent . mouseEnter ( sideNav ) ;
283+ expect ( onToggleMock ) . toHaveBeenCalledWith ( true , true ) ;
284+ } ) ;
285+
286+ it ( 'should not call handleToggle if isRail is false' , ( ) => {
287+ const onToggleMock = jest . fn ( ) ;
288+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
289+ const { getByRole } = render (
290+ < SideNav
291+ aria-label = "test"
292+ defaultExpanded
293+ onToggle = { onToggleMock }
294+ isRail = { false } >
295+ < TestChild />
296+ </ SideNav >
297+ ) ;
298+ const sideNav = getByRole ( 'navigation' ) ;
299+ fireEvent . mouseLeave ( sideNav ) ;
300+ expect ( onToggleMock ) . not . toHaveBeenCalled ( ) ;
301+ } ) ;
302+
303+ it ( 'should expand the SideNav immediately on click' , ( ) => {
304+ const TestChild = ( ) => < div data-testid = "child" > Child</ div > ;
305+ const onToggleMock = jest . fn ( ) ;
306+ const { getByRole } = render (
307+ < SideNav aria-label = "test" onToggle = { onToggleMock } isRail >
308+ < TestChild />
309+ </ SideNav >
310+ ) ;
311+ const sideNav = getByRole ( 'navigation' ) ;
312+ expect ( sideNav ) . not . toHaveClass ( 'cds--side-nav--expanded' ) ;
313+ fireEvent . click ( sideNav ) ;
314+ expect ( onToggleMock ) . toHaveBeenCalledWith ( true , true ) ;
315+ expect ( sideNav ) . toHaveClass ( 'cds--side-nav--expanded' ) ;
316+ } ) ;
317+
318+ it ( 'should focus SideNav after tabbing from headerMenuButton' , ( ) => {
319+ render (
320+ < >
321+ < button className = "cds--header__menu-toggle" > </ button >
322+ < SideNav
323+ aria-label = "test"
324+ expanded
325+ isFixedNav = { false }
326+ defaultExpanded
327+ />
328+ </ >
329+ ) ;
330+ const mockHeaderMenuButton = screen . getByRole ( 'button' ) ;
331+ const sideNav = screen . getByRole ( 'navigation' ) ;
332+
333+ mockHeaderMenuButton . focus ( ) ;
334+ fireEvent . keyDown ( window , { key : 'Tab' } ) ;
335+ expect ( document . activeElement ) . toBe ( sideNav ) ;
336+ } ) ;
67337} ) ;
0 commit comments