From a2cbfaf0923f7df174c0a615b9c89fa27cf5c15d Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Sun, 5 Feb 2017 20:31:39 +0200 Subject: [PATCH 1/2] Track from which template a project is created Add tracking from which template a project is created. This will give us better picture of the usage of CLI and the types of projects that the users are creating. --- lib/services/project-templates-service.ts | 39 ++++++++++++----------- test/project-service.ts | 20 ++++++------ test/project-templates-service.ts | 32 ++++++++++--------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/lib/services/project-templates-service.ts b/lib/services/project-templates-service.ts index 99c0acaee0..a8fee7a6df 100644 --- a/lib/services/project-templates-service.ts +++ b/lib/services/project-templates-service.ts @@ -6,31 +6,32 @@ temp.track(); export class ProjectTemplatesService implements IProjectTemplatesService { public constructor(private $errors: IErrors, - private $fs: IFileSystem, - private $logger: ILogger, - private $npm: INodePackageManager, - private $npmInstallationManager: INpmInstallationManager) { } + private $fs: IFileSystem, + private $logger: ILogger, + private $npm: INodePackageManager, + private $npmInstallationManager: INpmInstallationManager, + private $analyticsService: IAnalyticsService) { } public prepareTemplate(originalTemplateName: string, projectDir: string): IFuture { return ((): string => { - let realTemplatePath: string; - if(originalTemplateName) { + let templateName = originalTemplateName || constants.RESERVED_TEMPLATE_NAMES["default"], + version: string = null; + + if (originalTemplateName) { // support @ syntax let data = originalTemplateName.split("@"), - name = data[0], - version = data[1]; - - if(constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()]) { - realTemplatePath = this.prepareNativeScriptTemplate(constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()], version, projectDir).wait(); - } else { - // Use the original template name, specified by user as it may be case-sensitive. - realTemplatePath = this.prepareNativeScriptTemplate(name, version, projectDir).wait(); - } - } else { - realTemplatePath = this.prepareNativeScriptTemplate(constants.RESERVED_TEMPLATE_NAMES["default"], null/*version*/, projectDir).wait(); + name = data[0]; + + version = data[1]; + + templateName = constants.RESERVED_TEMPLATE_NAMES[name.toLowerCase()] || name; } - if(realTemplatePath) { + this.$analyticsService.track("Template used for project creation", templateName).wait(); + + const realTemplatePath = this.prepareNativeScriptTemplate(templateName, version, projectDir).wait(); + + if (realTemplatePath) { //this removes dependencies from templates so they are not copied to app folder this.$fs.deleteDirectory(path.join(realTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); return realTemplatePath; @@ -51,7 +52,7 @@ export class ProjectTemplatesService implements IProjectTemplatesService { */ private prepareNativeScriptTemplate(templateName: string, version?: string, projectDir?: string): IFuture { this.$logger.trace(`Using NativeScript verified template: ${templateName} with version ${version}.`); - return this.$npmInstallationManager.install(templateName, projectDir, {version: version, dependencyType: "save"}); + return this.$npmInstallationManager.install(templateName, projectDir, { version: version, dependencyType: "save" }); } } $injector.register("projectTemplatesService", ProjectTemplatesService); diff --git a/test/project-service.ts b/test/project-service.ts index 0103125260..f637d3d34f 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -112,6 +112,7 @@ class ProjectIntegrationTest { this.testInjector.register("fs", FileSystem); this.testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); this.testInjector.register("staticConfig", StaticConfig); + this.testInjector.register("analyticsService", { track: () => Future.fromResult() }); this.testInjector.register("npmInstallationManager", NpmInstallationManager); this.testInjector.register("npm", NpmLib.NodePackageManager); @@ -134,12 +135,12 @@ class ProjectIntegrationTest { describe("Project Service Tests", () => { describe("project service integration tests", () => { - let defaultTemplatePath:string; - let defaultSpecificVersionTemplatePath:string; - let angularTemplatePath:string; + let defaultTemplatePath: string; + let defaultSpecificVersionTemplatePath: string; + let angularTemplatePath: string; let typescriptTemplatePath: string; - before(function() { + before(function () { let projectIntegrationTest = new ProjectIntegrationTest(); let fs: IFileSystem = projectIntegrationTest.testInjector.resolve("fs"); let npmInstallationManager: INpmInstallationManager = projectIntegrationTest.testInjector.resolve("npmInstallationManager"); @@ -153,7 +154,7 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world", defaultTemplateDir, {dependencyType: "save"}).wait(); + npmInstallationManager.install("tns-template-hello-world", defaultTemplateDir, { dependencyType: "save" }).wait(); defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", "tns-template-hello-world"); fs.deleteDirectory(path.join(defaultTemplatePath, "node_modules")); @@ -166,7 +167,7 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world", defaultSpecificVersionTemplateDir, {version: "1.4.0", dependencyType: "save"}).wait(); + npmInstallationManager.install("tns-template-hello-world", defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }).wait(); defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", "tns-template-hello-world"); fs.deleteDirectory(path.join(defaultSpecificVersionTemplatePath, "node_modules")); @@ -179,7 +180,7 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world-ng", angularTemplateDir, {dependencyType: "save"}).wait(); + npmInstallationManager.install("tns-template-hello-world-ng", angularTemplateDir, { dependencyType: "save" }).wait(); angularTemplatePath = path.join(angularTemplateDir, "node_modules", "tns-template-hello-world-ng"); fs.deleteDirectory(path.join(angularTemplatePath, "node_modules")); @@ -192,7 +193,7 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world-ts", typescriptTemplateDir, {dependencyType: "save"}).wait(); + npmInstallationManager.install("tns-template-hello-world-ts", typescriptTemplateDir, { dependencyType: "save" }).wait(); typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", "tns-template-hello-world-ts"); fs.deleteDirectory(path.join(typescriptTemplatePath, "node_modules")); }); @@ -447,7 +448,7 @@ describe("Project Service Tests", () => { projectIntegrationTest.createProject(projectName).wait(); options.copyFrom = defaultTemplatePath; - projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`,null).wait(); + projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, null).wait(); }); }); @@ -468,6 +469,7 @@ function createTestInjector() { testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); testInjector.register("staticConfig", StaticConfig); + testInjector.register("analyticsService", { track: () => Future.fromResult() }); testInjector.register("npmInstallationManager", NpmInstallationManager); testInjector.register("httpClient", HttpClientLib.HttpClient); diff --git a/test/project-templates-service.ts b/test/project-templates-service.ts index 872a915401..c45176570e 100644 --- a/test/project-templates-service.ts +++ b/test/project-templates-service.ts @@ -1,6 +1,6 @@ -import {Yok} from "../lib/common/yok"; +import { Yok } from "../lib/common/yok"; import * as stubs from "./stubs"; -import {ProjectTemplatesService} from "../lib/services/project-templates-service"; +import { ProjectTemplatesService } from "../lib/services/project-templates-service"; import * as assert from "assert"; import Future = require("fibers/future"); import * as path from "path"; @@ -9,20 +9,20 @@ import temp = require("temp"); let isDeleteDirectoryCalledForNodeModulesDir = false; let nativeScriptValidatedTemplatePath = "nsValidatedTemplatePath"; -function createTestInjector(configuration?: {shouldNpmInstallThrow: boolean, npmInstallationDirContents: string[], npmInstallationDirNodeModulesContents: string[]}): IInjector { +function createTestInjector(configuration?: { shouldNpmInstallThrow: boolean, npmInstallationDirContents: string[], npmInstallationDirNodeModulesContents: string[] }): IInjector { let injector = new Yok(); injector.register("errors", stubs.ErrorsStub); injector.register("logger", stubs.LoggerStub); injector.register("fs", { readDirectory: (dirPath: string): string[] => { - if(dirPath.toLowerCase().indexOf("node_modules") !== -1) { + if (dirPath.toLowerCase().indexOf("node_modules") !== -1) { return configuration.npmInstallationDirNodeModulesContents; } return configuration.npmInstallationDirContents; }, deleteDirectory: (directory: string) => { - if(directory.indexOf("node_modules") !== -1) { + if (directory.indexOf("node_modules") !== -1) { isDeleteDirectoryCalledForNodeModulesDir = true; } } @@ -30,7 +30,7 @@ function createTestInjector(configuration?: {shouldNpmInstallThrow: boolean, npm }); injector.register("npm", { install: (packageName: string, pathToSave: string, config?: any) => { - if(configuration.shouldNpmInstallThrow) { + if (configuration.shouldNpmInstallThrow) { throw new Error("NPM install throws error."); } @@ -40,7 +40,7 @@ function createTestInjector(configuration?: {shouldNpmInstallThrow: boolean, npm injector.register("npmInstallationManager", { install: (packageName: string, options?: INpmInstallOptions) => { - if(configuration.shouldNpmInstallThrow) { + if (configuration.shouldNpmInstallThrow) { throw new Error("NPM install throws error."); } @@ -50,6 +50,8 @@ function createTestInjector(configuration?: {shouldNpmInstallThrow: boolean, npm injector.register("projectTemplatesService", ProjectTemplatesService); + injector.register("analyticsService", { track: () => Future.fromResult() }); + return injector; } @@ -61,9 +63,9 @@ describe("project-templates-service", () => { }); describe("prepareTemplate", () => { - describe("throws error", () =>{ + describe("throws error", () => { it("when npm install fails", () => { - testInjector = createTestInjector({shouldNpmInstallThrow: true, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null}); + testInjector = createTestInjector({ shouldNpmInstallThrow: true, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: null }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); let tempFolder = temp.mkdirSync("preparetemplate"); assert.throws(() => projectTemplatesService.prepareTemplate("invalidName", tempFolder).wait()); @@ -71,8 +73,8 @@ describe("project-templates-service", () => { }); describe("returns correct path to template", () => { - it("when reserved template name is used", () =>{ - testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []}); + it("when reserved template name is used", () => { + testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); let tempFolder = temp.mkdirSync("preparetemplate"); let actualPathToTemplate = projectTemplatesService.prepareTemplate("typescript", tempFolder).wait(); @@ -80,8 +82,8 @@ describe("project-templates-service", () => { assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); }); - it("when reserved template name is used (case-insensitive test)", () =>{ - testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []}); + it("when reserved template name is used (case-insensitive test)", () => { + testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); let tempFolder = temp.mkdirSync("preparetemplate"); let actualPathToTemplate = projectTemplatesService.prepareTemplate("tYpEsCriPT", tempFolder).wait(); @@ -89,8 +91,8 @@ describe("project-templates-service", () => { assert.strictEqual(isDeleteDirectoryCalledForNodeModulesDir, true, "When correct path is returned, template's node_modules directory should be deleted."); }); - it("uses defaultTemplate when undefined is passed as parameter", () =>{ - testInjector = createTestInjector({shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: []}); + it("uses defaultTemplate when undefined is passed as parameter", () => { + testInjector = createTestInjector({ shouldNpmInstallThrow: false, npmInstallationDirContents: [], npmInstallationDirNodeModulesContents: [] }); projectTemplatesService = testInjector.resolve("projectTemplatesService"); let tempFolder = temp.mkdirSync("preparetemplate"); let actualPathToTemplate = projectTemplatesService.prepareTemplate(undefined, tempFolder).wait(); From 7813962c5ea0ad8816ace2d17d0f11b98b2f3493 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Mon, 6 Feb 2017 12:49:07 +0200 Subject: [PATCH 2/2] Track project type when deploy/run/livesync is executed Track the project type (Angular, Pure TypeScript, Pure JavaScript) when any of the commands is executed: * prepare * deploy * run * livesync This will allow us to better understand the type of projects that the users are building. --- lib/definitions/platform.d.ts | 10 ++- lib/definitions/project.d.ts | 2 + lib/project-data.ts | 43 +++++++++++++ lib/services/livesync/livesync-service.ts | 2 + lib/services/platform-service.ts | 31 +++++++-- test/npm-support.ts | 4 ++ test/platform-commands.ts | 5 ++ test/platform-service.ts | 3 + test/project-data.ts | 77 +++++++++++++++++++++++ test/project-service.ts | 16 ++--- test/stubs.ts | 6 ++ 11 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 test/project-data.ts diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index a586db5126..8c476c7bc3 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -142,6 +142,14 @@ interface IPlatformService { * @returns {string} The contents of the file or null when there is no such file. */ readFile(device: Mobile.IDevice, deviceFilePath: string): IFuture; + + /** + * Sends information to analytics for current project type. + * The information is sent once per process for each project. + * In long living process, where the project may change, each of the projects will be tracked after it's being opened. + * @returns {IFuture} + */ + trackProjectType(): IFuture; } interface IPlatformData { @@ -183,4 +191,4 @@ interface INodeModulesDependenciesBuilder { interface IBuildInfo { prepareTime: string; buildTime: string; -} \ No newline at end of file +} diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 35a26e80eb..c444c4df9b 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -10,8 +10,10 @@ interface IProjectData { projectFilePath: string; projectId?: string; dependencies: any; + devDependencies: IStringDictionary; appDirectoryPath: string; appResourcesDirectoryPath: string; + projectType: string; } interface IProjectDataService { diff --git a/lib/project-data.ts b/lib/project-data.ts index a2e113902b..e56c258c64 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -2,9 +2,33 @@ import * as constants from "./constants"; import * as path from "path"; import { EOL } from "os"; +interface IProjectType { + type: string; + requiredDependencies?: string[]; + isDefaultProjectType?: boolean; +} + export class ProjectData implements IProjectData { private static OLD_PROJECT_FILE_NAME = ".tnsproject"; + /** + * NOTE: Order of the elements is important as the TypeScript dependencies are commonly included in Angular project as well. + */ + private static PROJECT_TYPES: IProjectType[] = [ + { + type: "Pure JavaScript", + isDefaultProjectType: true + }, + { + type: "Angular", + requiredDependencies: ["@angular/core", "nativescript-angular"] + }, + { + type: "Pure TypeScript", + requiredDependencies: ["typescript", "nativescript-dev-typescript"] + } + ]; + public projectDir: string; public platformsDir: string; public projectFilePath: string; @@ -13,6 +37,8 @@ export class ProjectData implements IProjectData { public appDirectoryPath: string; public appResourcesDirectoryPath: string; public dependencies: any; + public devDependencies: IStringDictionary; + public projectType: string; constructor(private $fs: IFileSystem, private $errors: IErrors, @@ -48,6 +74,8 @@ export class ProjectData implements IProjectData { if (data) { this.projectId = data.id; this.dependencies = fileContent.dependencies; + this.devDependencies = fileContent.devDependencies; + this.projectType = this.getProjectType(); } else { // This is the case when we have package.json file but nativescipt key is not presented in it this.tryToUpgradeProject(); } @@ -57,6 +85,21 @@ export class ProjectData implements IProjectData { } } + private getProjectType(): string { + let detectedProjectType = _.find(ProjectData.PROJECT_TYPES, (projectType) => projectType.isDefaultProjectType).type; + + const deps: string[] = _.keys(this.dependencies).concat(_.keys(this.devDependencies)); + + _.each(ProjectData.PROJECT_TYPES, projectType => { + if (_.some(projectType.requiredDependencies, requiredDependency => deps.indexOf(requiredDependency) !== -1)) { + detectedProjectType = projectType.type; + return false; + } + }); + + return detectedProjectType; + } + private throwNoProjectFoundError(): void { this.$errors.fail("No project found at or above '%s' and neither was a --path specified.", this.$options.path || path.resolve(".")); } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 368a1ff214..d5131ff199 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -99,6 +99,8 @@ class LiveSyncService implements ILiveSyncService { @helpers.hook('livesync') private liveSyncCore(liveSyncData: ILiveSyncData[], applicationReloadAction: (deviceAppData: Mobile.IDeviceAppData, localToDevicePaths: Mobile.ILocalToDevicePathData[]) => IFuture): IFuture { return (() => { + this.$platformService.trackProjectType().wait(); + let watchForChangeActions: ((event: string, filePath: string, dispatcher: IFutureDispatcher) => void)[] = []; _.each(liveSyncData, (dataItem) => { let service: IPlatformLiveSyncService = this.$injector.resolve("platformLiveSyncService", { _liveSyncData: dataItem }); diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index 0978dbcee8..b7e7073f02 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -12,6 +12,7 @@ let clui = require("clui"); const buildInfoFileName = ".nsbuildinfo"; export class PlatformService implements IPlatformService { + private _trackedProjectFilePath: string = null; constructor(private $devicesService: Mobile.IDevicesService, private $errors: IErrors, @@ -38,7 +39,8 @@ export class PlatformService implements IPlatformService { private $deviceAppDataFactory: Mobile.IDeviceAppDataFactory, private $projectChangesService: IProjectChangesService, private $emulatorPlatformService: IEmulatorPlatformService, - private $childProcess: IChildProcess) { } + private $childProcess: IChildProcess, + private $analyticsService: IAnalyticsService) { } public addPlatforms(platforms: string[]): IFuture { return (() => { @@ -196,6 +198,8 @@ export class PlatformService implements IPlatformService { return (() => { this.validatePlatform(platform); + this.trackProjectType().wait(); + //We need dev-dependencies here, so before-prepare hooks will be executed correctly. try { this.$pluginsService.ensureAllDependenciesAreInstalled().wait(); @@ -348,7 +352,7 @@ export class PlatformService implements IPlatformService { } let platformData = this.$platformsData.getPlatformData(platform); let forDevice = !buildConfig || buildConfig.buildForDevice; - let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; + let outputPath = forDevice ? platformData.deviceBuildOutputPath : platformData.emulatorBuildOutputPath; if (!this.$fs.exists(outputPath)) { return true; } @@ -372,9 +376,24 @@ export class PlatformService implements IPlatformService { }).future()(); } + public trackProjectType(): IFuture { + return (() => { + // Track each project once per process. + // In long living process, where we may work with multiple projects, we would like to track the information for each of them. + if (this.$projectData && (this.$projectData.projectFilePath !== this._trackedProjectFilePath)) { + this._trackedProjectFilePath = this.$projectData.projectFilePath; + + this.$analyticsService.track("Working with project type", this.$projectData.projectType).wait(); + } + }).future()(); + } + public buildPlatform(platform: string, buildConfig?: IBuildConfig): IFuture { return (() => { this.$logger.out("Building project..."); + + this.trackProjectType().wait(); + let platformData = this.$platformsData.getPlatformData(platform); platformData.platformProjectService.buildProject(platformData.projectRoot, buildConfig).wait(); let prepareInfo = this.$projectChangesService.getPrepareInfo(platform); @@ -449,6 +468,8 @@ export class PlatformService implements IPlatformService { public runPlatform(platform: string): IFuture { return (() => { + this.trackProjectType().wait(); + if (this.$options.justlaunch) { this.$options.watch = false; } @@ -484,7 +505,7 @@ export class PlatformService implements IPlatformService { this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait(); let found: Mobile.IDeviceInfo[] = []; if (this.$devicesService.hasDevices) { - found = this.$devicesService.getDevices().filter((device:Mobile.IDeviceInfo) => device.identifier === this.$options.device); + found = this.$devicesService.getDevices().filter((device: Mobile.IDeviceInfo) => device.identifier === this.$options.device); } if (found.length === 0) { this.$errors.fail("Cannot find device with name: %s", this.$options.device); @@ -514,7 +535,7 @@ export class PlatformService implements IPlatformService { let deviceFilePath = this.getDeviceBuildInfoFilePath(device); try { return JSON.parse(this.readFile(device, deviceFilePath).wait()); - } catch(e) { + } catch (e) { return null; }; }).future()(); @@ -527,7 +548,7 @@ export class PlatformService implements IPlatformService { try { let buildInfoTime = this.$fs.readJson(buildInfoFile); return buildInfoTime; - } catch(e) { + } catch (e) { return null; } } diff --git a/test/npm-support.ts b/test/npm-support.ts index b888981e66..82c4bedc67 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -80,6 +80,10 @@ function createTestInjector(): IInjector { testInjector.register("config", StaticConfigLib.Configuration); testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); + testInjector.register("analyticsService", { + track: () => Future.fromResult() + }); + return testInjector; } diff --git a/test/platform-commands.ts b/test/platform-commands.ts index 3b1c1f604f..d4cca7aa78 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -21,6 +21,7 @@ import { XmlValidator } from "../lib/xml-validator"; import * as ChildProcessLib from "../lib/common/child-process"; import {CleanCommand} from "../lib/commands/platform-clean"; import ProjectChangesLib = require("../lib/services/project-changes-service"); +import Future = require("fibers/future"); let isCommandExecuted = true; @@ -142,6 +143,10 @@ function createTestInjector() { testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); + testInjector.register("analyticsService", { + track: () => Future.fromResult() + }); + return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts index 47161acbd0..04ac25bcb0 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -81,6 +81,9 @@ function createTestInjector() { testInjector.register("childProcess", ChildProcessLib.ChildProcess); testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); testInjector.register("emulatorPlatformService", stubs.EmulatorPlatformService); + testInjector.register("analyticsService", { + track: () => Future.fromResult() + }); return testInjector; } diff --git a/test/project-data.ts b/test/project-data.ts new file mode 100644 index 0000000000..b02399d165 --- /dev/null +++ b/test/project-data.ts @@ -0,0 +1,77 @@ +import { ProjectData } from "../lib/project-data"; +import { Yok } from "../lib/common/yok"; +import { assert } from "chai"; +import * as stubs from "./stubs"; +import * as path from "path"; + +describe("projectData", () => { + const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + + testInjector.register("projectHelper", { + projectDir: null, + sanitizeName: (name: string) => name + }); + + testInjector.register("fs", { + exists: () => true, + readJson: (): any => null + }); + + testInjector.register("staticConfig", { + CLIENT_NAME_KEY_IN_PROJECT_FILE: "nativescript", + PROJECT_FILE_NAME: "package.json" + }); + + testInjector.register("errors", stubs.ErrorsStub); + + testInjector.register("logger", stubs.LoggerStub); + + testInjector.register("options", {}); + + testInjector.register("projectData", ProjectData); + + return testInjector; + }; + + describe("projectType", () => { + + const assertProjectType = (dependencies: any, devDependencies: any, expectedProjecType: string) => { + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); + fs.exists = (filePath: string) => filePath && path.basename(filePath) === "package.json"; + + fs.readJson = () => ({ + nativescript: {}, + dependencies: dependencies, + devDependencies: devDependencies + }); + + const projectHelper: IProjectHelper = testInjector.resolve("projectHelper"); + projectHelper.projectDir = "projectDir"; + + const projectData: IProjectData = testInjector.resolve("projectData"); + assert.deepEqual(projectData.projectType, expectedProjecType); + }; + + it("detects project as Angular when @angular/core exists as dependency", () => { + assertProjectType({ "@angular/core": "*" }, null, "Angular"); + }); + + it("detects project as Angular when nativescript-angular exists as dependency", () => { + assertProjectType({ "nativescript-angular": "*" }, null, "Angular"); + }); + + it("detects project as TypeScript when nativescript-dev-typescript exists as dependency", () => { + assertProjectType(null, { "nativescript-dev-typescript": "*" }, "Pure TypeScript"); + }); + + it("detects project as TypeScript when typescript exists as dependency", () => { + assertProjectType(null, { "typescript": "*" }, "Pure TypeScript"); + }); + + it("detects project as JavaScript when no other project type is detected", () => { + assertProjectType(null, null, "Pure JavaScript"); + }); + }); +}); diff --git a/test/project-service.ts b/test/project-service.ts index f637d3d34f..3b49d376a4 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -154,8 +154,8 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world", defaultTemplateDir, { dependencyType: "save" }).wait(); - defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", "tns-template-hello-world"); + npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultTemplateDir, { dependencyType: "save" }).wait(); + defaultTemplatePath = path.join(defaultTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]); fs.deleteDirectory(path.join(defaultTemplatePath, "node_modules")); let defaultSpecificVersionTemplateDir = temp.mkdirSync("defaultTemplateSpeciffic"); @@ -167,8 +167,8 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world", defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }).wait(); - defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", "tns-template-hello-world"); + npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["default"], defaultSpecificVersionTemplateDir, { version: "1.4.0", dependencyType: "save" }).wait(); + defaultSpecificVersionTemplatePath = path.join(defaultSpecificVersionTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["default"]); fs.deleteDirectory(path.join(defaultSpecificVersionTemplatePath, "node_modules")); let angularTemplateDir = temp.mkdirSync("angularTemplate"); @@ -180,8 +180,8 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world-ng", angularTemplateDir, { dependencyType: "save" }).wait(); - angularTemplatePath = path.join(angularTemplateDir, "node_modules", "tns-template-hello-world-ng"); + npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["angular"], angularTemplateDir, { dependencyType: "save" }).wait(); + angularTemplatePath = path.join(angularTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["angular"]); fs.deleteDirectory(path.join(angularTemplatePath, "node_modules")); let typescriptTemplateDir = temp.mkdirSync("typescriptTemplate"); @@ -193,8 +193,8 @@ describe("Project Service Tests", () => { "readme": "dummy", "repository": "dummy" }); - npmInstallationManager.install("tns-template-hello-world-ts", typescriptTemplateDir, { dependencyType: "save" }).wait(); - typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", "tns-template-hello-world-ts"); + npmInstallationManager.install(constants.RESERVED_TEMPLATE_NAMES["typescript"], typescriptTemplateDir, { dependencyType: "save" }).wait(); + typescriptTemplatePath = path.join(typescriptTemplateDir, "node_modules", constants.RESERVED_TEMPLATE_NAMES["typescript"]); fs.deleteDirectory(path.join(typescriptTemplatePath, "node_modules")); }); diff --git a/test/stubs.ts b/test/stubs.ts index 708904ac2e..d44f2a5f68 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -236,6 +236,8 @@ export class ProjectDataStub implements IProjectData { dependencies: any; appDirectoryPath: string; appResourcesDirectoryPath: string; + devDependencies: IStringDictionary; + projectType: string; } export class PlatformsDataStub implements IPlatformsData { @@ -669,6 +671,10 @@ export class PlatformServiceStub implements IPlatformService { public readFile(device: Mobile.IDevice, deviceFilePath: string): IFuture { return Future.fromResult(""); } + + public trackProjectType(): IFuture { + return Future.fromResult(); + } } export class EmulatorPlatformService implements IEmulatorPlatformService {