@@ -25,6 +25,9 @@ const mocks = vi.hoisted(() => ({
2525 getMetroInstance : vi . fn ( ) ,
2626 isMetroCacheReusable : vi . fn ( ( ) => false ) ,
2727 logMetroCacheReused : vi . fn ( ) ,
28+ logRunnerStarting : vi . fn ( ) ,
29+ logRunnerStillWaitingInQueue : vi . fn ( ) ,
30+ logRunnerWaitingInQueue : vi . fn ( ) ,
2831 waitForMetroBackedAppReady : vi . fn ( ) ,
2932} ) ) ;
3033
@@ -47,6 +50,9 @@ vi.mock('@react-native-harness/bridge/server', () => ({
4750
4851vi . mock ( '../logs.js' , ( ) => ( {
4952 logMetroCacheReused : mocks . logMetroCacheReused ,
53+ logRunnerStarting : mocks . logRunnerStarting ,
54+ logRunnerStillWaitingInQueue : mocks . logRunnerStillWaitingInQueue ,
55+ logRunnerWaitingInQueue : mocks . logRunnerWaitingInQueue ,
5056} ) ) ;
5157
5258vi . mock ( '@react-native-harness/tools' , async ( ) => {
@@ -316,6 +322,7 @@ describe('getHarness', () => {
316322
317323 const platform : HarnessPlatform = {
318324 config : { } ,
325+ getResourceLockKey : ( ) => 'ios:test-platform-ready-timeout' ,
319326 name : 'ios' ,
320327 platformId : 'ios' ,
321328 runner : `data:text/javascript,${ encodeURIComponent (
@@ -354,6 +361,7 @@ describe('getHarness', () => {
354361
355362 const platform : HarnessPlatform = {
356363 config : { } ,
364+ getResourceLockKey : ( ) => 'ios:test-platform-init-signal' ,
357365 name : 'ios' ,
358366 platformId : 'ios' ,
359367 runner : `data:text/javascript,${ encodeURIComponent (
@@ -378,6 +386,41 @@ describe('getHarness', () => {
378386 await harness . dispose ( ) ;
379387 } ) ;
380388
389+ it ( 'falls back to a default resource lock key for platforms without getResourceLockKey' , async ( ) => {
390+ const { serverBridge } = createBridgeServer ( ) ;
391+ const appMonitor = createAppMonitor ( ) ;
392+ const platformInstance = createPlatformRunner ( {
393+ createAppMonitor : ( ) => appMonitor . appMonitor ,
394+ } ) ;
395+ const metroInstance = createMetroInstance ( ) ;
396+
397+ mocks . getBridgeServer . mockResolvedValue ( serverBridge ) ;
398+ mocks . getMetroInstance . mockResolvedValue ( metroInstance ) ;
399+
400+ (
401+ globalThis as typeof globalThis & {
402+ __HARNESS_PLATFORM_RUNNER__ ?: ( ...args : unknown [ ] ) => Promise < unknown > ;
403+ }
404+ ) . __HARNESS_PLATFORM_RUNNER__ = vi . fn ( async ( ) => platformInstance ) ;
405+
406+ const platform : HarnessPlatform = {
407+ config : { } ,
408+ name : 'legacy-ios' ,
409+ platformId : 'ios' ,
410+ runner : `data:text/javascript,${ encodeURIComponent (
411+ 'export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);'
412+ ) } `,
413+ } ;
414+
415+ const harness = await getHarness (
416+ createHarnessConfig ( ) ,
417+ platform ,
418+ '/tmp/project'
419+ ) ;
420+
421+ await harness . dispose ( ) ;
422+ } ) ;
423+
381424 it ( 'routes ensureAppReady through the shared Metro startup helper' , async ( ) => {
382425 const { serverBridge, emitReady } = createBridgeServer ( ) ;
383426 const appMonitor = createAppMonitor ( ) ;
@@ -418,6 +461,7 @@ describe('getHarness', () => {
418461 runner : `data:text/javascript,${ encodeURIComponent (
419462 'export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);'
420463 ) } `,
464+ getResourceLockKey : ( ) => 'ios:simulator:iPhone 17 Pro:26.2' ,
421465 } ;
422466
423467 const harness = await getHarness (
@@ -483,6 +527,7 @@ describe('getHarness', () => {
483527 runner : `data:text/javascript,${ encodeURIComponent (
484528 'export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);'
485529 ) } `,
530+ getResourceLockKey : ( ) => 'ios:simulator:iPhone 17 Pro:26.2' ,
486531 } ;
487532
488533 const harness = await getHarness (
@@ -612,6 +657,7 @@ describe('plugins', () => {
612657 runner : `data:text/javascript,${ encodeURIComponent (
613658 'export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);'
614659 ) } `,
660+ getResourceLockKey : ( ) => 'ios:simulator:iPhone 17 Pro:26.2' ,
615661 } ;
616662
617663 const harness = await getHarness (
@@ -656,6 +702,75 @@ describe('plugins', () => {
656702 'beforeDispose:1:normal' ,
657703 ] ) ;
658704 } ) ;
705+
706+ it ( 'waits in queue before starting Metro and releases the lock on dispose' , async ( ) => {
707+ const resourceKey = 'ios:simulator:iPhone 17 Pro:26.2' ;
708+ const firstPlatformRunner = createPlatformRunner ( ) ;
709+ const secondPlatformRunner = createPlatformRunner ( ) ;
710+ const secondAppMonitor = createAppMonitor ( ) ;
711+ const firstMetroInstance = createMetroInstance ( ) ;
712+ const secondMetroInstance = createMetroInstance ( ) ;
713+ const firstBridge = createBridgeServer ( ) ;
714+ const secondBridge = createBridgeServer ( ) ;
715+
716+ mocks . getBridgeServer
717+ . mockResolvedValueOnce ( firstBridge . serverBridge )
718+ . mockResolvedValueOnce ( secondBridge . serverBridge ) ;
719+ mocks . getMetroInstance
720+ . mockResolvedValueOnce ( firstMetroInstance )
721+ . mockResolvedValueOnce ( secondMetroInstance ) ;
722+
723+ let invocationCount = 0 ;
724+ (
725+ globalThis as typeof globalThis & {
726+ __HARNESS_PLATFORM_RUNNER__ ?: ( ...args : unknown [ ] ) => Promise < unknown > ;
727+ }
728+ ) . __HARNESS_PLATFORM_RUNNER__ = vi . fn ( async ( ) => {
729+ invocationCount += 1 ;
730+ return invocationCount === 1
731+ ? firstPlatformRunner
732+ : createPlatformRunner ( {
733+ createAppMonitor : ( ) => secondAppMonitor . appMonitor ,
734+ dispose : secondPlatformRunner . dispose ,
735+ } ) ;
736+ } ) ;
737+
738+ const platform : HarnessPlatform = {
739+ config : { } ,
740+ name : 'ios' ,
741+ platformId : 'ios' ,
742+ runner : `data:text/javascript,${ encodeURIComponent (
743+ 'export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);'
744+ ) } `,
745+ getResourceLockKey : ( ) => resourceKey ,
746+ } ;
747+
748+ const firstHarness = await getHarness (
749+ createHarnessConfig ( ) ,
750+ platform ,
751+ '/tmp/project'
752+ ) ;
753+
754+ const secondHarnessPromise = getHarness (
755+ createHarnessConfig ( ) ,
756+ platform ,
757+ '/tmp/project'
758+ ) ;
759+
760+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1100 ) ) ;
761+
762+ expect ( mocks . logRunnerWaitingInQueue ) . toHaveBeenCalledWith ( platform ) ;
763+ expect ( mocks . logRunnerStarting ) . not . toHaveBeenCalled ( ) ;
764+ expect ( mocks . getMetroInstance ) . toHaveBeenCalledTimes ( 1 ) ;
765+
766+ await firstHarness . dispose ( ) ;
767+ const secondHarness = await secondHarnessPromise ;
768+
769+ expect ( mocks . logRunnerStarting ) . toHaveBeenCalledWith ( platform ) ;
770+ expect ( mocks . getMetroInstance ) . toHaveBeenCalledTimes ( 2 ) ;
771+
772+ await secondHarness . dispose ( ) ;
773+ } ) ;
659774} ) ;
660775
661776describe ( 'StartupStallError' , ( ) => {
0 commit comments