1- import { Component } from '@angular/core' ;
1+ import {
2+ Component ,
3+ DebugElement ,
4+ } from '@angular/core' ;
25import {
36 ComponentFixture ,
7+ fakeAsync ,
8+ flush ,
49 TestBed ,
510 waitForAsync ,
611} from '@angular/core/testing' ;
@@ -10,9 +15,11 @@ import { of as observableOf } from 'rxjs';
1015
1116import { HostWindowService } from '../../shared/host-window.service' ;
1217import { MenuService } from '../../shared/menu/menu.service' ;
18+ import { LinkMenuItemModel } from '../../shared/menu/menu-item/models/link.model' ;
19+ import { MenuSection } from '../../shared/menu/menu-section.model' ;
1320import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub' ;
1421import { MenuServiceStub } from '../../shared/testing/menu-service.stub' ;
15- import { VarDirective } from '../../shared/utils/var .directive' ;
22+ import { HoverOutsideDirective } from '../../shared/utils/hover-outside .directive' ;
1623import { ExpandableNavbarSectionComponent } from './expandable-navbar-section.component' ;
1724
1825describe ( 'ExpandableNavbarSectionComponent' , ( ) => {
@@ -23,11 +30,17 @@ describe('ExpandableNavbarSectionComponent', () => {
2330 describe ( 'on larger screens' , ( ) => {
2431 beforeEach ( waitForAsync ( ( ) => {
2532 TestBed . configureTestingModule ( {
26- imports : [ NoopAnimationsModule , ExpandableNavbarSectionComponent , TestComponent , VarDirective ] ,
33+ imports : [
34+ ExpandableNavbarSectionComponent ,
35+ HoverOutsideDirective ,
36+ NoopAnimationsModule ,
37+ TestComponent ,
38+ ] ,
2739 providers : [
2840 { provide : 'sectionDataProvider' , useValue : { } } ,
2941 { provide : MenuService , useValue : menuService } ,
3042 { provide : HostWindowService , useValue : new HostWindowServiceStub ( 800 ) } ,
43+ TestComponent ,
3144 ] ,
3245 } ) . compileComponents ( ) ;
3346 } ) ) ;
@@ -41,10 +54,6 @@ describe('ExpandableNavbarSectionComponent', () => {
4154 fixture . detectChanges ( ) ;
4255 } ) ;
4356
44- it ( 'should create' , ( ) => {
45- expect ( component ) . toBeTruthy ( ) ;
46- } ) ;
47-
4857 describe ( 'when the mouse enters the section header (while inactive)' , ( ) => {
4958 beforeEach ( ( ) => {
5059 spyOn ( component , 'onMouseEnter' ) . and . callThrough ( ) ;
@@ -141,6 +150,8 @@ describe('ExpandableNavbarSectionComponent', () => {
141150 } ) ;
142151
143152 describe ( 'when spacebar is pressed on section header (while inactive)' , ( ) => {
153+ let sidebarToggler : DebugElement ;
154+
144155 beforeEach ( ( ) => {
145156 spyOn ( component , 'toggleSection' ) . and . callThrough ( ) ;
146157 spyOn ( menuService , 'toggleActiveSection' ) ;
@@ -149,15 +160,27 @@ describe('ExpandableNavbarSectionComponent', () => {
149160 component . ngOnInit ( ) ;
150161 fixture . detectChanges ( ) ;
151162
152- const sidebarToggler = fixture . debugElement . query ( By . css ( '[data-test="navbar-section-toggler"]' ) ) ;
153- // dispatch the (keyup.space) action used in our component HTML
154- sidebarToggler . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keyup' , { key : ' ' } ) ) ;
163+ sidebarToggler = fixture . debugElement . query ( By . css ( '[data-test="navbar-section-toggler"]' ) ) ;
155164 } ) ;
156165
157166 it ( 'should call toggleSection on the menuService' , ( ) => {
167+ // dispatch the (keyup.space) action used in our component HTML
168+ sidebarToggler . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keyup' , { code : 'Space' , key : ' ' } ) ) ;
169+
158170 expect ( component . toggleSection ) . toHaveBeenCalled ( ) ;
159171 expect ( menuService . toggleActiveSection ) . toHaveBeenCalled ( ) ;
160172 } ) ;
173+
174+ // Should not do anything in order to work correctly with NVDA: https://www.nvaccess.org/
175+ it ( 'should not do anything on keydown space' , ( ) => {
176+ const event : Event = new KeyboardEvent ( 'keydown' , { code : 'Space' , key : ' ' } ) ;
177+ spyOn ( event , 'preventDefault' ) . and . callThrough ( ) ;
178+
179+ // dispatch the (keyup.space) action used in our component HTML
180+ sidebarToggler . nativeElement . dispatchEvent ( event ) ;
181+
182+ expect ( event . preventDefault ) . toHaveBeenCalled ( ) ;
183+ } ) ;
161184 } ) ;
162185
163186 describe ( 'when spacebar is pressed on section header (while active)' , ( ) => {
@@ -179,12 +202,116 @@ describe('ExpandableNavbarSectionComponent', () => {
179202 expect ( menuService . toggleActiveSection ) . toHaveBeenCalled ( ) ;
180203 } ) ;
181204 } ) ;
205+
206+ describe ( 'when enter is pressed on section header (while inactive)' , ( ) => {
207+ let sidebarToggler : DebugElement ;
208+
209+ beforeEach ( ( ) => {
210+ spyOn ( component , 'toggleSection' ) . and . callThrough ( ) ;
211+ spyOn ( menuService , 'toggleActiveSection' ) ;
212+ // Make sure section is 'inactive'. Requires calling ngOnInit() to update component 'active' property.
213+ spyOn ( menuService , 'isSectionActive' ) . and . returnValue ( observableOf ( false ) ) ;
214+ component . ngOnInit ( ) ;
215+ fixture . detectChanges ( ) ;
216+
217+ sidebarToggler = fixture . debugElement . query ( By . css ( '[data-test="navbar-section-toggler"]' ) ) ;
218+ } ) ;
219+
220+ // Should not do anything in order to work correctly with NVDA: https://www.nvaccess.org/
221+ it ( 'should not do anything on keydown space' , ( ) => {
222+ const event : Event = new KeyboardEvent ( 'keydown' , { code : 'Enter' } ) ;
223+ spyOn ( event , 'preventDefault' ) . and . callThrough ( ) ;
224+
225+ // dispatch the (keyup.space) action used in our component HTML
226+ sidebarToggler . nativeElement . dispatchEvent ( event ) ;
227+
228+ expect ( event . preventDefault ) . toHaveBeenCalled ( ) ;
229+ } ) ;
230+ } ) ;
231+
232+ describe ( 'when arrow down is pressed on section header' , ( ) => {
233+ it ( 'should call activateSection' , ( ) => {
234+ spyOn ( component , 'activateSection' ) . and . callThrough ( ) ;
235+
236+ const sidebarToggler : DebugElement = fixture . debugElement . query ( By . css ( '[data-test="navbar-section-toggler"]' ) ) ;
237+ // dispatch the (keydown.ArrowDown) action used in our component HTML
238+ sidebarToggler . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'ArrowDown' } ) ) ;
239+
240+ expect ( component . focusOnFirstChildSection ) . toBe ( true ) ;
241+ expect ( component . activateSection ) . toHaveBeenCalled ( ) ;
242+ } ) ;
243+ } ) ;
244+
245+ describe ( 'when tab is pressed on section header' , ( ) => {
246+ it ( 'should call deactivateSection' , ( ) => {
247+ spyOn ( component , 'deactivateSection' ) . and . callThrough ( ) ;
248+
249+ const sidebarToggler : DebugElement = fixture . debugElement . query ( By . css ( '[data-test="navbar-section-toggler"]' ) ) ;
250+ // dispatch the (keydown.ArrowDown) action used in our component HTML
251+ sidebarToggler . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'Tab' } ) ) ;
252+
253+ expect ( component . deactivateSection ) . toHaveBeenCalled ( ) ;
254+ } ) ;
255+ } ) ;
256+
257+ describe ( 'navigateDropdown' , ( ) => {
258+ beforeEach ( fakeAsync ( ( ) => {
259+ jasmine . getEnv ( ) . allowRespy ( true ) ;
260+ spyOn ( menuService , 'getSubSectionsByParentID' ) . and . returnValue ( observableOf ( [
261+ Object . assign ( new MenuSection ( ) , {
262+ id : 'subSection1' ,
263+ model : Object . assign ( new LinkMenuItemModel ( ) , {
264+ type : 'TEST_LINK' ,
265+ } ) ,
266+ parentId : component . section . id ,
267+ } ) ,
268+ Object . assign ( new MenuSection ( ) , {
269+ id : 'subSection2' ,
270+ model : Object . assign ( new LinkMenuItemModel ( ) , {
271+ type : 'TEST_LINK' ,
272+ } ) ,
273+ parentId : component . section . id ,
274+ } ) ,
275+ ] ) ) ;
276+ component . ngOnInit ( ) ;
277+ flush ( ) ;
278+ fixture . detectChanges ( ) ;
279+ component . focusOnFirstChildSection = true ;
280+ component . active$ . next ( true ) ;
281+ fixture . detectChanges ( ) ;
282+ } ) ) ;
283+
284+ it ( 'should close the modal on Tab' , ( ) => {
285+ spyOn ( menuService , 'deactivateSection' ) . and . callThrough ( ) ;
286+
287+ const firstSubsection : DebugElement = fixture . debugElement . queryAll ( By . css ( '.dropdown-menu a[role="menuitem"]' ) ) [ 0 ] ;
288+ firstSubsection . nativeElement . focus ( ) ;
289+ firstSubsection . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'Tab' } ) ) ;
290+
291+ expect ( menuService . deactivateSection ) . toHaveBeenCalled ( ) ;
292+ } ) ;
293+
294+ it ( 'should close the modal on Escape' , ( ) => {
295+ spyOn ( menuService , 'deactivateSection' ) . and . callThrough ( ) ;
296+
297+ const firstSubsection : DebugElement = fixture . debugElement . queryAll ( By . css ( '.dropdown-menu a[role="menuitem"]' ) ) [ 0 ] ;
298+ firstSubsection . nativeElement . focus ( ) ;
299+ firstSubsection . nativeElement . dispatchEvent ( new KeyboardEvent ( 'keydown' , { code : 'Escape' } ) ) ;
300+
301+ expect ( menuService . deactivateSection ) . toHaveBeenCalled ( ) ;
302+ } ) ;
303+ } ) ;
182304 } ) ;
183305
184306 describe ( 'on smaller, mobile screens' , ( ) => {
185307 beforeEach ( waitForAsync ( ( ) => {
186308 TestBed . configureTestingModule ( {
187- imports : [ NoopAnimationsModule , ExpandableNavbarSectionComponent , TestComponent , VarDirective ] ,
309+ imports : [
310+ ExpandableNavbarSectionComponent ,
311+ HoverOutsideDirective ,
312+ NoopAnimationsModule ,
313+ TestComponent ,
314+ ] ,
188315 providers : [
189316 { provide : 'sectionDataProvider' , useValue : { } } ,
190317 { provide : MenuService , useValue : menuService } ,
@@ -253,7 +380,9 @@ describe('ExpandableNavbarSectionComponent', () => {
253380// declare a test component
254381@Component ( {
255382 selector : 'ds-test-cmp' ,
256- template : `` ,
383+ template : `
384+ <a role="menuitem">link</a>
385+ ` ,
257386 standalone : true ,
258387} )
259388class TestComponent {
0 commit comments