diff --git a/resources/test/plugin-with-scoped-dependency.zip b/resources/test/plugin-with-scoped-dependency.zip deleted file mode 100644 index c96dc0606d..0000000000 Binary files a/resources/test/plugin-with-scoped-dependency.zip and /dev/null differ diff --git a/test/npm-support.ts b/test/npm-support.ts deleted file mode 100644 index 7092a306f4..0000000000 --- a/test/npm-support.ts +++ /dev/null @@ -1,350 +0,0 @@ -import yok = require('../lib/common/yok'); -import stubs = require('./stubs'); -import ErrorsLib = require("../lib/common/errors"); -import NpmLib = require("../lib/node-package-manager"); -import PackageManagerLib = require("../lib/package-manager"); -import YarnLib = require("../lib/yarn-package-manager"); -import FsLib = require("../lib/common/file-system"); -import OptionsLib = require("../lib/options"); -import StaticConfigLib = require("../lib/config"); -import HostInfoLib = require("../lib/common/host-info"); -import PlatformsDataLib = require("../lib/platforms-data"); -import PlatformServiceLib = require('../lib/services/platform-service'); -import ProjectDataLib = require("../lib/project-data"); -import ProjectHelperLib = require("../lib/common/project-helper"); -import ProjectDataServiceLib = require("../lib/services/project-data-service"); -import CommandsServiceLib = require("../lib/common/services/commands-service"); -import NodeModulesLib = require("../lib/tools/node-modules/node-modules-builder"); -import PluginsServiceLib = require("../lib/services/plugins-service"); -import ChildProcessLib = require("../lib/common/child-process"); -import ProjectFilesManagerLib = require("../lib/common/services/project-files-manager"); -import { PreparePlatformNativeService } from "../lib/services/prepare-platform-native-service"; -import { PreparePlatformJSService } from "../lib/services/prepare-platform-js-service"; -import { LocalToDevicePathDataFactory } from "../lib/common/mobile/local-to-device-path-data-factory"; -import { MobileHelper } from "../lib/common/mobile/mobile-helper"; -import { ProjectFilesProvider } from "../lib/providers/project-files-provider"; -import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -import { XmlValidator } from "../lib/xml-validator"; -import ProjectChangesLib = require("../lib/services/project-changes-service"); -import { Messages } from "../lib/common/messages/messages"; -import { NodeModulesDependenciesBuilder } from "../lib/tools/node-modules/node-modules-dependencies-builder"; -import { SettingsService } from "../lib/common/test/unit-tests/stubs"; - -import path = require("path"); -import temp = require("temp"); -temp.track(); - -const assert = require("chai").assert; -const nodeModulesFolderName = "node_modules"; -const packageJsonName = "package.json"; - -function createTestInjector(): IInjector { - const testInjector = new yok.Yok(); - testInjector.register("fs", FsLib.FileSystem); - testInjector.register("adb", {}); - testInjector.register("options", OptionsLib.Options); - testInjector.register("errors", ErrorsLib.Errors); - testInjector.register("staticConfig", StaticConfigLib.StaticConfig); - testInjector.register("hostInfo", HostInfoLib.HostInfo); - testInjector.register("platformsData", PlatformsDataLib.PlatformsData); - testInjector.register("platformService", PlatformServiceLib.PlatformService); - testInjector.register("logger", stubs.LoggerStub); - testInjector.register("packageInstallationManager", { - install: () => Promise.resolve() - }); - testInjector.register("prompter", {}); - testInjector.register("sysInfo", {}); - testInjector.register("androidProjectService", {}); - testInjector.register("iOSProjectService", {}); - testInjector.register("devicesService", {}); - testInjector.register("resources", {}); - testInjector.register("projectData", ProjectDataLib.ProjectData); - testInjector.register("projectHelper", ProjectHelperLib.ProjectHelper); - testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); - testInjector.register("commandsService", CommandsServiceLib.CommandsService); - testInjector.register("hooksService", stubs.HooksServiceStub); - testInjector.register("nodeModulesBuilder", NodeModulesLib.NodeModulesBuilder); - testInjector.register("pluginsService", PluginsServiceLib.PluginsService); - testInjector.register("userSettingsService", { - getSettingValue: async (settingName: string): Promise => undefined - }); - testInjector.register("npm", NpmLib.NodePackageManager); - testInjector.register("packageManager", PackageManagerLib.PackageManager); - testInjector.register("yarn", YarnLib.YarnPackageManager); - testInjector.register("childProcess", ChildProcessLib.ChildProcess); - testInjector.register("projectFilesManager", ProjectFilesManagerLib.ProjectFilesManager); - testInjector.register("preparePlatformNativeService", PreparePlatformNativeService); - testInjector.register("preparePlatformJSService", PreparePlatformJSService); - testInjector.register("pluginVariablesService", {}); - testInjector.register("localToDevicePathDataFactory", LocalToDevicePathDataFactory); - testInjector.register("mobileHelper", MobileHelper); - testInjector.register("projectFilesProvider", ProjectFilesProvider); - testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - testInjector.register("xmlValidator", XmlValidator); - testInjector.register("config", StaticConfigLib.Configuration); - testInjector.register("projectChangesService", ProjectChangesLib.ProjectChangesService); - testInjector.register("analyticsService", { - track: async (): Promise => undefined - }); - testInjector.register("messages", Messages); - testInjector.register("nodeModulesDependenciesBuilder", NodeModulesDependenciesBuilder); - testInjector.register("settingsService", SettingsService); - testInjector.register("devicePathProvider", {}); - testInjector.register("terminalSpinnerService", { - createSpinner: (msg: string) => ({ - start: (): void => undefined, - stop: (): void => undefined, - message: (): void => undefined - }) - }); - testInjector.register("httpClient", {}); - testInjector.register("androidResourcesMigrationService", stubs.AndroidResourcesMigrationServiceStub); - testInjector.register("filesHashService", { - getChanges: () => Promise.resolve({}), - generateHashes: () => Promise.resolve() - }); - testInjector.register("pacoteService", { - extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined - }); - testInjector.register("usbLiveSyncService", () => ({})); - testInjector.register("doctorService", { - checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined - }); - - return testInjector; -} - -function createProject(testInjector: IInjector, dependencies?: any): string { - const tempFolder = temp.mkdirSync("npmSupportTests"); - const options = testInjector.resolve("options"); - options.path = tempFolder; - - dependencies = dependencies || { - "lodash": "3.9.3" - }; - - const packageJsonData: any = { - "name": "testModuleName", - "version": "0.1.0", - "nativescript": { - "id": "org.nativescript.Test", - "tns-android": { - "version": "1.0.0" - } - }, - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }; - packageJsonData["dependencies"] = dependencies; - packageJsonData["devDependencies"] = {}; - - testInjector.resolve("fs").writeJson(path.join(tempFolder, "package.json"), packageJsonData); - return tempFolder; -} - -async function setupProject(dependencies?: any): Promise { - const testInjector = createTestInjector(); - const projectFolder = createProject(testInjector, dependencies); - - const fs = testInjector.resolve("fs"); - - // Creates app folder - const appFolderPath = path.join(projectFolder, "app"); - fs.createDirectory(appFolderPath); - const appResourcesFolderPath = path.join(appFolderPath, "App_Resources"); - fs.createDirectory(appResourcesFolderPath); - fs.createDirectory(path.join(appResourcesFolderPath, "Android")); - fs.createDirectory(path.join(appResourcesFolderPath, "Android", "mockdir")); - fs.createDirectory(path.join(appFolderPath, "tns_modules")); - - // Creates platforms/android folder - const androidFolderPath = path.join(projectFolder, "platforms", "android"); - fs.ensureDirectoryExists(androidFolderPath); - - // Mock platform data - const appDestinationFolderPath = path.join(androidFolderPath, "assets"); - const platformsData = testInjector.resolve("platformsData"); - - platformsData.getPlatformData = (platform: string) => { - return { - appDestinationDirectoryPath: appDestinationFolderPath, - appResourcesDestinationDirectoryPath: path.join(appDestinationFolderPath, "app", "App_Resources"), - frameworkPackageName: "tns-android", - normalizedPlatformName: "Android", - projectRoot: projectFolder, - configurationFileName: "AndroidManifest.xml", - platformProjectService: { - prepareProject: (): any => null, - prepareAppResources: (): any => null, - beforePrepareAllPlugins: () => Promise.resolve(), - handleNativeDependenciesChange: () => Promise.resolve(), - getAppResourcesDestinationDirectoryPath: () => path.join(androidFolderPath, "src", "main", "res"), - processConfigurationFilesFromAppResources: () => Promise.resolve(), - ensureConfigurationFileInAppResources: (): any => null, - interpolateConfigurationFile: (): void => undefined, - isPlatformPrepared: (projectRoot: string) => false, - validatePlugins: (projectData: IProjectData) => Promise.resolve(), - checkForChanges: () => { /* */ }, - cleanProject: () => Promise.resolve() - } - }; - }; - - return { - testInjector: testInjector, - projectFolder: projectFolder, - appDestinationFolderPath: appDestinationFolderPath, - }; -} - -async function addDependencies(testInjector: IInjector, projectFolder: string, dependencies: any, devDependencies?: any): Promise { - const fs = testInjector.resolve("fs"); - const packageJsonPath = path.join(projectFolder, "package.json"); - const packageJsonData = fs.readJson(packageJsonPath); - - const currentDependencies = packageJsonData.dependencies; - _.extend(currentDependencies, dependencies); - - if (devDependencies) { - const currentDevDependencies = packageJsonData.devDependencies; - _.extend(currentDevDependencies, devDependencies); - } - fs.writeJson(packageJsonPath, packageJsonData); -} - -async function preparePlatform(testInjector: IInjector): Promise { - const platformService: IPlatformService = testInjector.resolve("platformService"); - const projectData: IProjectData = testInjector.resolve("projectData"); - projectData.initializeProjectData(); - const options: IOptions = testInjector.resolve("options"); - - await platformService.preparePlatform({ - platform: "android", - appFilesUpdaterOptions: { bundle: !!options.bundle, release: options.release, useHotModuleReload: false }, - platformTemplate: "", - projectData, - config: options, - env: {} - }); -} - -describe("Npm support tests", () => { - let testInjector: IInjector, projectFolder: string, appDestinationFolderPath: string; - beforeEach(async () => { - const projectSetup = await setupProject(); - testInjector = projectSetup.testInjector; - projectFolder = projectSetup.projectFolder; - appDestinationFolderPath = projectSetup.appDestinationFolderPath; - }); - it("Ensures that the installed dependencies are prepared correctly", async () => { - const fs: IFileSystem = testInjector.resolve("fs"); - // Setup - await addDependencies(testInjector, projectFolder, { "bplist": "0.0.4" }); - - // Act - await preparePlatform(testInjector); - - // Assert - const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - - const results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { - return true; - }, { enumerateDirectories: true }); - - assert.isTrue(results.filter((val) => _.endsWith(val, "lodash")).length === 1); - assert.isTrue(results.filter((val) => _.endsWith(val, path.join(tnsModulesFolderPath, "bplist"))).length === 1); - assert.isTrue(results.filter((val) => _.endsWith(val, "bplist-creator")).length === 1); - assert.isTrue(results.filter((val) => _.endsWith(val, "bplist-parser")).length === 1); - }); - it("Ensures that scoped dependencies are prepared correctly", async () => { - // Setup - const fs = testInjector.resolve("fs"); - const scopedName = "@reactivex/rxjs"; - const dependencies: any = {}; - dependencies[scopedName] = "0.0.0-prealpha.3"; - // Do not pass dependencies object as the sinopia cannot work with scoped dependencies. Instead move them manually. - await addDependencies(testInjector, projectFolder, dependencies); - // Act - await preparePlatform(testInjector); - // Assert - const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - const scopedDependencyPath = path.join(tnsModulesFolderPath, "@reactivex", "rxjs"); - assert.isTrue(fs.exists(scopedDependencyPath)); - }); - - it("Ensures that scoped dependencies are prepared correctly when are not in root level", async () => { - // Setup - const customPluginName = "plugin-with-scoped-dependency"; - const customPluginDirectory = temp.mkdirSync("custom-plugin-directory"); - - const fs: IFileSystem = testInjector.resolve("fs"); - await fs.unzip(path.join("resources", "test", `${customPluginName}.zip`), customPluginDirectory); - - await addDependencies(testInjector, projectFolder, { "plugin-with-scoped-dependency": `file:${path.join(customPluginDirectory, customPluginName)}` }); - // Act - await preparePlatform(testInjector); - // Assert - const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - const results = fs.enumerateFilesInDirectorySync(tnsModulesFolderPath, (file, stat) => { - return true; - }, { enumerateDirectories: true }); - - const filteredResults = results.filter((val) => { - return _.endsWith(val, path.join("@scoped-plugin", "inner-plugin")); - }); - - assert.isTrue(filteredResults.length === 1); - }); -}); - -describe("Flatten npm modules tests", () => { - it("Doesn't handle the dependencies of devDependencies", async () => { - const projectSetup = await setupProject({}); - const testInjector = projectSetup.testInjector; - const projectFolder = projectSetup.projectFolder; - const appDestinationFolderPath = projectSetup.appDestinationFolderPath; - - const devDependencies = { - "gulp": "3.9.0", - "gulp-jscs": "1.6.0", - "gulp-jshint": "1.11.0" - }; - - await addDependencies(testInjector, projectFolder, {}, devDependencies); - - await preparePlatform(testInjector); - - // Assert - const fs = testInjector.resolve("fs"); - const tnsModulesFolderPath = path.join(appDestinationFolderPath, "app", "tns_modules"); - - const gulpFolderPath = path.join(tnsModulesFolderPath, "gulp"); - assert.isFalse(fs.exists(gulpFolderPath)); - - const gulpJscsFolderPath = path.join(tnsModulesFolderPath, "gulp-jscs"); - assert.isFalse(fs.exists(gulpJscsFolderPath)); - - const gulpJshint = path.join(tnsModulesFolderPath, "gulp-jshint"); - assert.isFalse(fs.exists(gulpJshint)); - - // Get all gulp dependencies - const gulpJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp", packageJsonName)); - _.each(_.keys(gulpJsonContent.dependencies), dependency => { - assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); - }); - - // Get all gulp-jscs dependencies - const gulpJscsJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jscs", packageJsonName)); - _.each(_.keys(gulpJscsJsonContent.dependencies), dependency => { - assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); - }); - - // Get all gulp-jshint dependencies - const gulpJshintJsonContent = fs.readJson(path.join(projectFolder, nodeModulesFolderName, "gulp-jshint", packageJsonName)); - _.each(_.keys(gulpJshintJsonContent.dependencies), dependency => { - assert.isFalse(fs.exists(path.join(tnsModulesFolderPath, dependency))); - }); - }); -}); diff --git a/test/project-service.ts b/test/project-service.ts index 35d6586d79..04b302076e 100644 --- a/test/project-service.ts +++ b/test/project-service.ts @@ -1,262 +1,105 @@ import * as yok from "../lib/common/yok"; -import * as stubs from "./stubs"; import * as constants from "../lib/constants"; -import { ChildProcess } from "../lib/common/child-process"; import * as ProjectServiceLib from "../lib/services/project-service"; -import { ProjectNameService } from "../lib/services/project-name-service"; -import * as ProjectDataServiceLib from "../lib/services/project-data-service"; -import * as ProjectHelperLib from "../lib/common/project-helper"; -import { StaticConfig } from "../lib/config"; -import * as NpmLib from "../lib/node-package-manager"; -import * as YarnLib from "../lib/yarn-package-manager"; -import * as PackageManagerLib from "../lib/package-manager"; -import { PackageInstallationManager } from "../lib/package-installation-manager"; -import { FileSystem } from "../lib/common/file-system"; -import * as path from "path"; -import temp = require("temp"); -import helpers = require("../lib/common/helpers"); import { assert } from "chai"; -import { Options } from "../lib/options"; -import { HostInfo } from "../lib/common/host-info"; -import { ProjectTemplatesService } from "../lib/services/project-templates-service"; import { SettingsService } from "../lib/common/test/unit-tests/stubs"; -import { DevicePlatformsConstants } from "../lib/common/mobile/device-platforms-constants"; -import { PacoteService } from "../lib/services/pacote-service"; - -const mockProjectNameValidator = { - validate: () => true -}; - -const dummyString: string = "dummyString"; -let hasPromptedForString = false; -const originalIsInteractive = helpers.isInteractive; - -temp.track(); - -async function prepareTestingPath(testInjector: IInjector, packageToInstall: string, packageName: string, options?: INpmInstallOptions): Promise { - options = options || { dependencyType: "save" }; - const fs = testInjector.resolve("fs"); - - const packageInstallationManager = testInjector.resolve("packageInstallationManager"); - const defaultTemplateDir = temp.mkdirSync("project-service"); - fs.writeJson(path.join(defaultTemplateDir, constants.PACKAGE_JSON_FILE_NAME), { - "name": "defaultTemplate", - "version": "1.0.0", - "description": "dummy", - "license": "MIT", - "readme": "dummy", - "repository": "dummy" - }); - - await packageInstallationManager.install(packageToInstall, defaultTemplateDir, options); - const defaultTemplatePath = path.join(defaultTemplateDir, constants.NODE_MODULES_FOLDER_NAME, packageName); - - fs.deleteDirectory(path.join(defaultTemplatePath, constants.NODE_MODULES_FOLDER_NAME)); - - return defaultTemplatePath; -} - -class ProjectIntegrationTest { - public testInjector: IInjector; - - constructor() { - this.createTestInjector(); - } - - public async createProject(projectOptions: IProjectSettings): Promise { - const projectService: IProjectService = this.testInjector.resolve("projectService"); - if (!projectOptions.template) { - projectOptions.template = constants.RESERVED_TEMPLATE_NAMES["default"]; - } - return projectService.createProject(projectOptions); - } - - public async assertProject(tempFolder: string, projectName: string, appId: string, projectSourceDirectory: string): Promise { - const fs: IFileSystem = this.testInjector.resolve("fs"); - const projectDir = path.join(tempFolder, projectName); - const appDirectoryPath = path.join(projectDir, "app"); - const tnsProjectFilePath = path.join(projectDir, "package.json"); - const tnsModulesPath = path.join(projectDir, constants.NODE_MODULES_FOLDER_NAME, constants.TNS_CORE_MODULES_NAME); - const packageJsonContent = fs.readJson(tnsProjectFilePath); - - assert.isTrue(fs.exists(appDirectoryPath)); - assert.isTrue(fs.exists(tnsProjectFilePath)); - assert.isTrue(fs.exists(tnsModulesPath)); - - assert.isFalse(fs.isEmptyDir(appDirectoryPath)); - - const actualAppId = packageJsonContent["nativescript"].id; - const expectedAppId = appId; - assert.equal(actualAppId, expectedAppId); - - const tnsCoreModulesRecord = packageJsonContent["dependencies"][constants.TNS_CORE_MODULES_NAME]; - assert.isTrue(tnsCoreModulesRecord !== null); - - const sourceDir = projectSourceDirectory; - - // assert dependencies and devDependencies are copied from template to real project - const sourcePackageJsonContent = fs.readJson(path.join(sourceDir, "package.json")); - const missingDeps = _.difference(_.keys(sourcePackageJsonContent.dependencies), _.keys(packageJsonContent.dependencies)); - const missingDevDeps = _.difference(_.keys(sourcePackageJsonContent.devDependencies), _.keys(packageJsonContent.devDependencies)); - assert.deepEqual(missingDeps, [], `All dependencies from template must be copied to project's package.json. Missing ones are: ${missingDeps.join(", ")}.`); - assert.deepEqual(missingDevDeps, [], `All devDependencies from template must be copied to project's package.json. Missing ones are: ${missingDevDeps.join(", ")}.`); - - // assert App_Resources are prepared correctly - const appResourcesDir = path.join(appDirectoryPath, "App_Resources"); - const appResourcesContents = fs.readDirectory(appResourcesDir); - assert.deepEqual(appResourcesContents, ["Android", "iOS"], "Project's app/App_Resources must contain Android and iOS directories."); - } - - public dispose(): void { - this.testInjector = undefined; - } - - private createTestInjector(): void { - this.testInjector = new yok.Yok(); - this.testInjector.register("childProcess", ChildProcess); - this.testInjector.register("errors", stubs.ErrorsStub); - this.testInjector.register('logger', stubs.LoggerStub); - this.testInjector.register("projectService", ProjectServiceLib.ProjectService); - this.testInjector.register("projectNameService", ProjectNameService); - this.testInjector.register("projectHelper", ProjectHelperLib.ProjectHelper); - this.testInjector.register("projectTemplatesService", ProjectTemplatesService); - this.testInjector.register("projectNameValidator", mockProjectNameValidator); - this.testInjector.register("projectData", stubs.ProjectDataStub); - - this.testInjector.register("fs", FileSystem); - this.testInjector.register("projectDataService", ProjectDataServiceLib.ProjectDataService); - this.testInjector.register("staticConfig", StaticConfig); - this.testInjector.register("analyticsService", { - track: async (): Promise => undefined, - trackEventActionInGoogleAnalytics: (data: IEventActionData) => Promise.resolve() - }); - - this.testInjector.register("userSettingsService", { - getSettingValue: async (settingName: string): Promise => undefined - }); - this.testInjector.register("npm", NpmLib.NodePackageManager); - this.testInjector.register("yarn", YarnLib.YarnPackageManager); - this.testInjector.register("packageManager", PackageManagerLib.PackageManager); - this.testInjector.register("httpClient", {}); - - this.testInjector.register("options", Options); - this.testInjector.register("hostInfo", HostInfo); - this.testInjector.register("prompter", { - confirm: async (message: string): Promise => true, - getString: async (message: string): Promise => { - hasPromptedForString = true; - return dummyString; - } - }); - this.testInjector.register("packageInstallationManager", PackageInstallationManager); - this.testInjector.register("settingsService", SettingsService); - this.testInjector.register("devicePlatformsConstants", DevicePlatformsConstants); - this.testInjector.register("androidResourcesMigrationService", { - hasMigrated: (appResourcesDir: string): boolean => true - }); - this.testInjector.register("hooksService", { - executeAfterHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined - }); - this.testInjector.register("pacoteService", PacoteService); - this.testInjector.register("proxyService", { - getCache: async (): Promise => null - }); - } -} - -describe("Project Service Tests", () => { - describe("project service integration tests", () => { - let defaultTemplatePath: string; - - before(async () => { - const projectIntegrationTest = new ProjectIntegrationTest(); - - defaultTemplatePath = await prepareTestingPath(projectIntegrationTest.testInjector, constants.RESERVED_TEMPLATE_NAMES["default"], constants.RESERVED_TEMPLATE_NAMES["default"]); - }); +import { LoggerStub, ErrorsStub } from "./stubs"; +import * as path from "path"; - describe("project name validation tests", () => { - const validProjectName = "valid"; - const invalidProjectName = "1invalid"; - let projectIntegrationTest: ProjectIntegrationTest; - let tempFolder: string; - let prompter: IPrompter; +describe("projectService", () => { + describe("createProject", () => { + const invalidProjectName = "1invalid"; + const dirToCreateProject: string = path.resolve("projectDir"); - beforeEach(() => { - hasPromptedForString = false; - helpers.isInteractive = () => true; - projectIntegrationTest = new ProjectIntegrationTest(); - tempFolder = temp.mkdirSync("project"); - prompter = projectIntegrationTest.testInjector.resolve("prompter"); + /* tslint:disable:no-empty */ + const getTestInjector = (opts: { projectName: string }): IInjector => { + const testInjector = new yok.Yok(); + testInjector.register("packageManager", { + install: async () => { } }); - - afterEach(() => { - helpers.isInteractive = originalIsInteractive; + testInjector.register("errors", ErrorsStub); + testInjector.register("fs", { + exists: () => true, + isEmptyDir: () => true, + createDirectory: () => { }, + writeJson: () => { }, + deleteDirectory: () => { }, + ensureDirectoryExists: () => { }, + readJson: () => ({}) }); - - it("creates project when is interactive and incorrect name is specified and the --force option is set", async () => { - const projectName = invalidProjectName; - await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true }); - await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath); + testInjector.register("logger", LoggerStub); + testInjector.register("projectDataService", { + getProjectData: (projectDir?: string): IProjectData => ({ + getAppResourcesDirectoryPath: () => "appResourcesDirectoryPath" + }), + setNSValue: () => { } }); - - it("creates project when is interactive and incorrect name is specified and the user confirms to use the incorrect name", async () => { - const projectName = invalidProjectName; - prompter.confirm = (message: string): Promise => Promise.resolve(true); - - await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder }); - await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath); + testInjector.register("projectData", {}); + testInjector.register("projectNameService", { + ensureValidName: async () => opts.projectName }); - - 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 () => { - const projectName = invalidProjectName; - - prompter.confirm = (message: string): Promise => Promise.resolve(false); - - await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder }); - assert.isTrue(hasPromptedForString); + testInjector.register("projectTemplatesService", { + prepareTemplate: async () => ({ + templateName: constants.RESERVED_TEMPLATE_NAMES["default"], + templatePath: "some path", + templateVersion: "v2", + templatePackageJsonContent: { + dependencies: { + ["tns-core-modules"]: "1.0.0" + } + }, + version: "1.0.0" + }) }); - - 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 () => { - const projectName = invalidProjectName; - - prompter.confirm = (message: string): Promise => Promise.resolve(false); - - const incorrectInputsLimit = 5; - let incorrectInputsCount = 0; - - prompter.getString = async (message: string): Promise => { - if (incorrectInputsCount < incorrectInputsLimit) { - incorrectInputsCount++; - } else { - hasPromptedForString = true; - - return validProjectName; - } - - return projectName; - }; - - await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder }); - assert.isTrue(hasPromptedForString); + testInjector.register("staticConfig", { + PROJECT_FILE_NAME: "package.json" }); - - it("does not create project when is not interactive and incorrect name is specified", async () => { - const projectName = invalidProjectName; - helpers.isInteractive = () => false; - - await assert.isRejected(projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: false })); + testInjector.register("projectHelper", { + generateDefaultAppId: () => `org.nativescript.${opts.projectName}` + }); + testInjector.register("packageInstallationManager", {}); + testInjector.register("settingsService", SettingsService); + testInjector.register("hooksService", { + executeAfterHooks: async (commandName: string, hookArguments?: IDictionary): Promise => undefined + }); + testInjector.register("pacoteService", { + manifest: () => Promise.resolve(), + downloadAndExtract: () => Promise.resolve(), + extractPackage: () => Promise.resolve() }); - it("creates project when is not interactive and incorrect name is specified and the --force option is set", async () => { - const projectName = invalidProjectName; - helpers.isInteractive = () => false; - - await projectIntegrationTest.createProject({ projectName: projectName, pathToProject: tempFolder, force: true }); + return testInjector; + }; + /* tslint:enable:no-empty */ + + it("creates project with invalid name when projectNameService does not fail", async () => { + const projectName = invalidProjectName; + const testInjector = getTestInjector({ projectName }); + const projectService = testInjector.resolve(ProjectServiceLib.ProjectService); + const projectCreationData = await projectService.createProject({ projectName: projectName, pathToProject: dirToCreateProject, force: true, template: constants.RESERVED_TEMPLATE_NAMES["default"] }); + assert.deepEqual(projectCreationData, { projectName, projectDir: path.join(dirToCreateProject, projectName) }); + }); - await projectIntegrationTest.assertProject(tempFolder, projectName, `org.nativescript.${projectName}`, defaultTemplatePath); - }); + it("fails when invalid name is passed when projectNameService fails", async () => { + const projectName = invalidProjectName; + const testInjector = getTestInjector({ projectName }); + const projectNameService = testInjector.resolve("projectNameService"); + const err = new Error("Invalid name"); + projectNameService.ensureValidName = (name: string) => { + throw err; + }; + const projectService = testInjector.resolve(ProjectServiceLib.ProjectService); + await assert.isRejected(projectService.createProject({ projectName: projectName, pathToProject: dirToCreateProject, template: constants.RESERVED_TEMPLATE_NAMES["default"] }), err.message); }); + it("fails when project directory is not empty", async () => { + const projectName = invalidProjectName; + const testInjector = getTestInjector({ projectName }); + const fs = testInjector.resolve("fs"); + fs.isEmptyDir = (name: string) => false; + const projectService = testInjector.resolve(ProjectServiceLib.ProjectService); + 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)}`); + }); }); describe("isValidNativeScriptProject", () => { @@ -291,7 +134,7 @@ describe("Project Service Tests", () => { const testInjector = getTestInjector({ projectDir: "projectDir", projectId: "projectId", - projectIdentifiers: { android: "projectId", ios: "projectId"}, + projectIdentifiers: { android: "projectId", ios: "projectId" }, }); const projectService: IProjectService = testInjector.resolve(ProjectServiceLib.ProjectService); @@ -304,7 +147,7 @@ describe("Project Service Tests", () => { const projectData: any = { projectDir: "projectDir", projectId: "projectId", - projectIdentifiers: { android: "projectId", ios: "projectId"} + projectIdentifiers: { android: "projectId", ios: "projectId" } }; let returnedProjectData: any = null; diff --git a/test/tools/node-modules/node-modules-dependencies-builder.ts b/test/tools/node-modules/node-modules-dependencies-builder.ts index 0b8d85dcb3..0884817c87 100644 --- a/test/tools/node-modules/node-modules-dependencies-builder.ts +++ b/test/tools/node-modules/node-modules-dependencies-builder.ts @@ -10,6 +10,7 @@ interface IDependencyInfo { depth: number; dependencies?: IDependencyInfo[]; nativescript?: any; + isDevDependency?: boolean; } // TODO: Add integration tests. @@ -58,10 +59,11 @@ describe("nodeModulesDependenciesBuilder", () => { return path.join(parentDir || pathToProject, constants.NODE_MODULES_FOLDER_NAME, dependencyName); }; - const getNodeModuleInfoForExpecteDependency = (name: string, depth: number, nativescript?: any, dependencies?: string[]): IDependencyData => { + const getNodeModuleInfoForExpecteDependency = (dir: string, depth: number, nativescript?: any, dependencies?: string[], name?: string): IDependencyData => { + const packageName = name || path.basename(dir); const result: IDependencyData = { - name: path.basename(name), - directory: getPathToDependencyInNodeModules(name), + name: packageName, + directory: getPathToDependencyInNodeModules(dir), depth, dependencies: dependencies || [] }; @@ -77,14 +79,20 @@ describe("nodeModulesDependenciesBuilder", () => { return path.join(getPathToDependencyInNodeModules(dependencyName, parentDir), constants.PACKAGE_JSON_FILE_NAME); }; - const getDependenciesObjectFromDependencyInfo = (depInfos: IDependencyInfo[], nativescript: any): { dependencies: any, nativescript?: any } => { + const getDependenciesObjectFromDependencyInfo = (depInfos: IDependencyInfo[], nativescript: any): { dependencies: IStringDictionary, nativescript?: any, devDependencies: IStringDictionary } => { const dependencies: any = {}; + const devDepdencies: any = {}; _.each(depInfos, innerDependency => { - dependencies[innerDependency.name] = innerDependency.version; + if (innerDependency.isDevDependency) { + devDepdencies[innerDependency.name] = innerDependency.version; + } else { + dependencies[innerDependency.name] = innerDependency.version; + } }); const result: any = { - dependencies + dependencies, + devDepdencies }; if (nativescript) { @@ -94,8 +102,8 @@ describe("nodeModulesDependenciesBuilder", () => { return result; }; - const getDependenciesObject = (filename: string, deps: IDependencyInfo[], parentDir: string): { dependencies: any } => { - let result: { dependencies: any } = null; + const getDependenciesObject = (filename: string, deps: IDependencyInfo[], parentDir: string): { dependencies: IStringDictionary, nativescript?: any, devDependencies: IStringDictionary } => { + let result: { dependencies: IStringDictionary, nativescript?: any, devDependencies: IStringDictionary } = null; for (const dependencyInfo of deps) { const pathToPackageJson = getPathToPackageJsonOfDependency(dependencyInfo.name, parentDir); if (filename === pathToPackageJson) { @@ -177,13 +185,14 @@ describe("nodeModulesDependenciesBuilder", () => { return nodeModulesDependenciesBuilder; }; - const generateDependency = (name: string, version: string, depth: number, dependencies: IDependencyInfo[], nativescript?: any): IDependencyInfo => { + const generateDependency = (name: string, version: string, depth: number, dependencies: IDependencyInfo[], nativescript?: any, opts?: { isDevDependency: boolean }): IDependencyInfo => { return { name, version, depth, dependencies, - nativescript + nativescript, + isDevDependency: !!(opts && opts.isDevDependency) }; }; @@ -195,6 +204,100 @@ describe("nodeModulesDependenciesBuilder", () => { const secondPackage = "secondPackage"; const thirdPackage = "thirdPackage"; + it("when there are both dependencies and devDependencies installed", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├── firstPackage@1.0.0 + // ├── secondPackage@1.1.0 + // └── thirdPackage@1.2.0 // this is devDependency + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, null), + generateDependency(secondPackage, "1.1.0", 0, null), + generateDependency(thirdPackage, "1.2.0", 0, null, null, { isDevDependency: true }) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0), + getNodeModuleInfoForExpecteDependency(secondPackage, 0) + ]; + + assert.deepEqual(actualResult, expectedResult); + }); + + it("when there are both dependencies and devDependencies installed, does not handle dependencies of devDependencies", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 // this is devDependency + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [generateDependency(secondPackage, "1.2.0", 1, null)], null, { isDevDependency: true }), + generateDependency(secondPackage, "1.1.0", 0, null) + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(secondPackage, 0), + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + assert.deepEqual(actualResult, expectedResult); + + }); + + it("when there are scoped dependencies", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ @scope/firstPackage@1.0.0 + // │ └── secondPackage@1.2.0 + // └── secondPackage@1.1.0 + + const scopedPackageName = `@scope/${firstPackage}`; + const rootDeps: IDependencyInfo[] = [ + generateDependency(scopedPackageName, "1.0.0", 0, [generateDependency(secondPackage, "1.2.0", 1, null)]), + generateDependency(secondPackage, "1.1.0", 0, null) + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(scopedPackageName, 0, null, [secondPackage], scopedPackageName), + getNodeModuleInfoForExpecteDependency(secondPackage, 0), + getNodeModuleInfoForExpecteDependency(path.join(scopedPackageName, constants.NODE_MODULES_FOLDER_NAME, secondPackage), 1) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + assert.deepEqual(actualResult, expectedResult); + }); + + it("when there are scoped dependencies as dependency of other non-scoped dependency", async () => { + // The test validates the following dependency tree, when npm 3+ is used. + // + // ├─┬ firstPackage@1.0.0 + // │ └── @scope/secondPackage@1.2.0 + // └── thirdPackage@1.1.0 + + const scopedPackageName = `@scope/${secondPackage}`; + const rootDeps: IDependencyInfo[] = [ + generateDependency(firstPackage, "1.0.0", 0, [generateDependency(scopedPackageName, "1.2.0", 1, null)]), + generateDependency(thirdPackage, "1.1.0", 0, null) + ]; + + const expectedResult: IDependencyData[] = [ + getNodeModuleInfoForExpecteDependency(firstPackage, 0, null, [scopedPackageName]), + getNodeModuleInfoForExpecteDependency(thirdPackage, 0), + getNodeModuleInfoForExpecteDependency(path.join(firstPackage, constants.NODE_MODULES_FOLDER_NAME, scopedPackageName), 1, null, null, scopedPackageName) + ]; + + const nodeModulesDependenciesBuilder = generateTest(rootDeps); + const actualResult = await nodeModulesDependenciesBuilder.getProductionDependencies(pathToProject); + assert.deepEqual(actualResult, expectedResult); + }); + it("when all dependencies are installed at the root level of the project", async () => { // The test validates the following dependency tree, when npm 3+ is used. //