Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/man_pages/project/testing/dev-test-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Run tests on a selected device | `$ ns test android --device <Device ID> [--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. Defaults to disabled in CI environments (when the `CI` or `JENKINS_HOME` environment variable is set).
* `--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.

Expand Down
2 changes: 1 addition & 1 deletion docs/man_pages/project/testing/dev-test-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. Defaults to disabled in CI environments (when the `CI` or `JENKINS_HOME` environment variable is set).
* `--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.
Expand Down
2 changes: 1 addition & 1 deletion docs/man_pages/project/testing/test-android.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Run tests on a selected device | `$ ns test android --device <Device ID> [--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. Defaults to disabled in CI environments (when the `CI` or `JENKINS_HOME` environment variable is set).
* `--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`. `<Device ID>` 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:
Expand Down
2 changes: 1 addition & 1 deletion docs/man_pages/project/testing/test-ios.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. Defaults to disabled in CI environments (when the `CI` or `JENKINS_HOME` environment variable is set).
* `--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. `<Device ID>` 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.
Expand Down
11 changes: 9 additions & 2 deletions lib/commands/test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion lib/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion lib/services/vitest-execution-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ export class VitestExecutionService implements IVitestExecutionService {
);
}

if (this.$options.watch) {
// $options.watch is on by default outside CI, so only an explicit flag
// tells us the user actually asked for watch mode.
const watchPassed = process.argv.some(
(arg) => arg === "--watch" || arg.startsWith("--watch="),
);
if (watchPassed && this.$options.watch) {
this.$logger.warn(
"'--watch' is not supported for on-device Vitest runs yet; running once.",
);
Expand Down
120 changes: 120 additions & 0 deletions test/commands/test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
85 changes: 85 additions & 0 deletions test/services/vitest-execution-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { assert } from "chai";
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import { VitestExecutionService } from "../../lib/services/vitest-execution-service";
import { IProjectData } from "../../lib/definitions/project";

const WATCH_WARNING =
"'--watch' is not supported for on-device Vitest runs yet; running once.";

describe("vitestExecutionService", () => {
let projectDir: string;
let warnings: string[];
let originalArgv: string[];

beforeEach(() => {
projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "ns-vitest-"));
const vitestDir = path.join(projectDir, "node_modules", "vitest");
fs.mkdirSync(vitestDir, { recursive: true });
fs.writeFileSync(
path.join(vitestDir, "package.json"),
JSON.stringify({ name: "vitest", version: "0.0.0" }),
);
warnings = [];
originalArgv = process.argv;
});

afterEach(() => {
process.argv = originalArgv;
fs.rmSync(projectDir, { recursive: true, force: true });
});

const startTestRun = async (
argv: string[],
options: { watch: boolean },
): Promise<void> => {
process.argv = ["node", "ns", "test", "ios", ...argv];
const service = new VitestExecutionService(
<any>{
spawnFromEvent: async () => ({ stdout: "", stderr: "", exitCode: 0 }),
},
<any>{
fail: (message: string): never => {
throw new Error(message);
},
},
<any>{},
<any>{ warn: (message: string) => warnings.push(message) },
<any>options,
);
await service.startTestRun("iOS", <IProjectData>{ projectDir });
};

describe("--watch warning", () => {
it("is not shown when watch is only on by default", async () => {
await startTestRun([], { watch: true });
assert.notInclude(warnings, WATCH_WARNING);
});

it("is shown when --watch is passed explicitly", async () => {
await startTestRun(["--watch"], { watch: true });
assert.include(warnings, WATCH_WARNING);
});

it("is shown when --watch=true is passed", async () => {
await startTestRun(["--watch=true"], { watch: true });
assert.include(warnings, WATCH_WARNING);
});

it("is not shown when --no-watch is passed", async () => {
await startTestRun(["--no-watch"], { watch: false });
assert.notInclude(warnings, WATCH_WARNING);
});

it("is not shown when --watch false is passed", async () => {
await startTestRun(["--watch", "false"], { watch: false });
assert.notInclude(warnings, WATCH_WARNING);
});

it("is not shown when --watch=false is passed", async () => {
await startTestRun(["--watch=false"], { watch: false });
assert.notInclude(warnings, WATCH_WARNING);
});
});
});
Loading