diff --git a/docs/man_pages/project/testing/dev-test-android.md b/docs/man_pages/project/testing/dev-test-android.md index a23194f3c5..dbd619b428 100644 --- a/docs/man_pages/project/testing/dev-test-android.md +++ b/docs/man_pages/project/testing/dev-test-android.md @@ -9,7 +9,7 @@ Run tests on a selected device | `$ ns test android --device [--watc Runs the tests in your project on connected Android devices and running native emulators.<% if(isConsole) { %> Your project must already be configured for unit testing by running `$ ns test init`.<% } %> ### Options -* `--watch` - If set, when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-run. +* `--watch` - Enabled by default; when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-run. Pass `--no-watch` to run the tests once. In CI environments (when the `CI` environment variable is set), it defaults to disabled. * `--device` - Specifies the serial number or the index of the connected device on which to run the tests. To list all connected devices, grouped by platform, run `$ ns device` * `--debug-brk` - Runs the tests under the debugger. The debugger will break just before your tests are executed, so you have a chance to place breakpoints. diff --git a/docs/man_pages/project/testing/dev-test-ios.md b/docs/man_pages/project/testing/dev-test-ios.md index e441336582..425a643f30 100644 --- a/docs/man_pages/project/testing/dev-test-ios.md +++ b/docs/man_pages/project/testing/dev-test-ios.md @@ -13,7 +13,7 @@ Runs the tests in your project on connected iOS devices or the iOS Simulator.<% <% if((isConsole && isMacOS) || isHtml) { %> ### Options -* `--watch` - If set, when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-ran. +* `--watch` - Enabled by default; when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-ran. Pass `--no-watch` to run the tests once. In CI environments (when the `CI` environment variable is set), it defaults to disabled. * `--device` - Specifies the serial number or the index of the connected device on which you want to run tests. To list all connected devices, grouped by platform, run `$ ns device`. You cannot set `--device` and `--emulator` simultaneously. * `--emulator` - Runs tests on the iOS Simulator. You cannot set `--device` and `--emulator` simultaneously. * `--debug-brk` - Runs the tests under the debugger. The debugger will break just before your tests are executed, so you have a chance to place breakpoints. diff --git a/docs/man_pages/project/testing/test-android.md b/docs/man_pages/project/testing/test-android.md index 46d3f460a5..a28198aa13 100644 --- a/docs/man_pages/project/testing/test-android.md +++ b/docs/man_pages/project/testing/test-android.md @@ -18,7 +18,7 @@ Run tests on a selected device | `$ ns test android --device [--watc ### Options -* `--watch` - If set, when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-run. +* `--watch` - Enabled by default; when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-run. Pass `--no-watch` to run the tests once. In CI environments (when the `CI` environment variable is set), it defaults to disabled. * `--device` - Specifies the serial number or the index of the connected device on which to run the tests. To list all connected devices, grouped by platform, run `$ ns device`. `` is the device index or identifier as listed by the `$ ns device` command. * `--debug-brk` - Runs the tests under the debugger. The debugger will break just before your tests are executed, so you have a chance to place breakpoints. * `--env.*` - Specifies additional flags that the bundler may process. Can be passed multiple times. Supported additional flags: diff --git a/docs/man_pages/project/testing/test-ios.md b/docs/man_pages/project/testing/test-ios.md index dac2ccfccb..b2939c8b2a 100644 --- a/docs/man_pages/project/testing/test-ios.md +++ b/docs/man_pages/project/testing/test-ios.md @@ -23,7 +23,7 @@ Run tests in the iOS Simulator | `$ ns test ios --emulator [--watch] [--debug-br ### Options -* `--watch` - If set, when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-ran. +* `--watch` - Enabled by default; when you save changes to the project, changes are automatically synchronized to the connected device and tests are re-ran. Pass `--no-watch` to run the tests once. In CI environments (when the `CI` environment variable is set), it defaults to disabled. * `--device` - Specifies the serial number or the index of the connected device on which you want to run tests. To list all connected devices, grouped by platform, run `$ ns device`. You cannot set `--device` and `--emulator` simultaneously. `` is the device index or identifier as listed by the `$ ns device` command. * `--emulator` - Runs tests on the iOS Simulator. You cannot set `--device` and `--emulator` simultaneously. * `--debug-brk` - Runs the tests under the debugger. The debugger will break just before your tests are executed, so you have a chance to place breakpoints. diff --git a/lib/commands/test.ts b/lib/commands/test.ts index 646af87e3e..ccbdcf97af 100644 --- a/lib/commands/test.ts +++ b/lib/commands/test.ts @@ -1,4 +1,4 @@ -import { hasValidAndroidSigning } from "../common/helpers"; +import { hasValidAndroidSigning, isCIEnvironment } from "../common/helpers"; import { ANDROID_RELEASE_BUILD_ERROR_MESSAGE, ANDROID_APP_BUNDLE_SIGNING_ERROR_MESSAGE, @@ -25,6 +25,13 @@ abstract class TestCommandBase { public allowedParameters: ICommandParameter[] = []; public dashedOptions = { hmr: { type: OptionType.Boolean, default: false, hasSensitiveValue: false }, + // Watch mode keeps the run alive waiting for changes; CI runs must + // execute once and exit, so --watch defaults to off there. + watch: { + type: OptionType.Boolean, + default: !isCIEnvironment(), + hasSensitiveValue: false, + }, }; protected abstract platform: string; @@ -214,7 +221,7 @@ class TestAndroidCommand extends TestCommandBase implements ICommand { } } -class TestIosCommand extends TestCommandBase implements ICommand { +export class TestIosCommand extends TestCommandBase implements ICommand { protected platform = "iOS"; constructor( diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index 878fcb9206..ad9006070d 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -349,7 +349,7 @@ function isRunningInTTY(): boolean { ); } -function isCIEnvironment(): boolean { +export function isCIEnvironment(): boolean { // The following CI environments set their own environment variables that we respect: // travis: "CI", // circleCI: "CI", diff --git a/test/commands/test.ts b/test/commands/test.ts new file mode 100644 index 0000000000..edb17ba513 --- /dev/null +++ b/test/commands/test.ts @@ -0,0 +1,120 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { Options } from "../../lib/options"; +import { TestIosCommand } from "../../lib/commands/test"; +import { IOptions } from "../../lib/declarations"; +import { ICommand } from "../../lib/common/definitions/commands"; +import { IInjector } from "../../lib/common/definitions/yok"; +import { IConfigurationSettings } from "../../lib/common/declarations"; + +const CI_ENVIRONMENT_VARIABLES = ["CI", "JENKINS_HOME"]; + +function createTestInjector(): IInjector { + const testInjector = new Yok(); + testInjector.register("settingsService", { + setSettings: (settings: IConfigurationSettings): any => undefined, + getProfileDir: () => "profileDir", + }); + testInjector.register("errors", { + fail: (message: string): never => { + throw new Error(message); + }, + failWithHelp: (message: string): never => { + throw new Error(message); + }, + }); + testInjector.register("logger", { + warn: (message: string): void => undefined, + }); + + testInjector.register("projectData", {}); + testInjector.register("testExecutionService", {}); + testInjector.register("vitestExecutionService", {}); + testInjector.register("analyticsService", {}); + testInjector.register("platformEnvironmentRequirements", {}); + testInjector.register("cleanupService", {}); + testInjector.register("liveSyncCommandHelper", {}); + testInjector.register("devicesService", {}); + testInjector.register("migrateController", {}); + + return testInjector; +} + +interface IResolvedTestCommand { + command: ICommand; + options: IOptions; +} + +function resolveTestIosCommand(): IResolvedTestCommand { + const testInjector = createTestInjector(); + const options = testInjector.resolve(Options); + testInjector.register("options", options); + testInjector.registerCommand("test|ios", TestIosCommand); + const command = testInjector.resolveCommand("test|ios"); + return { command, options }; +} + +function validateWithArgs( + resolved: IResolvedTestCommand, + args: string[] = [], +): void { + args.forEach((arg) => process.argv.push(arg)); + resolved.options.validateOptions(resolved.command.dashedOptions); + args.forEach(() => process.argv.pop()); +} + +describe("test ios command", () => { + const savedCiEnvironment: { [key: string]: string } = {}; + + beforeEach(() => { + CI_ENVIRONMENT_VARIABLES.forEach((name) => { + savedCiEnvironment[name] = process.env[name]; + delete process.env[name]; + }); + }); + + afterEach(() => { + CI_ENVIRONMENT_VARIABLES.forEach((name) => { + if (savedCiEnvironment[name] === undefined) { + delete process.env[name]; + } else { + process.env[name] = savedCiEnvironment[name]; + } + }); + }); + + describe("--watch option", () => { + it("defaults to off in CI environments", () => { + process.env.CI = "true"; + const resolved = resolveTestIosCommand(); + validateWithArgs(resolved); + assert.isFalse(resolved.options.argv.watch); + }); + + it("defaults to off when JENKINS_HOME is set", () => { + process.env.JENKINS_HOME = "/var/jenkins"; + const resolved = resolveTestIosCommand(); + validateWithArgs(resolved); + assert.isFalse(resolved.options.argv.watch); + }); + + it("defaults to on outside CI environments", () => { + const resolved = resolveTestIosCommand(); + validateWithArgs(resolved); + assert.isTrue(resolved.options.argv.watch); + }); + + it("stays on when --watch is passed explicitly in CI", () => { + process.env.CI = "true"; + const resolved = resolveTestIosCommand(); + validateWithArgs(resolved, ["--watch"]); + assert.isTrue(resolved.options.argv.watch); + }); + + it("stays off when --no-watch is passed outside CI", () => { + const resolved = resolveTestIosCommand(); + validateWithArgs(resolved, ["--no-watch"]); + assert.isFalse(resolved.options.argv.watch); + }); + }); +});