@@ -8,13 +8,13 @@ import {
88 ERR_VIRTUAL_NOT_STARTED ,
99} from "./errors" ;
1010import { getLiveSpokenPhrase , LIVE } from "./getLiveSpokenPhrase" ;
11+ import { type UserEvent , userEvent } from "@testing-library/user-event" ;
1112import { flattenTree } from "./flattenTree" ;
1213import { getElementNode } from "./commands/getElementNode" ;
1314import { getItemText } from "./getItemText" ;
1415import { getSpokenPhrase } from "./getSpokenPhrase" ;
1516import { observeDOM } from "./observeDOM" ;
1617import { tick } from "./tick" ;
17- import { userEvent } from "@testing-library/user-event" ;
1818import type { VirtualCommandArgs } from "./commands/types" ;
1919
2020/**
@@ -100,6 +100,15 @@ export interface StartOptions {
100100 * Defaults to `false`.
101101 */
102102 displayCursor ?: boolean ;
103+
104+ /**
105+ * A function to be called internally to advance your fake timers (if applicable)
106+ *
107+ * @example jest.advanceTimersByTime
108+ *
109+ * @returns A promise that resolves after the specified delay, or void if not asynchronous.
110+ */
111+ advanceTimers ?: ( delay : number ) => Promise < void > | void ;
103112}
104113
105114const defaultUserEventOptions = {
@@ -206,6 +215,7 @@ export class Virtual {
206215 #treeCache: AccessibilityNode [ ] | null = null ;
207216 #disconnectDOMObserver: ( ( ) => void ) | null = null ;
208217 #boundHandleFocusChange: ( ( event : Event ) => Promise < void > ) | null = null ;
218+ #userEvent: UserEvent | null = null ;
209219
210220 #checkContainer( ) {
211221 if ( ! this . #container) {
@@ -284,7 +294,7 @@ export class Virtual {
284294 * REF: https://www.w3.org/TR/wai-aria-1.2/#aria-modal
285295 */
286296 return tree . filter (
287- ( { parentDialog } ) => this . #activeNode! . parentDialog === parentDialog
297+ ( { parentDialog } ) => this . #activeNode! . parentDialog === parentDialog ,
288298 ) ;
289299 }
290300
@@ -330,7 +340,7 @@ export class Virtual {
330340 getLiveSpokenPhrase ( {
331341 container,
332342 mutation,
333- } )
343+ } ) ,
334344 )
335345 . filter ( Boolean )
336346 . forEach ( ( spokenPhrase ) => {
@@ -342,7 +352,7 @@ export class Virtual {
342352 return this . #spokenPhraseLog. filter (
343353 ( spokenPhrase ) =>
344354 ! spokenPhrase . startsWith ( LIVE . ASSERTIVE ) &&
345- ! spokenPhrase . startsWith ( LIVE . POLITE )
355+ ! spokenPhrase . startsWith ( LIVE . POLITE ) ,
346356 ) ;
347357 }
348358
@@ -368,7 +378,7 @@ export class Virtual {
368378 // cursor has changed.
369379 const tree = this . #getAccessibilityTree( ) ;
370380 const parentDialogNode = tree . find (
371- ( { node } ) => node === accessibilityNode . parentDialog
381+ ( { node } ) => node === accessibilityNode . parentDialog ,
372382 ) ! ;
373383
374384 const spokenPhrase = getSpokenPhrase ( parentDialogNode ) ;
@@ -426,7 +436,7 @@ export class Virtual {
426436 accessibleValue === this . #activeNode?. accessibleValue &&
427437 node === this . #activeNode?. node &&
428438 role === this . #activeNode?. role &&
429- spokenRole === this . #activeNode?. spokenRole
439+ spokenRole === this . #activeNode?. spokenRole ,
430440 ) ;
431441 }
432442
@@ -490,8 +500,8 @@ export class Virtual {
490500 get commands ( ) {
491501 return Object . fromEntries < keyof VirtualCommands > (
492502 ( Object . keys ( commands ) as ( keyof VirtualCommands ) [ ] ) . map (
493- ( command : keyof VirtualCommands ) => [ command , command ]
494- )
503+ ( command : keyof VirtualCommands ) => [ command , command ] ,
504+ ) ,
495505 ) as { [ K in keyof VirtualCommands ] : K } ;
496506 }
497507
@@ -568,10 +578,15 @@ export class Virtual {
568578 // @ts -ignore for non-TS users we default the container to `null` which
569579 // prompts the missing container error.
570580 async start (
571- { container, displayCursor = false , window : root } : StartOptions = {
581+ {
582+ container,
583+ displayCursor = false ,
584+ window : root ,
585+ advanceTimers,
586+ } : StartOptions = {
572587 container : null as never ,
573588 displayCursor : false ,
574- }
589+ } ,
575590 ) {
576591 if ( ! container ) {
577592 throw new Error ( ERR_VIRTUAL_MISSING_CONTAINER ) ;
@@ -593,9 +608,15 @@ export class Virtual {
593608 ( mutations : MutationRecord [ ] ) => {
594609 this . #invalidateTreeCache( ) ;
595610 this . #announceLiveRegions( mutations ) ;
596- }
611+ } ,
597612 ) ;
598613
614+ this . #userEvent = userEvent . setup ( {
615+ ...defaultUserEventOptions ,
616+ document : container . ownerDocument ?? globalThis . document ,
617+ ...( advanceTimers ? { advanceTimers } : { } ) ,
618+ } ) ;
619+
599620 const tree = this . #getAccessibilityTree( ) ;
600621
601622 if ( ! tree . length ) {
@@ -634,7 +655,10 @@ export class Virtual {
634655 */
635656 async stop ( ) {
636657 this . #disconnectDOMObserver?.( ) ;
637- this . #container?. removeEventListener ( "focusin" , this . #boundHandleFocusChange) ;
658+ this . #container?. removeEventListener (
659+ "focusin" ,
660+ this . #boundHandleFocusChange,
661+ ) ;
638662 this . #invalidateTreeCache( ) ;
639663
640664 if ( this . #cursor) {
@@ -647,6 +671,7 @@ export class Virtual {
647671 this . #itemTextLog = [ ] ;
648672 this . #spokenPhraseLog = [ ] ;
649673 this . #boundHandleFocusChange = null ;
674+ this . #userEvent = null ;
650675 return ;
651676 }
652677
@@ -762,6 +787,7 @@ export class Virtual {
762787 */
763788 async act ( ) {
764789 this . #checkContainer( ) ;
790+
765791 await tick ( ) ;
766792
767793 if ( ! this . #activeNode) {
@@ -776,7 +802,7 @@ export class Virtual {
776802 *
777803 * REF: https://www.w3.org/TR/core-aam-1.2/#mapping_actions
778804 */
779- await userEvent . click ( target , defaultUserEventOptions ) ;
805+ await this . # userEvent! . click ( target ) ;
780806
781807 return ;
782808 }
@@ -878,7 +904,7 @@ export class Virtual {
878904 ] . join ( "" ) ;
879905
880906 this . #focusActiveElement( ) ;
881- await userEvent . keyboard ( keyboardCommand , defaultUserEventOptions ) ;
907+ await this . # userEvent! . keyboard ( keyboardCommand ) ;
882908 await this . #refreshState( true ) ;
883909
884910 return ;
@@ -918,7 +944,7 @@ export class Virtual {
918944 }
919945
920946 const target = getElementNode ( this . #activeNode) ;
921- await userEvent . type ( target , text , defaultUserEventOptions ) ;
947+ await this . # userEvent! . type ( target , text ) ;
922948 await this . #refreshState( true ) ;
923949
924950 return ;
@@ -949,7 +975,7 @@ export class Virtual {
949975 */
950976 async perform <
951977 T extends keyof VirtualCommands ,
952- K extends Omit < Parameters < VirtualCommands [ T ] > [ 0 ] , keyof VirtualCommandArgs >
978+ K extends Omit < Parameters < VirtualCommands [ T ] > [ 0 ] , keyof VirtualCommandArgs > ,
953979 > ( command : T , options ?: { [ L in keyof K ] : K [ L ] } ) {
954980 this . #checkContainer( ) ;
955981 await tick ( ) ;
@@ -1023,10 +1049,7 @@ export class Virtual {
10231049 const keys = key . repeat ( clickCount ) ;
10241050 const target = getElementNode ( this . #activeNode) ;
10251051
1026- await userEvent . pointer (
1027- [ { target } , { keys, target } ] ,
1028- defaultUserEventOptions
1029- ) ;
1052+ await this . #userEvent! . pointer ( [ { target } , { keys, target } ] ) ;
10301053
10311054 return ;
10321055 }
0 commit comments