@@ -114,6 +114,7 @@ describe('Tree', () => {
114114 if ( config . label !== undefined ) node . label = config . label ;
115115 if ( config . children !== undefined ) node . children = config . children ;
116116 if ( config . disabled !== undefined ) node . disabled = config . disabled ;
117+ if ( config . selectable !== undefined ) node . selectable = config . selectable ;
117118 updateTree ( { nodes : newNodes } ) ;
118119 return ;
119120 }
@@ -309,6 +310,16 @@ describe('Tree', () => {
309310 expect ( appleItem . getAttribute ( 'aria-current' ) ) . toBe ( 'location' ) ;
310311 } ) ;
311312
313+ it ( 'should not set aria-current when not selectable' , ( ) => {
314+ expandAll ( ) ;
315+ updateTree ( { nav : true , value : [ 'apple' ] } ) ;
316+ const appleItem = getTreeItemElementByValue ( 'apple' ) ! ;
317+ expect ( appleItem . getAttribute ( 'aria-current' ) ) . toBe ( 'page' ) ;
318+
319+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
320+ expect ( appleItem . hasAttribute ( 'aria-current' ) ) . toBe ( false ) ;
321+ } ) ;
322+
312323 it ( 'should not set aria-selected when nav="true"' , ( ) => {
313324 expandAll ( ) ;
314325
@@ -319,6 +330,16 @@ describe('Tree', () => {
319330 updateTree ( { nav : false } ) ;
320331 expect ( appleItem . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
321332 } ) ;
333+
334+ it ( 'should not set aria-selected when not selectable' , ( ) => {
335+ expandAll ( ) ;
336+ updateTree ( { value : [ 'apple' ] } ) ;
337+ const appleItem = getTreeItemElementByValue ( 'apple' ) ! ;
338+ expect ( appleItem . getAttribute ( 'aria-selected' ) ) . toBe ( 'true' ) ;
339+
340+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
341+ expect ( appleItem . hasAttribute ( 'aria-selected' ) ) . toBe ( false ) ;
342+ } ) ;
322343 } ) ;
323344
324345 describe ( 'roving focus mode (focusMode="roving")' , ( ) => {
@@ -492,6 +513,18 @@ describe('Tree', () => {
492513 click ( appleEl ) ;
493514 expect ( treeInstance . value ( ) ) . toEqual ( [ 'banana' ] ) ;
494515 } ) ;
516+
517+ describe ( 'selectable=false' , ( ) => {
518+ it ( 'should not select an item on click' , ( ) => {
519+ updateTree ( { value : [ 'banana' ] } ) ;
520+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
521+ const appleEl = getTreeItemElementByValue ( 'apple' ) ! ;
522+
523+ click ( appleEl ) ;
524+ expect ( treeInstance . value ( ) ) . not . toContain ( 'apple' ) ;
525+ expect ( treeInstance . value ( ) ) . toContain ( 'banana' ) ;
526+ } ) ;
527+ } ) ;
495528 } ) ;
496529
497530 describe ( 'selectionMode="follow"' , ( ) => {
@@ -560,6 +593,39 @@ describe('Tree', () => {
560593 'broccoli' ,
561594 ] ) ;
562595 } ) ;
596+
597+ describe ( 'selectable=false' , ( ) => {
598+ it ( 'should not select a range with shift+click if an item is not selectable' , ( ) => {
599+ updateTreeItemByValue ( 'banana' , { selectable : false } ) ;
600+ const appleEl = getTreeItemElementByValue ( 'apple' ) ! ;
601+ const berriesEl = getTreeItemElementByValue ( 'berries' ) ! ;
602+
603+ click ( appleEl ) ;
604+ shiftClick ( berriesEl ) ;
605+
606+ expect ( treeInstance . value ( ) ) . not . toContain ( 'banana' ) ;
607+ expect ( treeInstance . value ( ) ) . toContain ( 'apple' ) ;
608+ expect ( treeInstance . value ( ) ) . toContain ( 'berries' ) ;
609+ } ) ;
610+
611+ it ( 'should not toggle selection of an item on simple click' , ( ) => {
612+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
613+ const appleEl = getTreeItemElementByValue ( 'apple' ) ! ;
614+
615+ click ( appleEl ) ;
616+ expect ( treeInstance . value ( ) ) . not . toContain ( 'apple' ) ;
617+ } ) ;
618+
619+ it ( 'should not add to selection with ctrl+click' , ( ) => {
620+ updateTree ( { value : [ 'banana' ] } ) ;
621+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
622+ const appleEl = getTreeItemElementByValue ( 'apple' ) ! ;
623+
624+ ctrlClick ( appleEl ) ;
625+ expect ( treeInstance . value ( ) ) . not . toContain ( 'apple' ) ;
626+ expect ( treeInstance . value ( ) ) . toContain ( 'banana' ) ;
627+ } ) ;
628+ } ) ;
563629 } ) ;
564630 } ) ;
565631 } ) ;
@@ -607,6 +673,20 @@ describe('Tree', () => {
607673 enter ( ) ;
608674 expect ( treeInstance . value ( ) ) . toEqual ( [ 'grains' ] ) ;
609675 } ) ;
676+
677+ describe ( 'selectable=false' , ( ) => {
678+ it ( 'should not select the focused item with Enter' , ( ) => {
679+ updateTreeItemByValue ( 'fruits' , { selectable : false } ) ;
680+ enter ( ) ;
681+ expect ( treeInstance . value ( ) ) . toEqual ( [ ] ) ;
682+ } ) ;
683+
684+ it ( 'should not select the focused item with Space' , ( ) => {
685+ updateTreeItemByValue ( 'fruits' , { selectable : false } ) ;
686+ space ( ) ;
687+ expect ( treeInstance . value ( ) ) . toEqual ( [ ] ) ;
688+ } ) ;
689+ } ) ;
610690 } ) ;
611691
612692 describe ( 'selectionMode="follow"' , ( ) => {
@@ -737,6 +817,35 @@ describe('Tree', () => {
737817 up ( { ctrlKey : true } ) ;
738818 expect ( treeInstance . value ( ) ) . toEqual ( [ 'fruits' ] ) ;
739819 } ) ;
820+
821+ describe ( 'selectable=false' , ( ) => {
822+ it ( 'should not toggle selection of the focused item with Space' , ( ) => {
823+ updateTreeItemByValue ( 'fruits' , { selectable : false } ) ;
824+ space ( ) ;
825+ expect ( treeInstance . value ( ) ) . toEqual ( [ ] ) ;
826+ } ) ;
827+
828+ it ( 'should not extend selection with Shift+ArrowDown' , ( ) => {
829+ updateTreeItemByValue ( 'vegetables' , { selectable : false } ) ;
830+ shift ( ) ;
831+ down ( { shiftKey : true } ) ;
832+ down ( { shiftKey : true } ) ;
833+ expect ( treeInstance . value ( ) ) . not . toContain ( 'vegetables' ) ;
834+ expect ( treeInstance . value ( ) . sort ( ) ) . toEqual ( [ 'fruits' , 'grains' ] ) ;
835+ } ) ;
836+
837+ it ( 'Ctrl+A should not select non-selectable items' , ( ) => {
838+ expandAll ( ) ;
839+ updateTreeItemByValue ( 'apple' , { selectable : false } ) ;
840+ updateTreeItemByValue ( 'carrot' , { selectable : false } ) ;
841+ keydown ( 'A' , { ctrlKey : true } ) ;
842+ const value = treeInstance . value ( ) ;
843+ expect ( value ) . not . toContain ( 'apple' ) ;
844+ expect ( value ) . not . toContain ( 'carrot' ) ;
845+ expect ( value ) . toContain ( 'banana' ) ;
846+ expect ( value ) . toContain ( 'broccoli' ) ;
847+ } ) ;
848+ } ) ;
740849 } ) ;
741850
742851 describe ( 'selectionMode="follow"' , ( ) => {
@@ -864,6 +973,37 @@ describe('Tree', () => {
864973 expect ( getFocusedTreeItemValue ( ) ) . toBe ( 'vegetables' ) ;
865974 } ) ;
866975
976+ describe ( 'selectable=false' , ( ) => {
977+ it ( 'should not select an item on ArrowDown' , ( ) => {
978+ updateTreeItemByValue ( 'vegetables' , { selectable : false } ) ;
979+ down ( ) ;
980+ expect ( treeInstance . value ( ) ) . not . toContain ( 'vegetables' ) ;
981+ expect ( treeInstance . value ( ) ) . toEqual ( [ ] ) ;
982+ } ) ;
983+
984+ it ( 'should not toggle selection of the focused item on Ctrl+Space' , ( ) => {
985+ updateTreeItemByValue ( 'fruits' , { selectable : false } ) ;
986+ space ( { ctrlKey : true } ) ;
987+ expect ( treeInstance . value ( ) ) . toEqual ( [ ] ) ;
988+ } ) ;
989+
990+ it ( 'should not extend selection with Shift+ArrowDown' , ( ) => {
991+ updateTreeItemByValue ( 'vegetables' , { selectable : false } ) ;
992+ shift ( ) ;
993+ down ( { shiftKey : true } ) ;
994+ down ( { shiftKey : true } ) ;
995+ expect ( treeInstance . value ( ) ) . not . toContain ( 'vegetables' ) ;
996+ expect ( treeInstance . value ( ) . sort ( ) ) . toEqual ( [ 'fruits' , 'grains' ] ) ;
997+ } ) ;
998+
999+ it ( 'typeahead should not select the focused item' , ( ) => {
1000+ updateTreeItemByValue ( 'vegetables' , { selectable : false } ) ;
1001+ type ( 'v' ) ;
1002+ expect ( getFocusedTreeItemValue ( ) ) . toBe ( 'vegetables' ) ;
1003+ expect ( treeInstance . value ( ) ) . not . toContain ( 'vegetables' ) ;
1004+ } ) ;
1005+ } ) ;
1006+
8671007 it ( 'should not select disabled items during Shift+ArrowKey navigation even if skipDisabled is false' , ( ) => {
8681008 right ( ) ; // Expands fruits
8691009 updateTreeItemByValue ( 'banana' , { disabled : true } ) ;
@@ -1302,6 +1442,7 @@ interface TestTreeNode<V = string> {
13021442 value : V ;
13031443 label : string ;
13041444 disabled ?: boolean ;
1445+ selectable ?: boolean ;
13051446 children ?: TestTreeNode < V > [ ] ;
13061447}
13071448
@@ -1332,6 +1473,7 @@ interface TestTreeNode<V = string> {
13321473 [value]="node.value"
13331474 [label]="node.label"
13341475 [disabled]="!!node.disabled"
1476+ [selectable]="node.selectable ?? true"
13351477 [parent]="parent"
13361478 [attr.data-value]="node.value"
13371479 #treeItem="ngTreeItem"
0 commit comments