1010 * governing permissions and limitations under the License.
1111 */
1212import { act , fireEvent , render , renderHook , screen , waitFor } from '@react-spectrum/test-utils' ;
13+ import { addWindowFocusTracking , useFocusVisible , useFocusVisibleListener } from '../' ;
1314import { hasSetupGlobalListeners } from '../src/useFocusVisible' ;
1415import React from 'react' ;
1516import { render as ReactDOMRender } from 'react-dom' ;
16- import { setupFocus , useFocusVisible , useFocusVisibleListener } from '../' ;
1717
1818function Example ( props ) {
1919 const { isFocusVisible} = useFocusVisible ( ) ;
20- return < div id = { props . id } > example{ isFocusVisible && '-focusVisible' } </ div > ;
20+ return < div { ... props } > example{ isFocusVisible && '-focusVisible' } </ div > ;
2121}
2222
2323function toggleBrowserTabs ( ) {
@@ -103,7 +103,7 @@ describe('useFocusVisible', function () {
103103 iframe . contentWindow . document . body . appendChild ( iframeRoot ) ;
104104 } ) ;
105105
106- afterEach ( ( ) => {
106+ afterEach ( async ( ) => {
107107 fireEvent ( iframe . contentWindow , new Event ( 'beforeunload' ) ) ;
108108 iframe . remove ( ) ;
109109 } ) ;
@@ -120,7 +120,7 @@ describe('useFocusVisible', function () {
120120 expect ( el . textContent ) . toBe ( 'example' ) ;
121121
122122 // Setup focus in iframe
123- setupFocus ( iframeRoot ) ;
123+ addWindowFocusTracking ( iframeRoot ) ;
124124 expect ( el . textContent ) . toBe ( 'example' ) ;
125125
126126 // Focus in iframe after setupFocus
@@ -129,16 +129,19 @@ describe('useFocusVisible', function () {
129129 } ) ;
130130
131131 it ( 'removes event listeners on beforeunload' , async function ( ) {
132- ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
133- setupFocus ( iframeRoot ) ;
132+ let tree = render ( < Example data-testid = "iframe-example" /> , iframeRoot ) ;
134133
135134 await waitFor ( ( ) => {
136- expect ( document . querySelector ( 'iframe' ) . contentWindow . document . body . querySelector ( 'div[id="iframe -example"] ') ) . toBeTruthy ( ) ;
135+ expect ( tree . getByTestId ( 'iframe-example' ) ) . toBeTruthy ( ) ;
137136 } ) ;
138- const el = document . querySelector ( 'iframe' ) . contentWindow . document . body . querySelector ( 'div[id="iframe-example"]' ) ;
137+ const el = tree . getByTestId ( 'iframe-example' ) ;
138+ // trigger keyboard focus
139+ fireEvent . keyDown ( el , { key : 'a' } ) ;
140+ fireEvent . keyUp ( el , { key : 'a' } ) ;
139141 expect ( el . textContent ) . toBe ( 'example-focusVisible' ) ;
140142
141143 fireEvent . mouseDown ( el ) ;
144+ fireEvent . mouseUp ( el ) ;
142145 expect ( el . textContent ) . toBe ( 'example' ) ;
143146
144147 // Focus events after beforeunload no longer work
@@ -147,14 +150,36 @@ describe('useFocusVisible', function () {
147150 expect ( el . textContent ) . toBe ( 'example' ) ;
148151 } ) ;
149152
153+ it ( 'removes event listeners using teardown function' , async function ( ) {
154+ let tree = render ( < Example data-testid = "iframe-example" /> , iframeRoot ) ;
155+ let tearDown = addWindowFocusTracking ( iframeRoot ) ;
156+
157+ await waitFor ( ( ) => {
158+ expect ( tree . getByTestId ( 'iframe-example' ) ) . toBeTruthy ( ) ;
159+ } ) ;
160+ const el = tree . getByTestId ( 'iframe-example' ) ;
161+ // trigger keyboard focus
162+ fireEvent . keyDown ( el , { key : 'a' } ) ;
163+ fireEvent . keyUp ( el , { key : 'a' } ) ;
164+ expect ( el . textContent ) . toBe ( 'example-focusVisible' ) ;
165+
166+ fireEvent . mouseDown ( el ) ;
167+ fireEvent . mouseUp ( el ) ;
168+ expect ( el . textContent ) . toBe ( 'example' ) ;
169+
170+ tearDown ( ) ;
171+ fireEvent . focus ( iframe . contentWindow . document . body ) ;
172+ expect ( el . textContent ) . toBe ( 'example' ) ;
173+ } ) ;
174+
150175 it ( 'removes the window object from the hasSetupGlobalListeners object on beforeunload' , async function ( ) {
151176 ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
152177 expect ( hasSetupGlobalListeners . size ) . toBe ( 1 ) ;
153178 expect ( hasSetupGlobalListeners . get ( window ) ) . toBeTruthy ( ) ;
154179 expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeFalsy ( ) ;
155180
156181 // After setup focus
157- setupFocus ( iframeRoot ) ;
182+ addWindowFocusTracking ( iframeRoot ) ;
158183 expect ( hasSetupGlobalListeners . size ) . toBe ( 2 ) ;
159184 expect ( hasSetupGlobalListeners . get ( window ) ) . toBeTruthy ( ) ;
160185 expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeTruthy ( ) ;
@@ -166,9 +191,27 @@ describe('useFocusVisible', function () {
166191 expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeFalsy ( ) ;
167192 } ) ;
168193
194+ it ( 'removes the window object from the hasSetupGlobalListeners object if we preemptively tear down' , async function ( ) {
195+ ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
196+ expect ( hasSetupGlobalListeners . size ) . toBe ( 1 ) ;
197+ expect ( hasSetupGlobalListeners . get ( window ) ) . toBeTruthy ( ) ;
198+ expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeFalsy ( ) ;
199+
200+ // After setup focus
201+ let tearDown = addWindowFocusTracking ( iframeRoot ) ;
202+ expect ( hasSetupGlobalListeners . size ) . toBe ( 2 ) ;
203+ expect ( hasSetupGlobalListeners . get ( window ) ) . toBeTruthy ( ) ;
204+ expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeTruthy ( ) ;
205+
206+ tearDown ( ) ;
207+ expect ( hasSetupGlobalListeners . size ) . toBe ( 1 ) ;
208+ expect ( hasSetupGlobalListeners . get ( window ) ) . toBeTruthy ( ) ;
209+ expect ( hasSetupGlobalListeners . get ( iframe . contentWindow ) ) . toBeFalsy ( ) ;
210+ } ) ;
211+
169212 it ( 'returns positive isFocusVisible result after toggling browser tabs after keyboard navigation' , async function ( ) {
170213 ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
171- setupFocus ( iframeRoot ) ;
214+ addWindowFocusTracking ( iframeRoot ) ;
172215
173216 // Fire focus in iframe
174217 await waitFor ( ( ) => {
@@ -188,7 +231,7 @@ describe('useFocusVisible', function () {
188231
189232 it ( 'returns negative isFocusVisible result after toggling browser tabs without prior keyboard navigation' , async function ( ) {
190233 ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
191- setupFocus ( iframeRoot ) ;
234+ addWindowFocusTracking ( iframeRoot ) ;
192235
193236 // Fire focus in iframe
194237 await waitFor ( ( ) => {
@@ -206,7 +249,7 @@ describe('useFocusVisible', function () {
206249
207250 it ( 'returns positive isFocusVisible result after toggling browser window after keyboard navigation' , async function ( ) {
208251 ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
209- setupFocus ( iframeRoot ) ;
252+ addWindowFocusTracking ( iframeRoot ) ;
210253
211254 // Fire focus in iframe
212255 await waitFor ( ( ) => {
@@ -225,7 +268,7 @@ describe('useFocusVisible', function () {
225268
226269 it ( 'returns negative isFocusVisible result after toggling browser window without prior keyboard navigation' , async function ( ) {
227270 ReactDOMRender ( < Example id = "iframe-example" /> , iframeRoot ) ;
228- setupFocus ( iframeRoot ) ;
271+ addWindowFocusTracking ( iframeRoot ) ;
229272
230273 // Fire focus in iframe
231274 await waitFor ( ( ) => {
0 commit comments