Skip to content

Commit fe8ffba

Browse files
chore: rewrite create project tests
There are some extremely slow integration tests for project creation. They mock the prompter and try to validate different use-cases when user enters invalid name and CLI prompts for correct name. However, this is already handled in `project-name-service` tests. So, remove the slow integration tests for project creation and replace them with unit tests that ensure the project creation will fail in case projectNameService fails.
1 parent 9b5ac6a commit fe8ffba

File tree

1 file changed

+86
-243
lines changed

1 file changed

+86
-243
lines changed

test/project-service.ts

+86-243
Original file line numberDiff line numberDiff line change
@@ -1,262 +1,105 @@
11
import * as yok from "../lib/common/yok";
2-
import * as stubs from "./stubs";
32
import * as constants from "../lib/constants";
4-
import { ChildProcess } from "../lib/common/child-process";
53
import * as ProjectServiceLib from "../lib/services/project-service";
6-
import { ProjectNameService } from "../lib/services/project-name-service";
7-
import * as ProjectDataServiceLib from "../lib/services/project-data-service";
8-
import * as ProjectHelperLib from "../lib/common/project-helper";
9-
import { StaticConfig } from "../lib/config";
10-
import * as NpmLib from "../lib/node-package-manager";
11-
import * as YarnLib from "../lib/yarn-package-manager";
12-
import * as PackageManagerLib from "../lib/package-manager";
13-
import { PackageInstallationManager } from "../lib/package-installation-manager";
14-
import { FileSystem } from "../lib/common/file-system";
15-
import * as path from "path";
16-
import temp = require("temp");
17-
import helpers = require("../lib/common/helpers");
184
import { assert } from "chai";
19-
import { Options } from "../lib/options";
20-
import { HostInfo } from "../lib/common/host-info";
21-
import { ProjectTemplatesService } from "../lib/services/project-templates-service";
225
import { SettingsService } from "../lib/common/test/unit-tests/stubs";
23-
import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants";
24-
import { PacoteService } from "../lib/services/pacote-service";
25-
26-
const mockProjectNameValidator = {
27-
validate: () => true
28-
};
29-
30-
const dummyString: string = "dummyString";
31-
let hasPromptedForString = false;
32-
const originalIsInteractive = helpers.isInteractive;
33-
34-
temp.track();
35-
36-
async function prepareTestingPath(testInjector: IInjector, packageToInstall: string, packageName: string, options?: INpmInstallOptions): Promise<string> {
37-
options = options || { dependencyType: "save" };
38-
const fs = testInjector.resolve<IFileSystem>("fs");
39-
40-
const packageInstallationManager = testInjector.resolve<IPackageInstallationManager>("packageInstallationManager");
41-
const defaultTemplateDir = temp.mkdirSync("project-service");
42-
fs.writeJson(path.join(defaultTemplateDir, constants.PACKAGE_JSON_FILE_NAME), {
43-
"name": "defaultTemplate",
44-
"version": "1.0.0",
45-
"description": "dummy",
46-
"license": "MIT",
47-
"readme": "dummy",
48-
"repository": "dummy"
49-
});
50-
51-
await packageInstallationManager.install(packageToInstall, defaultTemplateDir, options);
52-
const defaultTemplatePath = path.join(defaultTemplateDir, constants.NODE_MODULES_FOLDER_NAME, packageName);
53-
54-
fs.deleteDirectory(path.join(defaultTemplatePath, constants.NODE_MODULES_FOLDER_NAME));
55-
56-
return defaultTemplatePath;
57-
}
58-
59-
class ProjectIntegrationTest {
60-
public testInjector: IInjector;
61-
62-
constructor() {
63-
this.createTestInjector();
64-
}
65-
66-
public async createProject(projectOptions: IProjectSettings): Promise<ICreateProjectData> {
67-
const projectService: IProjectService = this.testInjector.resolve("projectService");
68-
if (!projectOptions.template) {
69-
projectOptions.template = constants.RESERVED_TEMPLATE_NAMES["default"];
70-
}
71-
return projectService.createProject(projectOptions);
72-
}
73-
74-
public async assertProject(tempFolder: string, projectName: string, appId: string, projectSourceDirectory: string): Promise<void> {
75-
const fs: IFileSystem = this.testInjector.resolve("fs");
76-
const projectDir = path.join(tempFolder, projectName);
77-
const appDirectoryPath = path.join(projectDir, "app");
78-
const tnsProjectFilePath = path.join(projectDir, "package.json");
79-
const tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME);
80-
const packageJsonContent = fs.readJson(tnsProjectFilePath);
81-
82-
assert.isTrue(fs.exists(appDirectoryPath));
83-
assert.isTrue(fs.exists(tnsProjectFilePath));
84-
assert.isTrue(fs.exists(tnsModulesPath));
85-
86-
assert.isFalse(fs.isEmptyDir(appDirectoryPath));
87-
88-
const actualAppId = packageJsonContent["nativescript"].id;
89-
const expectedAppId = appId;
90-
assert.equal(actualAppId, expectedAppId);
91-
92-
const tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME];
93-
assert.isTrue(tnsCoreModulesRecord !== null);
94-
95-
const sourceDir = projectSourceDirectory;
96-
97-
// assert dependencies and devDependencies are copied from template to real project
98-
const sourcePackageJsonContent = fs.readJson(path.join(sourceDir, "package.json"));
99-
const missingDeps = _.difference(_.keys(sourcePackageJsonContent.dependencies), _.keys(packageJsonContent.dependencies));
100-
const missingDevDeps = _.difference(_.keys(sourcePackageJsonContent.devDependencies), _.keys(packageJsonContent.devDependencies));
101-
assert.deepEqual(missingDeps, [], `All dependencies from template must be copied to project's package.json. Missing ones are: ${missingDeps.join(", ")}.`);
102-
assert.deepEqual(missingDevDeps, [], `All devDependencies from template must be copied to project's package.json. Missing ones are: ${missingDevDeps.join(", ")}.`);
103-
104-
// assert App_Resources are prepared correctly
105-
const appResourcesDir = path.join(appDirectoryPath, "App_Resources");
106-
const appResourcesContents = fs.readDirectory(appResourcesDir);
107-
assert.deepEqual(appResourcesContents, ["Android", "iOS"], "Project's app/App_Resources must contain Android and iOS directories.");
108-
}
109-
110-
public dispose(): void {
111-
this.testInjector = undefined;
112-
}
113-
114-
private createTestInjector(): void {
115-
this.testInjector = new yok.Yok();
116-
this.testInjector.register("childProcess", ChildProcess);
117-
this.testInjector.register("errors", stubs.ErrorsStub);
118-
this.testInjector.register('logger', stubs.LoggerStub);
119-
this.testInjector.register("projectService", ProjectServiceLib.ProjectService);
120-
this.testInjector.register("projectNameService", ProjectNameService);
121-
this.testInjector.register("projectHelper", ProjectHelperLib.ProjectHelper);
122-
this.testInjector.register("projectTemplatesService", ProjectTemplatesService);
123-
this.testInjector.register("projectNameValidator", mockProjectNameValidator);
124-
this.testInjector.register("projectData", stubs.ProjectDataStub);
125-
126-
this.testInjector.register("fs", FileSystem);
127-
this.testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService);
128-
this.testInjector.register("staticConfig", StaticConfig);
129-
this.testInjector.register("analyticsService", {
130-
track: async (): Promise<any> => undefined,
131-
trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve()
132-
});
133-
134-
this.testInjector.register("userSettingsService", {
135-
getSettingValue: async (settingName: string): Promise<void> => undefined
136-
});
137-
this.testInjector.register("npm", NpmLib.NodePackageManager);
138-
this.testInjector.register("yarn", YarnLib.YarnPackageManager);
139-
this.testInjector.register("packageManager", PackageManagerLib.PackageManager);
140-
this.testInjector.register("httpClient", {});
141-
142-
this.testInjector.register("options", Options);
143-
this.testInjector.register("hostInfo", HostInfo);
144-
this.testInjector.register("prompter", {
145-
confirm: async (message: string): Promise<boolean> => true,
146-
getString: async (message: string): Promise<string> => {
147-
hasPromptedForString = true;
148-
return dummyString;
149-
}
150-
});
151-
this.testInjector.register("packageInstallationManager", PackageInstallationManager);
152-
this.testInjector.register("settingsService", SettingsService);
153-
this.testInjector.register("devicePlatformsConstants", DevicePlatformsConstants);
154-
this.testInjector.register("androidResourcesMigrationService", {
155-
hasMigrated: (appResourcesDir: string): boolean => true
156-
});
157-
this.testInjector.register("hooksService", {
158-
executeAfterHooks: async (commandName: string, hookArguments?: IDictionary<any>): Promise<void> => undefined
159-
});
160-
this.testInjector.register("pacoteService", PacoteService);
161-
this.testInjector.register("proxyService", {
162-
getCache: async (): Promise<IProxySettings> => null
163-
});
164-
}
165-
}
166-
167-
describe("Project Service Tests", () => {
168-
describe("project service integration tests", () => {
169-
let defaultTemplatePath: string;
170-
171-
before(async () => {
172-
const projectIntegrationTest = new ProjectIntegrationTest();
173-
174-
defaultTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"]);
175-
});
6+
import { LoggerStub, ErrorsStub } from "./stubs";
7+
import * as path from "path";
1768

177-
describe("project name validation tests", () => {
178-
const validProjectName = "valid";
179-
const invalidProjectName = "1invalid";
180-
let projectIntegrationTest: ProjectIntegrationTest;
181-
let tempFolder: string;
182-
let prompter: IPrompter;
9+
describe("projectService", () => {
10+
describe("createProject", () => {
11+
const invalidProjectName = "1invalid";
12+
const dirToCreateProject: string = path.resolve("projectDir");
18313

184-
beforeEach(() => {
185-
hasPromptedForString = false;
186-
helpers.isInteractive = () => true;
187-
projectIntegrationTest = new ProjectIntegrationTest();
188-
tempFolder = temp.mkdirSync("project");
189-
prompter = projectIntegrationTest.testInjector.resolve("prompter");
14+
/* tslint:disable:no-empty */
15+
const getTestInjector = (opts: { projectName: string }): IInjector => {
16+
const testInjector = new yok.Yok();
17+
testInjector.register("packageManager", {
18+
install: async () => { }
19019
});
191-
192-
afterEach(() => {
193-
helpers.isInteractive = originalIsInteractive;
20+
testInjector.register("errors", ErrorsStub);
21+
testInjector.register("fs", {
22+
exists: () => true,
23+
isEmptyDir: () => true,
24+
createDirectory: () => { },
25+
writeJson: () => { },
26+
deleteDirectory: () => { },
27+
ensureDirectoryExists: () => { },
28+
readJson: () => ({})
19429
});
195-
196-
it("creates project when is interactive and incorrect name is specified and the --force option is set", async () => {
197-
const projectName = invalidProjectName;
198-
await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true });
199-
await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath);
30+
testInjector.register("logger", LoggerStub);
31+
testInjector.register("projectDataService", {
32+
getProjectData: (projectDir?: string): IProjectData => (<any>{
33+
getAppResourcesDirectoryPath: () => "appResourcesDirectoryPath"
34+
}),
35+
setNSValue: () => { }
20036
});
201-
202-
it("creates project when is interactive and incorrect name is specified and the user confirms to use the incorrect name", async () => {
203-
const projectName = invalidProjectName;
204-
prompter.confirm = (message: string): Promise<boolean> => Promise.resolve(true);
205-
206-
await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder });
207-
await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath);
37+
testInjector.register("projectData", {});
38+
testInjector.register("projectNameService", {
39+
ensureValidName: async () => opts.projectName
20840
});
209-
210-
it("prompts for new name when is interactive and incorrect name is specified and the user does not confirm to use the incorrect name", async () => {
211-
const projectName = invalidProjectName;
212-
213-
prompter.confirm = (message: string): Promise<boolean> => Promise.resolve(false);
214-
215-
await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder });
216-
assert.isTrue(hasPromptedForString);
41+
testInjector.register("projectTemplatesService", {
42+
prepareTemplate: async () => ({
43+
templateName: constants.RESERVED_TEMPLATE_NAMES["default"],
44+
templatePath: "some path",
45+
templateVersion: "v2",
46+
templatePackageJsonContent: {
47+
dependencies: {
48+
["tns-core-modules"]: "1.0.0"
49+
}
50+
},
51+
version: "1.0.0"
52+
})
21753
});
218-
219-
it("creates project when is interactive and incorrect name s specified and the user does not confirm to use the incorrect name and enters incorrect name again several times and then enters correct name", async () => {
220-
const projectName = invalidProjectName;
221-
222-
prompter.confirm = (message: string): Promise<boolean> => Promise.resolve(false);
223-
224-
const incorrectInputsLimit = 5;
225-
let incorrectInputsCount = 0;
226-
227-
prompter.getString = async (message: string): Promise<string> => {
228-
if (incorrectInputsCount < incorrectInputsLimit) {
229-
incorrectInputsCount++;
230-
} else {
231-
hasPromptedForString = true;
232-
233-
return validProjectName;
234-
}
235-
236-
return projectName;
237-
};
238-
239-
await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder });
240-
assert.isTrue(hasPromptedForString);
54+
testInjector.register("staticConfig", {
55+
PROJECT_FILE_NAME: "package.json"
24156
});
242-
243-
it("does not create project when is not interactive and incorrect name is specified", async () => {
244-
const projectName = invalidProjectName;
245-
helpers.isInteractive = () => false;
246-
247-
await assert.isRejected(projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: false }));
57+
testInjector.register("projectHelper", {
58+
generateDefaultAppId: () => `org.nativescript.${opts.projectName}`
59+
});
60+
testInjector.register("packageInstallationManager", {});
61+
testInjector.register("settingsService", SettingsService);
62+
testInjector.register("hooksService", {
63+
executeAfterHooks: async (commandName: string, hookArguments?: IDictionary<any>): Promise<void> => undefined
64+
});
65+
testInjector.register("pacoteService", {
66+
manifest: () => Promise.resolve(),
67+
downloadAndExtract: () => Promise.resolve(),
68+
extractPackage: () => Promise.resolve()
24869
});
24970

250-
it("creates project when is not interactive and incorrect name is specified and the --force option is set", async () => {
251-
const projectName = invalidProjectName;
252-
helpers.isInteractive = () => false;
253-
254-
await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true });
71+
return testInjector;
72+
};
73+
/* tslint:enable:no-empty */
74+
75+
it("creates project with invalid name when projectNameService does not fail", async () => {
76+
const projectName = invalidProjectName;
77+
const testInjector = getTestInjector({ projectName });
78+
const projectService = testInjector.resolve<IProjectService>(ProjectServiceLib.ProjectService);
79+
const projectCreationData = await projectService.createProject({ projectName: projectName, pathToProject: dirToCreateProject, force: true, template: constants.RESERVED_TEMPLATE_NAMES["default"] });
80+
assert.deepEqual(projectCreationData, { projectName, projectDir: path.join(dirToCreateProject, projectName) });
81+
});
25582

256-
await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath);
257-
});
83+
it("fails when invalid name is passed when projectNameService fails", async () => {
84+
const projectName = invalidProjectName;
85+
const testInjector = getTestInjector({ projectName });
86+
const projectNameService = testInjector.resolve<IProjectNameService>("projectNameService");
87+
const err = new Error("Invalid name");
88+
projectNameService.ensureValidName = (name: string) => {
89+
throw err;
90+
};
91+
const projectService = testInjector.resolve<IProjectService>(ProjectServiceLib.ProjectService);
92+
await assert.isRejected(projectService.createProject({ projectName: projectName, pathToProject: dirToCreateProject, template: constants.RESERVED_TEMPLATE_NAMES["default"] }), err.message);
25893
});
25994

95+
it("fails when project directory is not empty", async () => {
96+
const projectName = invalidProjectName;
97+
const testInjector = getTestInjector({ projectName });
98+
const fs = testInjector.resolve<IFileSystem>("fs");
99+
fs.isEmptyDir = (name: string) => false;
100+
const projectService = testInjector.resolve<IProjectService>(ProjectServiceLib.ProjectService);
101+
await assert.isRejected(projectService.createProject({ projectName: projectName, pathToProject: dirToCreateProject, template: constants.RESERVED_TEMPLATE_NAMES["default"] }), `Path already exists and is not empty ${path.join(dirToCreateProject, projectName)}`);
102+
});
260103
});
261104

262105
describe("isValidNativeScriptProject", () => {
@@ -291,7 +134,7 @@ describe("Project Service Tests", () => {
291134
const testInjector = getTestInjector({
292135
projectDir: "projectDir",
293136
projectId: "projectId",
294-
projectIdentifiers: { android: "projectId", ios: "projectId"},
137+
projectIdentifiers: { android: "projectId", ios: "projectId" },
295138
});
296139

297140
const projectService: IProjectService = testInjector.resolve(ProjectServiceLib.ProjectService);
@@ -304,7 +147,7 @@ describe("Project Service Tests", () => {
304147
const projectData: any = {
305148
projectDir: "projectDir",
306149
projectId: "projectId",
307-
projectIdentifiers: { android: "projectId", ios: "projectId"}
150+
projectIdentifiers: { android: "projectId", ios: "projectId" }
308151
};
309152

310153
let returnedProjectData: any = null;

0 commit comments

Comments
 (0)