@@ -11,6 +11,9 @@ import * as simctl from '../xcrun/simctl.js';
1111import * as devicectl from '../xcrun/devicectl.js' ;
1212import * as libimobiledevice from '../libimobiledevice.js' ;
1313import { HarnessAppPathError } from '../errors.js' ;
14+ import { mkdtempSync , mkdirSync , rmSync } from 'node:fs' ;
15+ import { tmpdir } from 'node:os' ;
16+ import { join } from 'node:path' ;
1417
1518const harnessConfig = {
1619 metroPort : DEFAULT_METRO_PORT ,
@@ -201,8 +204,95 @@ describe('iOS platform instance dependency validation', () => {
201204 expect ( shutdownSimulator ) . toHaveBeenCalledWith ( 'sim-udid' ) ;
202205 } ) ;
203206
207+ it ( 'waits for a simulator that is already booting' , async ( ) => {
208+ vi . spyOn ( simctl , 'getSimulatorId' ) . mockResolvedValue ( 'sim-udid' ) ;
209+ vi . spyOn ( simctl , 'getSimulatorStatus' ) . mockResolvedValue ( 'Booting' ) ;
210+ const bootSimulator = vi
211+ . spyOn ( simctl , 'bootSimulator' )
212+ . mockResolvedValue ( undefined ) ;
213+ const waitForBoot = vi
214+ . spyOn ( simctl , 'waitForBoot' )
215+ . mockResolvedValue ( undefined ) ;
216+ vi . spyOn ( simctl , 'isAppInstalled' ) . mockResolvedValue ( true ) ;
217+ vi . spyOn ( simctl , 'applyHarnessJsLocationOverride' ) . mockResolvedValue (
218+ undefined
219+ ) ;
220+ vi . spyOn ( simctl , 'stopApp' ) . mockResolvedValue ( undefined ) ;
221+ vi . spyOn ( simctl , 'clearHarnessJsLocationOverride' ) . mockResolvedValue (
222+ undefined
223+ ) ;
224+ const shutdownSimulator = vi
225+ . spyOn ( simctl , 'shutdownSimulator' )
226+ . mockResolvedValue ( undefined ) ;
227+
228+ const instance = await getAppleSimulatorPlatformInstance (
229+ {
230+ name : 'ios' ,
231+ device : {
232+ type : 'simulator' ,
233+ name : 'iPhone 16 Pro' ,
234+ systemVersion : '18.0' ,
235+ } ,
236+ bundleId : 'com.harnessplayground' ,
237+ } ,
238+ harnessConfig
239+ ) ;
240+
241+ expect ( bootSimulator ) . not . toHaveBeenCalled ( ) ;
242+ expect ( waitForBoot ) . toHaveBeenCalledWith ( 'sim-udid' ) ;
243+
244+ await instance . dispose ( ) ;
245+
246+ expect ( shutdownSimulator ) . not . toHaveBeenCalled ( ) ;
247+ } ) ;
248+
249+ it ( 'boots and waits for other non-booted simulator states' , async ( ) => {
250+ vi . spyOn ( simctl , 'getSimulatorId' ) . mockResolvedValue ( 'sim-udid' ) ;
251+ vi . spyOn ( simctl , 'getSimulatorStatus' ) . mockResolvedValue ( 'Creating' ) ;
252+ const bootSimulator = vi
253+ . spyOn ( simctl , 'bootSimulator' )
254+ . mockResolvedValue ( undefined ) ;
255+ const waitForBoot = vi
256+ . spyOn ( simctl , 'waitForBoot' )
257+ . mockResolvedValue ( undefined ) ;
258+ vi . spyOn ( simctl , 'isAppInstalled' ) . mockResolvedValue ( true ) ;
259+ vi . spyOn ( simctl , 'applyHarnessJsLocationOverride' ) . mockResolvedValue (
260+ undefined
261+ ) ;
262+ vi . spyOn ( simctl , 'stopApp' ) . mockResolvedValue ( undefined ) ;
263+ vi . spyOn ( simctl , 'clearHarnessJsLocationOverride' ) . mockResolvedValue (
264+ undefined
265+ ) ;
266+ const shutdownSimulator = vi
267+ . spyOn ( simctl , 'shutdownSimulator' )
268+ . mockResolvedValue ( undefined ) ;
269+
270+ const instance = await getAppleSimulatorPlatformInstance (
271+ {
272+ name : 'ios' ,
273+ device : {
274+ type : 'simulator' ,
275+ name : 'iPhone 16 Pro' ,
276+ systemVersion : '18.0' ,
277+ } ,
278+ bundleId : 'com.harnessplayground' ,
279+ } ,
280+ harnessConfig
281+ ) ;
282+
283+ expect ( bootSimulator ) . toHaveBeenCalledWith ( 'sim-udid' ) ;
284+ expect ( waitForBoot ) . toHaveBeenCalledWith ( 'sim-udid' ) ;
285+
286+ await instance . dispose ( ) ;
287+
288+ expect ( shutdownSimulator ) . toHaveBeenCalledWith ( 'sim-udid' ) ;
289+ } ) ;
290+
204291 it ( 'installs the app from HARNESS_APP_PATH when missing' , async ( ) => {
205- vi . stubEnv ( 'HARNESS_APP_PATH' , '/tmp/HarnessPlayground.app' ) ;
292+ const appDir = mkdtempSync ( join ( tmpdir ( ) , 'rn-harness-ios-app-' ) ) ;
293+ const bundlePath = join ( appDir , 'HarnessPlayground.app' ) ;
294+ mkdirSync ( bundlePath ) ;
295+ vi . stubEnv ( 'HARNESS_APP_PATH' , bundlePath ) ;
206296 vi . spyOn ( simctl , 'getSimulatorId' ) . mockResolvedValue ( 'sim-udid' ) ;
207297 vi . spyOn ( simctl , 'getSimulatorStatus' ) . mockResolvedValue ( 'Booted' ) ;
208298 vi . spyOn ( simctl , 'isAppInstalled' ) . mockResolvedValue ( false ) ;
@@ -212,30 +302,27 @@ describe('iOS platform instance dependency validation', () => {
212302 vi . spyOn ( simctl , 'applyHarnessJsLocationOverride' ) . mockResolvedValue (
213303 undefined
214304 ) ;
215- const existsSync = vi
216- . spyOn ( await import ( 'node:fs' ) , 'existsSync' )
217- . mockReturnValue ( true ) ;
218305
219- await expect (
220- getAppleSimulatorPlatformInstance (
221- {
222- name : 'ios' ,
223- device : {
224- type : 'simulator' ,
225- name : 'iPhone 16 Pro' ,
226- systemVersion : '18.0' ,
306+ try {
307+ await expect (
308+ getAppleSimulatorPlatformInstance (
309+ {
310+ name : 'ios' ,
311+ device : {
312+ type : 'simulator' ,
313+ name : 'iPhone 16 Pro' ,
314+ systemVersion : '18.0' ,
315+ } ,
316+ bundleId : 'com.harnessplayground' ,
227317 } ,
228- bundleId : 'com.harnessplayground' ,
229- } ,
230- harnessConfig
231- )
232- ) . resolves . toBeDefined ( ) ;
318+ harnessConfig
319+ )
320+ ) . resolves . toBeDefined ( ) ;
233321
234- expect ( existsSync ) . toHaveBeenCalledWith ( '/tmp/HarnessPlayground.app' ) ;
235- expect ( installApp ) . toHaveBeenCalledWith (
236- 'sim-udid' ,
237- '/tmp/HarnessPlayground.app'
238- ) ;
322+ expect ( installApp ) . toHaveBeenCalledWith ( 'sim-udid' , bundlePath ) ;
323+ } finally {
324+ rmSync ( appDir , { force : true , recursive : true } ) ;
325+ }
239326 } ) ;
240327
241328 it ( 'throws a HarnessAppPathError when HARNESS_APP_PATH is missing' , async ( ) => {
@@ -260,11 +347,13 @@ describe('iOS platform instance dependency validation', () => {
260347 } ) ;
261348
262349 it ( 'throws a HarnessAppPathError when HARNESS_APP_PATH points to a missing app' , async ( ) => {
263- vi . stubEnv ( 'HARNESS_APP_PATH' , '/tmp/missing.app' ) ;
350+ vi . stubEnv (
351+ 'HARNESS_APP_PATH' ,
352+ join ( tmpdir ( ) , 'rn-harness-ios-missing-app' , 'Missing.app' )
353+ ) ;
264354 vi . spyOn ( simctl , 'getSimulatorId' ) . mockResolvedValue ( 'sim-udid' ) ;
265355 vi . spyOn ( simctl , 'getSimulatorStatus' ) . mockResolvedValue ( 'Booted' ) ;
266356 vi . spyOn ( simctl , 'isAppInstalled' ) . mockResolvedValue ( false ) ;
267- vi . spyOn ( await import ( 'node:fs' ) , 'existsSync' ) . mockReturnValue ( false ) ;
268357
269358 await expect (
270359 getAppleSimulatorPlatformInstance (
0 commit comments