@@ -64,6 +64,7 @@ vi.mock('../../../platforms/ios/apps.ts', async (importOriginal) => {
6464 ...actual ,
6565 listIosApps : vi . fn ( async ( ) => [ ] ) ,
6666 resolveIosApp : vi . fn ( async ( ) => undefined ) ,
67+ resolveIosSimulatorDeepLinkBundleId : vi . fn ( async ( ) => undefined ) ,
6768 } ;
6869} ) ;
6970vi . mock ( '../../app-log.ts' , async ( importOriginal ) => {
@@ -108,7 +109,7 @@ import {
108109 ensureAndroidEmulatorBooted ,
109110} from '../../../platforms/android/devices.ts' ;
110111import { listAppleDevices } from '../../../platforms/ios/devices.ts' ;
111- import { resolveIosApp } from '../../../platforms/ios/apps.ts' ;
112+ import { resolveIosApp , resolveIosSimulatorDeepLinkBundleId } from '../../../platforms/ios/apps.ts' ;
112113import { startAppLog , stopAppLog } from '../../app-log.ts' ;
113114import { defaultInstallOps , defaultReinstallOps } from '../session-deploy.ts' ;
114115import { clearRequestCanceled , markRequestCanceled } from '../../request-cancel.ts' ;
@@ -129,6 +130,7 @@ const mockRunCmd = vi.mocked(runCmd);
129130const mockListAndroidDevices = vi . mocked ( listAndroidDevices ) ;
130131const mockListAppleDevices = vi . mocked ( listAppleDevices ) ;
131132const mockResolveIosApp = vi . mocked ( resolveIosApp ) ;
133+ const mockResolveIosSimulatorDeepLinkBundleId = vi . mocked ( resolveIosSimulatorDeepLinkBundleId ) ;
132134const mockEnsureAndroidEmulatorBooted = vi . mocked ( ensureAndroidEmulatorBooted ) ;
133135const mockStartAppLog = vi . mocked ( startAppLog ) ;
134136const mockStopAppLog = vi . mocked ( stopAppLog ) ;
@@ -177,6 +179,8 @@ beforeEach(() => {
177179 }
178180 return app . includes ( '.' ) ? app : `com.example.${ normalizedApp } ` ;
179181 } ) ;
182+ mockResolveIosSimulatorDeepLinkBundleId . mockReset ( ) ;
183+ mockResolveIosSimulatorDeepLinkBundleId . mockResolvedValue ( undefined ) ;
180184 mockEnsureAndroidEmulatorBooted . mockReset ( ) ;
181185 mockStartAppLog . mockReset ( ) ;
182186 mockStopAppLog . mockReset ( ) ;
@@ -1865,6 +1869,102 @@ test('open URL on existing iOS device session preserves app bundle id context',
18651869 expect ( dispatchedContext ?. appBundleId ) . toBe ( 'com.example.app' ) ;
18661870} ) ;
18671871
1872+ test ( 'open custom URL on existing iOS simulator session preserves app bundle id context' , async ( ) => {
1873+ const sessionStore = makeSessionStore ( ) ;
1874+ const sessionName = 'ios-simulator-session' ;
1875+ sessionStore . set ( sessionName , {
1876+ ...makeSession ( sessionName , {
1877+ platform : 'ios' ,
1878+ id : 'sim-1' ,
1879+ name : 'iPhone 17 Pro' ,
1880+ kind : 'simulator' ,
1881+ booted : true ,
1882+ } ) ,
1883+ appBundleId : 'com.example.app' ,
1884+ appName : 'Example App' ,
1885+ } ) ;
1886+ mockResolveTargetDevice . mockResolvedValue ( {
1887+ platform : 'ios' ,
1888+ id : 'sim-1' ,
1889+ name : 'iPhone 17 Pro' ,
1890+ kind : 'simulator' ,
1891+ booted : true ,
1892+ } ) ;
1893+
1894+ let dispatchedContext : Record < string , unknown > | undefined ;
1895+ mockDispatch . mockImplementation ( async ( _device , _command , _positionals , _out , context ) => {
1896+ dispatchedContext = context as Record < string , unknown > | undefined ;
1897+ return { } ;
1898+ } ) ;
1899+
1900+ const response = await handleSessionCommands ( {
1901+ req : {
1902+ token : 't' ,
1903+ session : sessionName ,
1904+ command : 'open' ,
1905+ positionals : [ 'myapp://item/42' ] ,
1906+ flags : { } ,
1907+ } ,
1908+ sessionName,
1909+ logPath : path . join ( os . tmpdir ( ) , 'daemon.log' ) ,
1910+ sessionStore,
1911+ invoke : noopInvoke ,
1912+ } ) ;
1913+
1914+ expect ( response ) . toBeTruthy ( ) ;
1915+ expect ( response ?. ok ) . toBe ( true ) ;
1916+ const updated = sessionStore . get ( sessionName ) ;
1917+ expect ( updated ?. appBundleId ) . toBe ( 'com.example.app' ) ;
1918+ expect ( updated ?. appName ) . toBe ( 'myapp://item/42' ) ;
1919+ expect ( dispatchedContext ?. appBundleId ) . toBe ( 'com.example.app' ) ;
1920+ } ) ;
1921+
1922+ test ( 'open custom URL on fresh iOS simulator session infers app bundle id from URL scheme' , async ( ) => {
1923+ const sessionStore = makeSessionStore ( ) ;
1924+ const sessionName = 'ios-simulator-url-session' ;
1925+ mockResolveTargetDevice . mockResolvedValue ( {
1926+ platform : 'ios' ,
1927+ id : 'sim-1' ,
1928+ name : 'iPhone 17 Pro' ,
1929+ kind : 'simulator' ,
1930+ booted : true ,
1931+ } ) ;
1932+ mockResolveIosSimulatorDeepLinkBundleId . mockResolvedValue ( 'org.reactnavigation.playground' ) ;
1933+
1934+ let dispatchedContext : Record < string , unknown > | undefined ;
1935+ mockDispatch . mockImplementation ( async ( _device , _command , _positionals , _out , context ) => {
1936+ dispatchedContext = context as Record < string , unknown > | undefined ;
1937+ return { } ;
1938+ } ) ;
1939+
1940+ const response = await handleSessionCommands ( {
1941+ req : {
1942+ token : 't' ,
1943+ session : sessionName ,
1944+ command : 'open' ,
1945+ positionals : [ 'rne://navigator-layout' ] ,
1946+ flags : { platform : 'ios' , udid : 'sim-1' } ,
1947+ } ,
1948+ sessionName,
1949+ logPath : path . join ( os . tmpdir ( ) , 'daemon.log' ) ,
1950+ sessionStore,
1951+ invoke : noopInvoke ,
1952+ } ) ;
1953+
1954+ expect ( response ) . toBeTruthy ( ) ;
1955+ expect ( response ?. ok ) . toBe ( true ) ;
1956+ expect ( mockResolveIosSimulatorDeepLinkBundleId ) . toHaveBeenCalledWith (
1957+ expect . objectContaining ( { id : 'sim-1' , kind : 'simulator' } ) ,
1958+ 'rne://navigator-layout' ,
1959+ ) ;
1960+ const updated = sessionStore . get ( sessionName ) ;
1961+ expect ( updated ?. appBundleId ) . toBe ( 'org.reactnavigation.playground' ) ;
1962+ expect ( updated ?. appName ) . toBe ( 'rne://navigator-layout' ) ;
1963+ expect ( dispatchedContext ?. appBundleId ) . toBe ( 'org.reactnavigation.playground' ) ;
1964+ expect ( mockPrewarmIosRunnerSession ) . toHaveBeenCalledTimes ( 1 ) ;
1965+ expect ( mockPrewarmIosRunnerXctestrun ) . not . toHaveBeenCalled ( ) ;
1966+ } ) ;
1967+
18681968test ( 'open iOS app session prewarms runner session when app bundle id is known' , async ( ) => {
18691969 const sessionStore = makeSessionStore ( ) ;
18701970 const sessionName = 'ios-device-session' ;
@@ -1904,7 +2004,7 @@ test('open iOS app session prewarms runner session when app bundle id is known',
19042004 expect ( mockPrewarmIosRunnerXctestrun ) . not . toHaveBeenCalled ( ) ;
19052005} ) ;
19062006
1907- test ( 'open iOS URL without app bundle id keeps xctestrun-only prewarm' , async ( ) => {
2007+ test ( 'open iOS URL without app bundle id skips runner prewarm' , async ( ) => {
19082008 const sessionStore = makeSessionStore ( ) ;
19092009 const sessionName = 'ios-device-session' ;
19102010 sessionStore . set (
@@ -1935,7 +2035,7 @@ test('open iOS URL without app bundle id keeps xctestrun-only prewarm', async ()
19352035 expect ( response ) . toBeTruthy ( ) ;
19362036 expect ( response ?. ok ) . toBe ( true ) ;
19372037 expect ( mockPrewarmIosRunnerSession ) . not . toHaveBeenCalled ( ) ;
1938- expect ( mockPrewarmIosRunnerXctestrun ) . toHaveBeenCalledTimes ( 1 ) ;
2038+ expect ( mockPrewarmIosRunnerXctestrun ) . not . toHaveBeenCalled ( ) ;
19392039} ) ;
19402040
19412041test ( 'open web URL on iOS device session without active app falls back to Safari' , async ( ) => {
0 commit comments