Skip to content

Commit 618fe3c

Browse files
Added check if project name starts with number when creating new project.
Created project name service with method to ensure the project name does not start with number or if it starts with number the client is informed about the consequences when building for Android. When invalid project name is entered the client gets warning about the problem and a prompt with choice to create the project with invalid name or to enter valid name. Added integration tests for the prompts logic. Added unit tests for the project name service.
1 parent 219a1ad commit 618fe3c

6 files changed

+335
-5
lines changed

lib/bootstrap.ts

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ $injector.require("androidProjectService", "./services/android-project-service")
1212
$injector.require("iOSProjectService", "./services/ios-project-service");
1313

1414
$injector.require("projectTemplatesService", "./services/project-templates-service");
15+
$injector.require("projectNameService", "./services/project-name-service");
1516
$injector.require("tnsModulesService", "./services/tns-modules-service");
1617

1718
$injector.require("platformsData", "./platforms-data");

lib/declarations.ts

+16-3
Original file line numberDiff line numberDiff line change
@@ -158,23 +158,23 @@ interface IAndroidToolsInfo {
158158
* @param {any} options Defines if the warning messages should treated as error and if the targetSdk value should be validated as well.
159159
* @return {boolean} True if there are detected issues, false otherwise.
160160
*/
161-
validateInfo(options?: {showWarningsAsErrors: boolean, validateTargetSdk: boolean}): IFuture<boolean>;
161+
validateInfo(options?: { showWarningsAsErrors: boolean, validateTargetSdk: boolean }): IFuture<boolean>;
162162

163163
/**
164164
* Validates the information about required JAVA version.
165165
* @param {string} installedJavaVersion The JAVA version that will be checked.
166166
* @param {any} options Defines if the warning messages should treated as error.
167167
* @return {boolean} True if there are detected issues, false otherwise.
168168
*/
169-
validateJavacVersion(installedJavaVersion: string, options?: {showWarningsAsErrors: boolean}): IFuture<boolean>;
169+
validateJavacVersion(installedJavaVersion: string, options?: { showWarningsAsErrors: boolean }): IFuture<boolean>;
170170

171171
/**
172172
* Returns the path to `android` executable. It should be `$ANDROID_HOME/tools/android`.
173173
* In case ANDROID_HOME is not defined, check if `android` is part of $PATH.
174174
* @param {any} options Defines if the warning messages should treated as error.
175175
* @return {string} Path to the `android` executable.
176176
*/
177-
getPathToAndroidExecutable(options?: {showWarningsAsErrors: boolean}): IFuture<string>;
177+
getPathToAndroidExecutable(options?: { showWarningsAsErrors: boolean }): IFuture<string>;
178178

179179
/**
180180
* Gets the path to `adb` executable from ANDROID_HOME. It should be `$ANDROID_HOME/platform-tools/adb` in case it exists.
@@ -255,3 +255,16 @@ interface IXmlValidator {
255255
*/
256256
getXmlFileErrors(sourceFile: string): IFuture<string>;
257257
}
258+
259+
/**
260+
* Describes methods for project name.
261+
*/
262+
interface IProjectNameService {
263+
/**
264+
* Ensures the passed project name is valid. If the project name is not valida prompts for actions.
265+
* @param {string} project name to be checked.
266+
* @param {IOptions} current command options.
267+
* @return {IFuture<strng>} returns the selected name of the project.
268+
*/
269+
ensureValidName(projectName: string, validateOptions?: {force: boolean}): IFuture<string>;
270+
}

lib/services/project-name-service.ts

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"use strict";
2+
3+
import { isInteractive } from "../common/helpers";
4+
5+
export class ProjectNameService implements IProjectNameService {
6+
constructor(private $projectNameValidator: IProjectNameValidator,
7+
private $errors: IErrors,
8+
private $logger: ILogger,
9+
private $prompter: IPrompter) { }
10+
11+
public ensureValidName(projectName: string, validateOptions?: { force: boolean }): IFuture<string> {
12+
return (() => {
13+
if (validateOptions && validateOptions.force) {
14+
return projectName;
15+
}
16+
17+
if (!this.$projectNameValidator.validate(projectName)) {
18+
return this.promptForNewName(projectName, validateOptions).wait();
19+
}
20+
21+
if (!this.checkIfNameStartsWithLetter(projectName)) {
22+
if (!isInteractive()) {
23+
this.$errors.fail("The project name does not start with letter and will fail to build for Android. If You want to create project with this name add --force to the create command.");
24+
return;
25+
}
26+
27+
return this.promptForNewName(projectName, validateOptions).wait();
28+
}
29+
30+
return projectName;
31+
}).future<string>()();
32+
}
33+
34+
private checkIfNameStartsWithLetter(projectName: string): boolean {
35+
let startsWithLetterExpression = /^[a-zA-Z]/;
36+
return startsWithLetterExpression.test(projectName);
37+
}
38+
39+
private promptForNewName(projectName: string, validateOptions?: { force: boolean }): IFuture<string> {
40+
return (() => {
41+
if (this.promptForForceNameConfirm().wait()) {
42+
return projectName;
43+
}
44+
45+
let newProjectName = this.$prompter.getString("Enter the new project name:").wait();
46+
return this.ensureValidName(newProjectName, validateOptions).wait();
47+
}).future<string>()();
48+
}
49+
50+
private promptForForceNameConfirm(): IFuture<boolean> {
51+
return (() => {
52+
this.$logger.warn("The project name does not start with letter and will fail to build for Android.");
53+
54+
return this.$prompter.confirm("Do You want to create the project with this name?").wait();
55+
}).future<boolean>()();
56+
}
57+
}
58+
59+
$injector.register("projectNameService", ProjectNameService);

lib/services/project-service.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class ProjectService implements IProjectService {
1414
private $logger: ILogger,
1515
private $projectDataService: IProjectDataService,
1616
private $projectHelper: IProjectHelper,
17-
private $projectNameValidator: IProjectNameValidator,
17+
private $projectNameService: IProjectNameService,
1818
private $projectTemplatesService: IProjectTemplatesService,
1919
private $options: IOptions) { }
2020

@@ -23,7 +23,8 @@ export class ProjectService implements IProjectService {
2323
if (!projectName) {
2424
this.$errors.fail("You must specify <App name> when creating a new project.");
2525
}
26-
this.$projectNameValidator.validate(projectName);
26+
27+
projectName = this.$projectNameService.ensureValidName(projectName, {force: this.$options.force}).wait();
2728

2829
let projectId = this.$options.appid || this.$projectHelper.generateDefaultAppId(projectName, constants.DEFAULT_APP_IDENTIFIER_PREFIX);
2930

test/project-name-service.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/// <reference path=".d.ts" />
2+
"use strict";
3+
4+
import * as yok from "../lib/common/yok";
5+
import { ProjectNameService } from "../lib/services/project-name-service";
6+
import {assert} from "chai";
7+
import {ErrorsStub, LoggerStub} from "./stubs";
8+
9+
let mockProjectNameValidator = {
10+
validate: () => { return true; }
11+
};
12+
13+
let dummyString: string = "dummyString";
14+
15+
function createTestInjector(): IInjector {
16+
let testInjector: IInjector;
17+
18+
testInjector = new yok.Yok();
19+
testInjector.register("projectNameService", ProjectNameService);
20+
testInjector.register("projectNameValidator", mockProjectNameValidator);
21+
testInjector.register("errors", ErrorsStub);
22+
testInjector.register("logger", LoggerStub);
23+
testInjector.register("prompter", {
24+
confirm: (message: string): IFuture<boolean> => {
25+
return (() => {
26+
return true;
27+
}).future<boolean>()();
28+
},
29+
getString: (message: string): IFuture<string> => {
30+
return (() => {
31+
return dummyString;
32+
}).future<string>()();
33+
}
34+
});
35+
36+
return testInjector;
37+
}
38+
39+
describe("Project Name Service Tests", () => {
40+
let testInjector: IInjector;
41+
let projectNameService: IProjectNameService;
42+
let validProjectName = "valid";
43+
let invalidProjectName = "1invalid";
44+
45+
before(() => {
46+
testInjector = createTestInjector();
47+
projectNameService = testInjector.resolve("projectNameService");
48+
});
49+
50+
it("returns correct name when valid name is entered", () => {
51+
let actualProjectName = projectNameService.ensureValidName(validProjectName).wait();
52+
53+
assert.deepEqual(actualProjectName, validProjectName);
54+
});
55+
56+
it("returns correct name when invalid name is entered several times and then valid name is entered", () => {
57+
let prompter = testInjector.resolve("prompter");
58+
prompter.confirm = (message: string): IFuture<boolean> => {
59+
return (() => {
60+
return false;
61+
}).future<boolean>()();
62+
};
63+
64+
let incorrectInputsLimit = 20;
65+
let incorrectInputsCount = 0;
66+
67+
prompter.getString = (message: string): IFuture<string> => {
68+
return (() => {
69+
if (incorrectInputsCount < incorrectInputsLimit) {
70+
incorrectInputsCount++;
71+
72+
return invalidProjectName;
73+
}
74+
else {
75+
return validProjectName;
76+
}
77+
}).future<string>()();
78+
};
79+
80+
let actualProjectName = projectNameService.ensureValidName(invalidProjectName).wait();
81+
82+
assert.deepEqual(actualProjectName, validProjectName);
83+
});
84+
85+
it("returns correct name when invalid name is entered and --force flag is present", () => {
86+
let actualProjectName = projectNameService.ensureValidName(validProjectName, { force: true }).wait();
87+
88+
assert.deepEqual(actualProjectName, validProjectName);
89+
});
90+
});

0 commit comments

Comments
 (0)