Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import child_process from 'child_process';
import fs from 'fs';
import {IOSProjectInfo} from '@react-native-community/cli-types';
import {Device} from '../../../types';
import {runOnSimulator} from '../runOnSimulator';
import {buildProject} from '../../buildCommand/buildProject';
import installApp from '../installApp';
import {FlagsT} from '../createRun';

jest.mock('child_process');
jest.mock('fs', () => ({existsSync: jest.fn()}));
jest.mock('../../buildCommand/buildProject');
jest.mock('../installApp');

const xcodeProject: IOSProjectInfo = {
name: 'TestApp.xcworkspace',
path: '/path/to/TestApp.xcworkspace',
isWorkspace: true,
};

const simulator: Device = {
name: 'iPhone 15',
udid: 'AAAA-BBBB-CCCC',
state: 'Booted',
type: 'simulator',
};

const args = {} as FlagsT;
const developerDir = '/Applications/Xcode.app/Contents/Developer';

beforeEach(() => {
jest.clearAllMocks();
(buildProject as jest.Mock).mockResolvedValue('');
(installApp as jest.Mock).mockResolvedValue(undefined);
(child_process.execFileSync as jest.Mock).mockReturnValue(
`${developerDir}\n`,
);
});

test('opens Simulator.app with the device UDID when it exists', async () => {
(fs.existsSync as jest.Mock).mockImplementation((target) =>
String(target).endsWith('Simulator.app'),
);

await runOnSimulator(
xcodeProject,
'ios',
'Debug',
'TestApp',
args,
simulator,
);

expect(child_process.execFileSync).toHaveBeenCalledWith('open', [
`${developerDir}/Applications/Simulator.app`,
'--args',
'-CurrentDeviceUDID',
simulator.udid,
]);
});

test('falls back to DeviceHub via devices:// deep link when Simulator.app is absent', async () => {
(fs.existsSync as jest.Mock).mockImplementation((target) =>
String(target).endsWith('DeviceHub.app'),
);

await runOnSimulator(
xcodeProject,
'ios',
'Debug',
'TestApp',
args,
simulator,
);

// DeviceHub registers the `devices://` URL scheme, which focuses the device by UDID.
expect(child_process.execFileSync).toHaveBeenCalledWith('open', [
`devices://device/open?id=${simulator.udid}`,
]);
// The deep link replaces the Simulator.app `-CurrentDeviceUDID` argument.
expect(child_process.execFileSync).not.toHaveBeenCalledWith(
'open',
expect.arrayContaining(['-CurrentDeviceUDID']),
);
});

test('does not boot the simulator when it is already booted', async () => {
(fs.existsSync as jest.Mock).mockReturnValue(true);

await runOnSimulator(
xcodeProject,
'ios',
'Debug',
'TestApp',
args,
simulator,
);

expect(child_process.spawnSync).not.toHaveBeenCalledWith(
'xcrun',
expect.arrayContaining(['boot']),
);
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import child_process from 'child_process';
import fs from 'fs';
import path from 'path';
import {IOSProjectInfo} from '@react-native-community/cli-types';
import {logger} from '@react-native-community/cli-tools';
import {ApplePlatform, Device} from '../../types';
Expand Down Expand Up @@ -32,12 +34,34 @@ export async function runOnSimulator(
.execFileSync('xcode-select', ['-p'], {encoding: 'utf8'})
.trim();

child_process.execFileSync('open', [
`${activeDeveloperDir}/Applications/Simulator.app`,
'--args',
'-CurrentDeviceUDID',
simulator.udid,
]);
// Xcode 27 replaces Simulator.app with DeviceHub.app and relocates it from
// <Xcode>/Contents/Developer/Applications to <Xcode>/Contents/Applications.
// Prefer Simulator.app while it exists (stable Xcode); fall back to DeviceHub
// for Xcode 27+. See https://developer.apple.com/documentation/xcode/device-hub
const simulatorApp = path.join(
activeDeveloperDir,
'Applications',
'Simulator.app',
);
const deviceHubApp = path.join(
activeDeveloperDir,
'..',
'Applications',
'DeviceHub.app',
);

if (fs.existsSync(simulatorApp)) {
child_process.execFileSync('open', [
simulatorApp,
'--args',
'-CurrentDeviceUDID',
simulator.udid,
]);
} else if (fs.existsSync(deviceHubApp)) {
child_process.execFileSync('open', [
`devices://device/open?id=${simulator.udid}`,
]);
}

if (simulator.state !== 'Booted') {
bootSimulator(simulator);
Expand Down
Loading