Skip to content

Commit c2f74d3

Browse files
committed
fix: validate test init before executing the test command
1 parent 534b2db commit c2f74d3

File tree

8 files changed

+165
-29
lines changed

8 files changed

+165
-29
lines changed

lib/commands/test-init.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ class TestInitCommand implements ICommand {
3737
const dependencies = this.frameworkDependencies[frameworkToInstall] || [];
3838
const modulesToInstall: IDependencyInformation[] = [
3939
{
40-
name: 'karma',
41-
// Hardcode the version unitl https://github.com/karma-runner/karma/issues/3052 is fixed
42-
version: "2.0.2"
40+
name: 'karma'
4341
},
4442
{
4543
name: `karma-${frameworkToInstall}`

lib/commands/test.ts

+66-23
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,73 @@
11
import * as helpers from "../common/helpers";
22

3-
function RunKarmaTestCommandFactory(platform: string) {
4-
return function RunKarmaTestCommand($options: IOptions, $testExecutionService: ITestExecutionService, $projectData: IProjectData, $analyticsService: IAnalyticsService, $platformEnvironmentRequirements: IPlatformEnvironmentRequirements) {
5-
$projectData.initializeProjectData();
6-
$analyticsService.setShouldDispose($options.justlaunch || !$options.watch);
7-
const projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: $options.release });
8-
this.execute = (args: string[]): Promise<void> => $testExecutionService.startKarmaServer(platform, $projectData, projectFilesConfig);
9-
this.canExecute = (args: string[]): Promise<boolean> => canExecute({ $platformEnvironmentRequirements, $projectData, $options, platform });
10-
this.allowedParameters = [];
11-
};
12-
}
3+
abstract class TestCommandBase {
4+
public allowedParameters: ICommandParameter[] = [];
5+
private projectFilesConfig: IProjectFilesConfig;
6+
protected abstract $projectData: IProjectData;
7+
protected abstract $testExecutionService: ITestExecutionService;
8+
protected abstract $analyticsService: IAnalyticsService;
9+
protected abstract $options: IOptions;
10+
protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements;
11+
protected abstract $errors: IErrors;
12+
13+
constructor(private platform: string) {
14+
}
15+
16+
async execute(args: string[]): Promise<void> {
17+
await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig);
18+
}
1319

14-
async function canExecute(input: { $platformEnvironmentRequirements: IPlatformEnvironmentRequirements, $projectData: IProjectData, $options: IOptions, platform: string }): Promise<boolean> {
15-
const { $platformEnvironmentRequirements, $projectData, $options, platform } = input;
16-
const output = await $platformEnvironmentRequirements.checkEnvironmentRequirements({
17-
platform,
18-
projectDir: $projectData.projectDir,
19-
options: $options,
20-
notConfiguredEnvOptions: {
21-
hideSyncToPreviewAppOption: true,
22-
hideCloudBuildOption: true
20+
async canExecute(args: string[]): Promise<boolean | ICanExecuteCommandOutput> {
21+
this.$projectData.initializeProjectData();
22+
this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch);
23+
this.projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release });
24+
25+
const output = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({
26+
platform: this.platform,
27+
projectDir: this.$projectData.projectDir,
28+
options: this.$options,
29+
notConfiguredEnvOptions: {
30+
hideSyncToPreviewAppOption: true,
31+
hideCloudBuildOption: true
32+
}
33+
});
34+
35+
const canStartKarmaServer = await this.$testExecutionService.canStartKarmaServer(this.$projectData);
36+
if (!canStartKarmaServer) {
37+
this.$errors.fail({
38+
formatStr: "Error: In order to run unit tests, your project must already be configured by running $ tns test init.",
39+
suppressCommandHelp: true,
40+
errorCode: ErrorCodes.TESTS_INIT_REQUIRED
41+
});
2342
}
24-
});
2543

26-
return output.canExecute;
44+
return output.canExecute && canStartKarmaServer;
45+
}
46+
}
47+
48+
class TestAndroidCommand extends TestCommandBase implements ICommand {
49+
constructor(protected $projectData: IProjectData,
50+
protected $testExecutionService: ITestExecutionService,
51+
protected $analyticsService: IAnalyticsService,
52+
protected $options: IOptions,
53+
protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
54+
protected $errors: IErrors) {
55+
super("android");
56+
}
57+
58+
}
59+
60+
class TestIosCommand extends TestCommandBase implements ICommand {
61+
constructor(protected $projectData: IProjectData,
62+
protected $testExecutionService: ITestExecutionService,
63+
protected $analyticsService: IAnalyticsService,
64+
protected $options: IOptions,
65+
protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
66+
protected $errors: IErrors) {
67+
super("iOS");
68+
}
69+
2770
}
2871

29-
$injector.registerCommand("test|android", RunKarmaTestCommandFactory('android'));
30-
$injector.registerCommand("test|ios", RunKarmaTestCommandFactory('iOS'));
72+
$injector.registerCommand("test|android", TestAndroidCommand);
73+
$injector.registerCommand("test|ios", TestIosCommand);

lib/common/declarations.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,7 @@ declare const enum ErrorCodes {
596596
KARMA_FAIL = 130,
597597
UNHANDLED_REJECTION_FAILURE = 131,
598598
DELETED_KILL_FILE = 132,
599+
TESTS_INIT_REQUIRED = 133
599600
}
600601

601602
interface IFutureDispatcher {

lib/common/errors.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as util from "util";
22
import * as path from "path";
33
import { SourceMapConsumer } from "source-map";
4+
import { isInteractive } from "./helpers";
45

56
// we need this to overwrite .stack property (read-only in Error)
67
function Exception() {
@@ -159,7 +160,7 @@ export class Errors implements IErrors {
159160
} catch (ex) {
160161
const loggerLevel: string = $injector.resolve("logger").getLevel().toUpperCase();
161162
const printCallStack = this.printCallStack || loggerLevel === "TRACE" || loggerLevel === "DEBUG";
162-
const message = printCallStack ? resolveCallStack(ex) : `\x1B[31;1m${ex.message}\x1B[0m`;
163+
const message = printCallStack ? resolveCallStack(ex) : isInteractive() ? `\x1B[31;1m${ex.message}\x1B[0m` : ex.message;
163164

164165
if (ex.printOnStdout) {
165166
this.$injector.resolve("logger").out(message);

lib/definitions/project.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ interface IValidatePlatformOutput {
465465

466466
interface ITestExecutionService {
467467
startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise<void>;
468+
canStartKarmaServer(projectData: IProjectData): Promise<boolean>;
468469
}
469470

470471
/**

lib/services/test-execution-service.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ interface IKarmaConfigOptions {
77
debugTransport: boolean;
88
}
99

10-
class TestExecutionService implements ITestExecutionService {
10+
export class TestExecutionService implements ITestExecutionService {
1111
private static CONFIG_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/config.js`;
1212
private static SOCKETIO_JS_FILE_NAME = `node_modules/${constants.TEST_RUNNER_NAME}/socket.io.js`;
1313

@@ -163,6 +163,19 @@ class TestExecutionService implements ITestExecutionService {
163163
});
164164
}
165165

166+
public async canStartKarmaServer(projectData: IProjectData): Promise<boolean> {
167+
let canStartKarmaServer = true;
168+
const requiredDependencies = ["karma", "nativescript-unit-test-runner"];
169+
_.each(requiredDependencies, (dep) => {
170+
if (!projectData.dependencies[dep] && !projectData.devDependencies[dep]) {
171+
canStartKarmaServer = false;
172+
return;
173+
}
174+
});
175+
176+
return canStartKarmaServer;
177+
}
178+
166179
allowedParameters: ICommandParameter[] = [];
167180

168181
private generateConfig(port: string, options: any): string {
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { InjectorStub } from "../stubs";
2+
import { TestExecutionService } from "../../lib/services/test-execution-service";
3+
import { assert } from "chai";
4+
5+
const karmaPluginName = "karma";
6+
const unitTestsPluginName = "nativescript-unit-test-runner";
7+
8+
function getTestExecutionService(): ITestExecutionService {
9+
const injector = new InjectorStub();
10+
injector.register("testExecutionService", TestExecutionService);
11+
12+
return injector.resolve("testExecutionService");
13+
}
14+
15+
function getDependenciesObj(deps: string[]): IDictionary<string> {
16+
const depsObj: IDictionary<string> = {};
17+
deps.forEach(dep => {
18+
depsObj[dep] = "1.0.0";
19+
});
20+
21+
return depsObj;
22+
}
23+
24+
describe("testExecutionService", () => {
25+
const testCases = [
26+
{
27+
name: "should return false when the project has no dependencies and dev dependencies",
28+
expectedCanStartKarmaServer: false,
29+
projectData: { dependencies: {}, devDependencies: {} }
30+
},
31+
{
32+
name: "should return false when the project has no karma",
33+
expectedCanStartKarmaServer: false,
34+
projectData: { dependencies: getDependenciesObj([unitTestsPluginName]), devDependencies: {} }
35+
},
36+
{
37+
name: "should return false when the project has no unit test runner",
38+
expectedCanStartKarmaServer: false,
39+
projectData: { dependencies: getDependenciesObj([karmaPluginName]), devDependencies: {} }
40+
},
41+
{
42+
name: "should return true when the project has the required plugins as dependencies",
43+
expectedCanStartKarmaServer: true,
44+
projectData: { dependencies: getDependenciesObj([karmaPluginName, unitTestsPluginName]), devDependencies: {} }
45+
},
46+
{
47+
name: "should return true when the project has the required plugins as dev dependencies",
48+
expectedCanStartKarmaServer: true,
49+
projectData: { dependencies: {}, devDependencies: getDependenciesObj([karmaPluginName, unitTestsPluginName]) }
50+
},
51+
{
52+
name: "should return true when the project has the required plugins as dev and normal dependencies",
53+
expectedCanStartKarmaServer: true,
54+
projectData: { dependencies: getDependenciesObj([karmaPluginName]), devDependencies: getDependenciesObj([unitTestsPluginName]) }
55+
}
56+
];
57+
58+
describe("canStartKarmaServer", () => {
59+
_.each(testCases, (testCase: any) => {
60+
it(`${testCase.name}`, async () => {
61+
const testExecutionService = getTestExecutionService();
62+
const canStartKarmaServer = await testExecutionService.canStartKarmaServer(testCase.projectData);
63+
assert.equal(canStartKarmaServer, testCase.expectedCanStartKarmaServer);
64+
});
65+
});
66+
});
67+
});

test/stubs.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ export class AndroidBundleValidatorHelper implements IAndroidBundleValidatorHelp
913913

914914
export class PerformanceService implements IPerformanceService {
915915
now(): number { return 10; }
916-
processExecutionData() {}
916+
processExecutionData() { }
917917
}
918918

919919
export class InjectorStub extends Yok implements IInjector {
@@ -942,5 +942,17 @@ export class InjectorStub extends Yok implements IInjector {
942942
this.register('projectData', ProjectDataStub);
943943
this.register('packageInstallationManager', PackageInstallationManagerStub);
944944
this.register('packageInstallationManager', PackageInstallationManagerStub);
945+
this.register("httpClient", {
946+
httpRequest: async (options: any, proxySettings?: IProxySettings): Promise<Server.IResponse> => undefined
947+
});
948+
this.register("pluginsService", {
949+
add: async (): Promise<void> => undefined,
950+
remove: async (): Promise<void> => undefined,
951+
ensureAllDependenciesAreInstalled: () => { return Promise.resolve(); },
952+
});
953+
this.register("devicesService", {
954+
getDevice: (): Mobile.IDevice => undefined,
955+
getDeviceByIdentifier: (): Mobile.IDevice => undefined
956+
});
945957
}
946958
}

0 commit comments

Comments
 (0)