Skip to content

Commit 90ba02c

Browse files
author
Dimitar Tachev
authored
Merge pull request #4381 from NativeScript/tachev/validate-test
fix: validate test init before executing the test command
2 parents e11faaa + c5a8767 commit 90ba02c

File tree

7 files changed

+165
-25
lines changed

7 files changed

+165
-25
lines changed

lib/commands/test.ts

+68-23
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,75 @@
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 platform: string;
7+
protected abstract $projectData: IProjectData;
8+
protected abstract $testExecutionService: ITestExecutionService;
9+
protected abstract $analyticsService: IAnalyticsService;
10+
protected abstract $options: IOptions;
11+
protected abstract $platformEnvironmentRequirements: IPlatformEnvironmentRequirements;
12+
protected abstract $errors: IErrors;
13+
14+
async execute(args: string[]): Promise<void> {
15+
await this.$testExecutionService.startKarmaServer(this.platform, this.$projectData, this.projectFilesConfig);
16+
}
17+
18+
async canExecute(args: string[]): Promise<boolean | ICanExecuteCommandOutput> {
19+
this.$projectData.initializeProjectData();
20+
this.$analyticsService.setShouldDispose(this.$options.justlaunch || !this.$options.watch);
21+
this.projectFilesConfig = helpers.getProjectFilesConfig({ isReleaseBuild: this.$options.release });
1322

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
23+
const output = await this.$platformEnvironmentRequirements.checkEnvironmentRequirements({
24+
platform: this.platform,
25+
projectDir: this.$projectData.projectDir,
26+
options: this.$options,
27+
notConfiguredEnvOptions: {
28+
hideSyncToPreviewAppOption: true,
29+
hideCloudBuildOption: true
30+
}
31+
});
32+
33+
const canStartKarmaServer = await this.$testExecutionService.canStartKarmaServer(this.$projectData);
34+
if (!canStartKarmaServer) {
35+
this.$errors.fail({
36+
formatStr: "Error: In order to run unit tests, your project must already be configured by running $ tns test init.",
37+
suppressCommandHelp: true,
38+
errorCode: ErrorCodes.TESTS_INIT_REQUIRED
39+
});
2340
}
24-
});
2541

26-
return output.canExecute;
42+
return output.canExecute && canStartKarmaServer;
43+
}
44+
}
45+
46+
class TestAndroidCommand extends TestCommandBase implements ICommand {
47+
protected platform = "android";
48+
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();
56+
}
57+
58+
}
59+
60+
class TestIosCommand extends TestCommandBase implements ICommand {
61+
protected platform = "iOS";
62+
63+
constructor(protected $projectData: IProjectData,
64+
protected $testExecutionService: ITestExecutionService,
65+
protected $analyticsService: IAnalyticsService,
66+
protected $options: IOptions,
67+
protected $platformEnvironmentRequirements: IPlatformEnvironmentRequirements,
68+
protected $errors: IErrors) {
69+
super();
70+
}
71+
2772
}
2873

29-
$injector.registerCommand("test|android", RunKarmaTestCommandFactory('android'));
30-
$injector.registerCommand("test|ios", RunKarmaTestCommandFactory('iOS'));
74+
$injector.registerCommand("test|android", TestAndroidCommand);
75+
$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
@@ -472,6 +472,7 @@ interface IValidatePlatformOutput {
472472

473473
interface ITestExecutionService {
474474
startKarmaServer(platform: string, projectData: IProjectData, projectFilesConfig: IProjectFilesConfig): Promise<void>;
475+
canStartKarmaServer(projectData: IProjectData): Promise<boolean>;
475476
}
476477

477478
/**

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

+12
Original file line numberDiff line numberDiff line change
@@ -946,5 +946,17 @@ export class InjectorStub extends Yok implements IInjector {
946946
this.register('projectData', ProjectDataStub);
947947
this.register('packageInstallationManager', PackageInstallationManagerStub);
948948
this.register('packageInstallationManager', PackageInstallationManagerStub);
949+
this.register("httpClient", {
950+
httpRequest: async (options: any, proxySettings?: IProxySettings): Promise<Server.IResponse> => undefined
951+
});
952+
this.register("pluginsService", {
953+
add: async (): Promise<void> => undefined,
954+
remove: async (): Promise<void> => undefined,
955+
ensureAllDependenciesAreInstalled: () => { return Promise.resolve(); },
956+
});
957+
this.register("devicesService", {
958+
getDevice: (): Mobile.IDevice => undefined,
959+
getDeviceByIdentifier: (): Mobile.IDevice => undefined
960+
});
949961
}
950962
}

0 commit comments

Comments
 (0)