11import { EventEmitter } from 'node:events' ;
2+ import fs from 'node:fs' ;
3+ import os from 'node:os' ;
4+ import path from 'node:path' ;
25import { PassThrough } from 'node:stream' ;
36import { beforeEach , describe , expect , it , vi } from 'vitest' ;
47import { SubprocessError } from '@react-native-harness/tools' ;
@@ -24,6 +27,8 @@ import * as avdConfig from '../avd-config.js';
2427const createAbortError = ( ) =>
2528 new DOMException ( 'The operation was aborted' , 'AbortError' ) ;
2629
30+ let sdkRoot : string ;
31+
2732const createMockChildProcess = ( ) => {
2833 const process = new EventEmitter ( ) as EventEmitter & {
2934 stdout : PassThrough ;
@@ -40,6 +45,16 @@ const createMockChildProcess = () => {
4045
4146beforeEach ( ( ) => {
4247 vi . restoreAllMocks ( ) ;
48+ vi . unstubAllEnvs ( ) ;
49+ sdkRoot = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'android-sdk-' ) ) ;
50+ fs . mkdirSync ( path . join ( sdkRoot , 'emulator' ) , { recursive : true } ) ;
51+ fs . writeFileSync ( path . join ( sdkRoot , 'emulator' , 'emulator' ) , '' ) ;
52+ fs . mkdirSync ( path . join ( sdkRoot , 'platform-tools' ) , { recursive : true } ) ;
53+ fs . writeFileSync ( path . join ( sdkRoot , 'platform-tools' , 'adb' ) , '' ) ;
54+ vi . stubEnv ( 'ANDROID_HOME' , sdkRoot ) ;
55+ vi . spyOn ( environment , 'ensureAndroidEmulatorAvailable' ) . mockResolvedValue (
56+ sdkRoot ,
57+ ) ;
4358} ) ;
4459
4560describe ( 'getStartAppArgs' , ( ) => {
@@ -152,7 +167,7 @@ describe('getStartAppArgs', () => {
152167 } ) ;
153168
154169 it ( 'checks whether an AVD exists' , async ( ) => {
155- vi . spyOn ( tools , 'spawn' ) . mockResolvedValueOnce ( {
170+ vi . spyOn ( tools , 'spawn' ) . mockResolvedValue ( {
156171 stdout : 'Pixel_6_API_33\nPixel_8_API_35\n' ,
157172 } as Awaited < ReturnType < typeof tools . spawn > > ) ;
158173
@@ -561,17 +576,29 @@ describe('getStartAppArgs', () => {
561576
562577 it ( 'aborts while waiting for boot completion' , async ( ) => {
563578 vi . useFakeTimers ( ) ;
564- const spawnSpy = vi . spyOn ( tools , 'spawn' ) ;
565- spawnSpy
566- . mockResolvedValueOnce ( {
567- stdout : 'List of devices attached\nemulator-5554\tdevice\n' ,
568- } as Awaited < ReturnType < typeof tools . spawn > > )
569- . mockResolvedValueOnce ( {
570- stdout : 'Pixel_8_API_35\n' ,
571- } as Awaited < ReturnType < typeof tools . spawn > > )
572- . mockResolvedValueOnce ( {
573- stdout : '0\n' ,
574- } as Awaited < ReturnType < typeof tools . spawn > > ) ;
579+ vi . spyOn ( tools , 'spawn' ) . mockImplementation (
580+ ( async ( _file : string , args ?: readonly string [ ] ) => {
581+ if ( args ?. [ 0 ] === 'devices' ) {
582+ return {
583+ stdout : 'List of devices attached\nemulator-5554\tdevice\n' ,
584+ } as Awaited < ReturnType < typeof tools . spawn > > ;
585+ }
586+
587+ if ( args ?. includes ( 'emu' ) && args ?. includes ( 'avd' ) && args ?. includes ( 'name' ) ) {
588+ return {
589+ stdout : 'Pixel_8_API_35\n' ,
590+ } as Awaited < ReturnType < typeof tools . spawn > > ;
591+ }
592+
593+ if ( args ?. includes ( 'getprop' ) ) {
594+ return {
595+ stdout : '0\n' ,
596+ } as Awaited < ReturnType < typeof tools . spawn > > ;
597+ }
598+
599+ throw new Error ( `Unexpected adb call: ${ args ?. join ( ' ' ) } ` ) ;
600+ } ) as typeof tools . spawn ,
601+ ) ;
575602 const controller = new AbortController ( ) ;
576603 const waitPromise = waitForBoot ( 'Pixel_8_API_35' , controller . signal ) ;
577604
0 commit comments