@@ -2,71 +2,70 @@ import { Component, ElementRef, inject, signal, viewChild } from '@angular/core'
22import { TestBed } from '@angular/core/testing' ;
33import { elementFocus } from './index' ;
44
5- // @TODO : add tests with focusVisible
65describe ( elementFocus . name , ( ) => {
76 describe ( 'with reactive target (signal query)' , ( ) => {
87 describe ( 'focus state tracking' , ( ) => {
98 @Component ( { template : '<input #input />' } )
109 class TestComponent {
1110 readonly input = viewChild < ElementRef > ( 'input' ) ;
12- readonly isFocused = elementFocus ( this . input ) ;
11+ readonly childFocused = elementFocus ( this . input ) ;
1312 }
1413
1514 function createComponent ( ) {
1615 const fixture = TestBed . createComponent ( TestComponent ) ;
1716 fixture . detectChanges ( ) ;
1817 return {
1918 component : fixture . componentInstance ,
20- target : fixture . nativeElement . querySelector ( 'input' ) ,
19+ focusableChildEl : fixture . nativeElement . querySelector ( 'input' ) ,
2120 } ;
2221 }
2322
2423 it ( 'should be false initially' , ( ) => {
2524 const { component } = createComponent ( ) ;
2625
27- expect ( component . isFocused ( ) ) . toBe ( false ) ;
26+ expect ( component . childFocused ( ) ) . toBe ( false ) ;
2827 } ) ;
2928
3029 it ( 'should handle multiple focus cycles' , ( ) => {
31- const { component, target } = createComponent ( ) ;
30+ const { component, focusableChildEl } = createComponent ( ) ;
3231
33- target . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
34- expect ( component . isFocused ( ) ) . toBe ( true ) ;
32+ focusableChildEl . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
33+ expect ( component . childFocused ( ) ) . toBe ( true ) ;
3534
36- target . dispatchEvent ( new FocusEvent ( 'blur' ) ) ;
37- expect ( component . isFocused ( ) ) . toBe ( false ) ;
35+ focusableChildEl . dispatchEvent ( new FocusEvent ( 'blur' ) ) ;
36+ expect ( component . childFocused ( ) ) . toBe ( false ) ;
3837
39- target . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
40- expect ( component . isFocused ( ) ) . toBe ( true ) ;
38+ focusableChildEl . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
39+ expect ( component . childFocused ( ) ) . toBe ( true ) ;
4140 } ) ;
4241 } ) ;
4342
4443 describe ( 'child element destroy' , ( ) => {
4544 it ( 'should reset to false when element is destroyed' , ( ) => {
4645 @Component ( {
4746 template : `
48- @if (show ()) {
47+ @if (childShown ()) {
4948 <input #input />
5049 }
5150 ` ,
5251 } )
5352 class ConditionalComponent {
5453 readonly input = viewChild < ElementRef > ( 'input' ) ;
55- readonly isFocused = elementFocus ( this . input ) ;
56- readonly show = signal ( true ) ;
54+ readonly childFocused = elementFocus ( this . input ) ;
55+ readonly childShown = signal ( true ) ;
5756 }
5857
5958 const fixture = TestBed . createComponent ( ConditionalComponent ) ;
6059 fixture . detectChanges ( ) ;
6160 const element = fixture . nativeElement . querySelector ( 'input' ) ;
6261 element . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
6362
64- expect ( fixture . componentInstance . isFocused ( ) ) . toBe ( true ) ;
63+ expect ( fixture . componentInstance . childFocused ( ) ) . toBe ( true ) ;
6564
66- fixture . componentInstance . show . set ( false ) ;
65+ fixture . componentInstance . childShown . set ( false ) ;
6766 fixture . detectChanges ( ) ;
6867
69- expect ( fixture . componentInstance . isFocused ( ) ) . toBe ( false ) ;
68+ expect ( fixture . componentInstance . childFocused ( ) ) . toBe ( false ) ;
7069 } ) ;
7170 } ) ;
7271 } ) ;
@@ -84,7 +83,7 @@ describe(elementFocus.name, () => {
8483 fixture . detectChanges ( ) ;
8584 return {
8685 component : fixture . componentInstance ,
87- target : fixture . nativeElement ,
86+ componentHostEl : fixture . nativeElement ,
8887 } ;
8988 }
9089
@@ -95,15 +94,15 @@ describe(elementFocus.name, () => {
9594 } ) ;
9695
9796 it ( 'should handle multiple focus cycles' , ( ) => {
98- const { component, target } = createComponent ( ) ;
97+ const { component, componentHostEl } = createComponent ( ) ;
9998
100- target . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
99+ componentHostEl . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
101100 expect ( component . isFocused ( ) ) . toBe ( true ) ;
102101
103- target . dispatchEvent ( new FocusEvent ( 'blur' ) ) ;
102+ componentHostEl . dispatchEvent ( new FocusEvent ( 'blur' ) ) ;
104103 expect ( component . isFocused ( ) ) . toBe ( false ) ;
105104
106- target . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
105+ componentHostEl . dispatchEvent ( new FocusEvent ( 'focus' ) ) ;
107106 expect ( component . isFocused ( ) ) . toBe ( true ) ;
108107 } ) ;
109108 } ) ;
@@ -131,4 +130,98 @@ describe(elementFocus.name, () => {
131130 } ) ;
132131 } ) ;
133132 } ) ;
133+
134+ describe ( 'focus control via set()' , ( ) => {
135+ @Component ( { template : '<input #input />' } )
136+ class TestComponent {
137+ readonly input = viewChild < ElementRef > ( 'input' ) ;
138+ readonly childFocused = elementFocus ( this . input ) ;
139+ }
140+
141+ function createComponent ( ) {
142+ const fixture = TestBed . createComponent ( TestComponent ) ;
143+ fixture . detectChanges ( ) ;
144+ return {
145+ component : fixture . componentInstance ,
146+ focusableChildEl : fixture . nativeElement . querySelector ( 'input' ) as HTMLInputElement ,
147+ } ;
148+ }
149+
150+ it ( 'should set focus when set(true) is called' , ( ) => {
151+ const { component, focusableChildEl } = createComponent ( ) ;
152+
153+ component . childFocused . set ( true ) ;
154+
155+ expect ( focusableChildEl ) . toBe ( document . activeElement ) ;
156+ expect ( component . childFocused ( ) ) . toBe ( true ) ;
157+ } ) ;
158+
159+ it ( 'should remove focus when set(false) is called' , ( ) => {
160+ const { component, focusableChildEl } = createComponent ( ) ;
161+
162+ focusableChildEl . focus ( ) ;
163+ expect ( component . childFocused ( ) ) . toBe ( true ) ;
164+
165+ component . childFocused . set ( false ) ;
166+
167+ expect ( focusableChildEl ) . not . toBe ( document . activeElement ) ;
168+ expect ( component . childFocused ( ) ) . toBe ( false ) ;
169+ } ) ;
170+
171+ it ( 'should not call focus() if already focused' , ( ) => {
172+ const { component, focusableChildEl } = createComponent ( ) ;
173+
174+ focusableChildEl . focus ( ) ;
175+ const spy = jest . spyOn ( focusableChildEl , 'focus' ) ;
176+
177+ component . childFocused . set ( true ) ;
178+
179+ expect ( spy ) . not . toHaveBeenCalled ( ) ;
180+ } ) ;
181+
182+ it ( 'should not call blur() if not focused' , ( ) => {
183+ const { component, focusableChildEl } = createComponent ( ) ;
184+ const spy = jest . spyOn ( focusableChildEl , 'blur' ) ;
185+
186+ component . childFocused . set ( false ) ;
187+
188+ expect ( spy ) . not . toHaveBeenCalled ( ) ;
189+ } ) ;
190+
191+ it ( 'should support update() method' , ( ) => {
192+ const { component } = createComponent ( ) ;
193+
194+ component . childFocused . set ( true ) ;
195+ expect ( component . childFocused ( ) ) . toBe ( true ) ;
196+
197+ component . childFocused . update ( v => ! v ) ;
198+ expect ( component . childFocused ( ) ) . toBe ( false ) ;
199+ } ) ;
200+ } ) ;
201+
202+ describe ( 'preventScroll option' , ( ) => {
203+ @Component ( { template : '<input #input />' } )
204+ class TestComponent {
205+ readonly input = viewChild < ElementRef > ( 'input' ) ;
206+ readonly childFocused = elementFocus ( this . input , { preventScroll : true } ) ;
207+ }
208+
209+ function createComponent ( ) {
210+ const fixture = TestBed . createComponent ( TestComponent ) ;
211+ fixture . detectChanges ( ) ;
212+ return {
213+ component : fixture . componentInstance ,
214+ focusableChildEl : fixture . nativeElement . querySelector ( 'input' ) as HTMLInputElement ,
215+ } ;
216+ }
217+
218+ it ( 'should call focus with preventScroll option' , ( ) => {
219+ const { component, focusableChildEl } = createComponent ( ) ;
220+ const spy = jest . spyOn ( focusableChildEl , 'focus' ) ;
221+
222+ component . childFocused . set ( true ) ;
223+
224+ expect ( spy ) . toHaveBeenCalledWith ( { preventScroll : true } ) ;
225+ } ) ;
226+ } ) ;
134227} ) ;
0 commit comments