diff --git a/packages/cli-platform-apple/src/commands/runCommand/__tests__/createRun.test.ts b/packages/cli-platform-apple/src/commands/runCommand/__tests__/createRun.test.ts
new file mode 100644
index 000000000..d4dd3d9ab
--- /dev/null
+++ b/packages/cli-platform-apple/src/commands/runCommand/__tests__/createRun.test.ts
@@ -0,0 +1,172 @@
+///
+import {Config} from '@react-native-community/cli-types';
+import createRun from '../createRun';
+import listDevices from '../../../tools/listDevices';
+import {runOnSimulator} from '../runOnSimulator';
+import {runOnDevice} from '../runOnDevice';
+import {getXcodeProjectAndDir} from '../../buildCommand/getXcodeProjectAndDir';
+import {getConfiguration} from '../../buildCommand/getConfiguration';
+import {getFallbackSimulator} from '../getFallbackSimulator';
+import {Device} from '../../../types';
+import path from 'path';
+
+const packageRoot = path.resolve(__dirname, '../../../../');
+
+jest.mock('../../../tools/listDevices');
+jest.mock('../runOnSimulator');
+jest.mock('../runOnDevice');
+jest.mock('../../buildCommand/getXcodeProjectAndDir');
+jest.mock('../../buildCommand/getConfiguration');
+jest.mock('../getFallbackSimulator');
+
+const fallbackSimulator: Device = {
+ name: 'iPhone 14',
+ udid: 'FALLBACK-SIM-UDID',
+ type: 'simulator',
+ state: 'Shutdown',
+ version: '17.0',
+};
+
+const bootedSimulator: Device = {
+ name: 'iPhone 17',
+ udid: 'BOOTED-SIM-UDID',
+ type: 'simulator',
+ state: 'Booted',
+ version: '26.2',
+};
+
+const physicalDevice: Device = {
+ name: 'Stefan’s iPhone 16',
+ udid: 'PHYSICAL-DEVICE-UDID',
+ type: 'device',
+};
+
+function buildCtx(): Config {
+ return {
+ root: packageRoot,
+ reactNativePath: '',
+ reactNativeVersion: 'unknown',
+ project: {
+ ios: {
+ sourceDir: packageRoot,
+ automaticPodsInstallation: false,
+ },
+ },
+ dependencies: {},
+ } as unknown as Config;
+}
+
+function buildArgs(overrides: Record = {}) {
+ return {
+ packager: false,
+ port: 8081,
+ terminal: undefined,
+ listDevices: false,
+ interactive: false,
+ onlyPods: false,
+ forcePods: false,
+ ...overrides,
+ } as any;
+}
+
+describe('createRun no-flag default targeting (issue #2765)', () => {
+ let chdirSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ chdirSpy = jest.spyOn(process, 'chdir').mockImplementation(() => {});
+ (getXcodeProjectAndDir as jest.Mock).mockReturnValue({
+ xcodeProject: {name: 'Demo', isWorkspace: true},
+ sourceDir: packageRoot,
+ });
+ (getConfiguration as jest.Mock).mockResolvedValue({
+ mode: 'Debug',
+ scheme: 'Demo',
+ });
+ (getFallbackSimulator as jest.Mock).mockReturnValue(fallbackSimulator);
+ (runOnSimulator as jest.Mock).mockResolvedValue(undefined);
+ (runOnDevice as jest.Mock).mockResolvedValue(undefined);
+ });
+
+ afterEach(() => {
+ chdirSpy.mockRestore();
+ });
+
+ test('connected iPhone + no booted simulator + no flags -> launches fallback simulator only', async () => {
+ (listDevices as jest.Mock).mockResolvedValue([physicalDevice]);
+
+ await createRun({platformName: 'ios'})([], buildCtx(), buildArgs());
+
+ expect(runOnSimulator).toHaveBeenCalledTimes(1);
+ expect(runOnSimulator).toHaveBeenCalledWith(
+ expect.anything(),
+ 'ios',
+ 'Debug',
+ 'Demo',
+ expect.anything(),
+ fallbackSimulator,
+ );
+ expect(runOnDevice).not.toHaveBeenCalled();
+ });
+
+ test('connected iPhone + booted simulator + no flags -> launches booted simulator only', async () => {
+ (listDevices as jest.Mock).mockResolvedValue([
+ physicalDevice,
+ bootedSimulator,
+ ]);
+
+ await createRun({platformName: 'ios'})([], buildCtx(), buildArgs());
+
+ expect(runOnSimulator).toHaveBeenCalledTimes(1);
+ expect(runOnSimulator).toHaveBeenCalledWith(
+ expect.anything(),
+ 'ios',
+ 'Debug',
+ 'Demo',
+ expect.anything(),
+ bootedSimulator,
+ );
+ expect(runOnDevice).not.toHaveBeenCalled();
+ });
+
+ test('connected iPhone + --device "name" -> runs on the physical device', async () => {
+ (listDevices as jest.Mock).mockResolvedValue([
+ physicalDevice,
+ bootedSimulator,
+ ]);
+
+ await createRun({platformName: 'ios'})(
+ [],
+ buildCtx(),
+ buildArgs({device: physicalDevice.name}),
+ );
+
+ expect(runOnDevice).toHaveBeenCalledTimes(1);
+ expect(runOnDevice).toHaveBeenCalledWith(
+ physicalDevice,
+ 'ios',
+ 'Debug',
+ 'Demo',
+ expect.anything(),
+ expect.anything(),
+ );
+ expect(runOnSimulator).not.toHaveBeenCalled();
+ });
+
+ test('no devices, no flags -> launches fallback simulator', async () => {
+ (listDevices as jest.Mock).mockResolvedValue([fallbackSimulator]);
+
+ await createRun({platformName: 'ios'})([], buildCtx(), buildArgs());
+
+ expect(runOnSimulator).toHaveBeenCalledTimes(1);
+ expect(runOnSimulator).toHaveBeenCalledWith(
+ expect.anything(),
+ 'ios',
+ 'Debug',
+ 'Demo',
+ expect.anything(),
+ fallbackSimulator,
+ );
+ expect(runOnDevice).not.toHaveBeenCalled();
+ });
+});
diff --git a/packages/cli-platform-apple/src/commands/runCommand/createRun.ts b/packages/cli-platform-apple/src/commands/runCommand/createRun.ts
index fa16ebe53..4a600b1da 100644
--- a/packages/cli-platform-apple/src/commands/runCommand/createRun.ts
+++ b/packages/cli-platform-apple/src/commands/runCommand/createRun.ts
@@ -266,48 +266,38 @@ const createRun =
const bootedSimulators = devices.filter(
({state, type}) => state === 'Booted' && type === 'simulator',
);
- const bootedDevices = devices.filter(({type}) => type === 'device'); // Physical devices here are always booted
- const booted = [...bootedSimulators, ...bootedDevices];
+ const connectedDevices = devices.filter(({type}) => type === 'device');
- if (booted.length === 0) {
- logger.info(
- 'No booted devices or simulators found. Launching first available simulator...',
- );
- return runOnSimulator(
- xcodeProject,
- platformName,
- mode,
- scheme,
- args,
- fallbackSimulator,
- );
- }
-
- logger.info(`Found booted ${booted.map(({name}) => name).join(', ')}`);
+ const targetSimulator = bootedSimulators[0] ?? fallbackSimulator;
- for (const simulator of bootedSimulators) {
- await runOnSimulator(
- xcodeProject,
- platformName,
- mode,
- scheme,
- args,
- simulator || fallbackSimulator,
+ if (bootedSimulators.length === 0) {
+ logger.info(
+ 'No booted simulators found. Launching first available simulator...',
);
+ } else {
+ logger.info(`Found booted ${targetSimulator.name}`);
}
- for (const device of bootedDevices) {
- await runOnDevice(
- device,
- platformName,
- mode,
- scheme,
- xcodeProject,
- args,
+ if (connectedDevices.length > 0) {
+ logger.info(
+ `Ignoring connected ${
+ connectedDevices.length === 1 ? 'device' : 'devices'
+ } (${connectedDevices
+ .map(({name}) => name)
+ .join(
+ ', ',
+ )}). Pass --device or --udid to install on a physical device.`,
);
}
- return;
+ return runOnSimulator(
+ xcodeProject,
+ platformName,
+ mode,
+ scheme,
+ args,
+ targetSimulator,
+ );
}
if (args.device && args.udid) {