diff --git a/.travis.yml b/.travis.yml index 26b0021abb..30a1f019ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,8 @@ node_js: - '6' git: submodules: true +install: +- npm install --ignore-scripts before_script: - gem install xcodeproj - gem install cocoapods diff --git a/CHANGELOG.md b/CHANGELOG.md index f72a4fa158..2a994e9ea7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ NativeScript CLI Changelog ================ +3.1.1 (2017, June 28) +== + +### Fixed +* [Fixed #2879](https://github.com/NativeScript/nativescript-cli/issues/2879): Livesync does not apply changes in CSS files on physical iOS devices +* [Fixed #2892](https://github.com/NativeScript/nativescript-cli/issues/2892): Not copying the CFBundleURLTypes from the plist file to the project +* [Fixed #2916](https://github.com/NativeScript/nativescript-cli/issues/2916): If no device or emulator is attached `tns debug android` kills the commandline process and doesn't start an emulator +* [Fixed #2923](https://github.com/NativeScript/nativescript-cli/issues/2923): Fix asking for user email on postinstall + 3.1.0 (2017, June 22) == @@ -66,7 +75,7 @@ NativeScript CLI Changelog * [Fixed #2661](https://github.com/NativeScript/nativescript-cli/issues/2661): Adding new files during livesync doesn't succeed on iOS Devices * [Fixed #2650](https://github.com/NativeScript/nativescript-cli/issues/2650): Release Build Android error: gradlew.bat failed with exit code 1 When Path contains Space * [Fixed #2125](https://github.com/NativeScript/nativescript-cli/issues/2125): NativeScript setup script fails on Mac -* [Fixed #2697](https://github.com/NativeScript/nativescript-cli/issues/2697): App_Resources being copied into app RAW +* [Fixed #2697](https://github.com/NativeScript/nativescript-cli/issues/2697): App_Resources being copied into app RAW 3.0.0-RC.1 (2017, March 29) == @@ -76,19 +85,19 @@ NativeScript CLI Changelog * [Implemented #2170](https://github.com/NativeScript/nativescript-cli/issues/2170): CLI should report adb install errors * [Implemented #2204](https://github.com/NativeScript/nativescript-cli/issues/2204): Remove fibers from CLI and use async/await * [Implemented #2349](https://github.com/NativeScript/nativescript-cli/issues/2349): Command "emulate" and options "--emulator/--device/--avd" are a bit confusing to use -* [Implemented #2513](https://github.com/NativeScript/nativescript-cli/issues/2513): Allow templates to omit App_Resources +* [Implemented #2513](https://github.com/NativeScript/nativescript-cli/issues/2513): Allow templates to omit App_Resources * [Implemented #2633](https://github.com/NativeScript/nativescript-cli/issues/2633): Deprecate `tns livesync` command ### Fixed -* [Fixed #2225](https://github.com/NativeScript/nativescript-cli/issues/2225): The tns debug ios command hangs on launch screen +* [Fixed #2225](https://github.com/NativeScript/nativescript-cli/issues/2225): The tns debug ios command hangs on launch screen * [Fixed #2357](https://github.com/NativeScript/nativescript-cli/issues/2357): --copy-to option is broken * [Fixed #2446](https://github.com/NativeScript/nativescript-cli/issues/2446): tns emulate --device NonexistentName retunrs different messages for ios and android * [Fixed #2486](https://github.com/NativeScript/nativescript-cli/issues/2486): tns run android (without started emulator/connected device) fails to start app * [Fixed #2487](https://github.com/NativeScript/nativescript-cli/issues/2487): Exception: The plugin tns-android@2.5.0 is already installed * [Fixed #2496](https://github.com/NativeScript/nativescript-cli/issues/2496): `tns platform clean android` does not maintain the same version of the runtimes * [Fixed #2511](https://github.com/NativeScript/nativescript-cli/issues/2511): Second run of `tns run android --release` does not respect changes in application code -* [Fixed #2557](https://github.com/NativeScript/nativescript-cli/issues/2557): 2.5.1 iOS incremental build generates inconsistent binary +* [Fixed #2557](https://github.com/NativeScript/nativescript-cli/issues/2557): 2.5.1 iOS incremental build generates inconsistent binary * [Fixed #2559](https://github.com/NativeScript/nativescript-cli/issues/2559): `tns init` fails, `ins init --force` produce invalid app * [Fixed #2560](https://github.com/NativeScript/nativescript-cli/issues/2560): `tns run` should do full prepare/rebuild if something in App_Resources is changes * [Fixed #2561](https://github.com/NativeScript/nativescript-cli/issues/2561): Fix prepare process terminates if passing too many arguments to a new node process @@ -116,8 +125,8 @@ NativeScript CLI Changelog ### Fixed * [Fixed #2476](https://github.com/NativeScript/nativescript-cli/issues/2476): `tns run ios` unable to sync files on iOS Simulator -* [Fixed #2491](https://github.com/NativeScript/nativescript-cli/issues/2491): "ERROR Error: Could not find module" after 2.5 upgrade -* [Fixed #2472](https://github.com/NativeScript/nativescript-cli/issues/2472): Livesync watches .DS_Store files +* [Fixed #2491](https://github.com/NativeScript/nativescript-cli/issues/2491): "ERROR Error: Could not find module" after 2.5 upgrade +* [Fixed #2472](https://github.com/NativeScript/nativescript-cli/issues/2472): Livesync watches .DS_Store files * [Fixed #2469](https://github.com/NativeScript/nativescript-cli/issues/2469): `tns run` can get stuck in a loop since 2.5 * [Fixed #2512](https://github.com/NativeScript/nativescript-cli/issues/2512): Run should not watch hidden files * [Fixed #2521](https://github.com/NativeScript/nativescript-cli/issues/2521): Enable requesting user input during plugin installation @@ -130,7 +139,7 @@ NativeScript CLI Changelog * [Implemented #2276](https://github.com/NativeScript/nativescript-cli/issues/2276): Support plugin development when using the watch option * [Implemented #2260]( https://github.com/NativeScript/nativescript-cli/issues/2260): Deprecate support for Node.js 4.x feature * [Implemented #2112]( https://github.com/NativeScript/nativescript-cli/issues/2112): Update webpack plugin to run as a npm script feature -* [Implemented #1906]( https://github.com/NativeScript/nativescript-cli/issues/1906): TNS Doctor Android tools detection feature +* [Implemented #1906]( https://github.com/NativeScript/nativescript-cli/issues/1906): TNS Doctor Android tools detection feature * [Implemented #1845](https://github.com/NativeScript/nativescript-cli/issues/1845): Remove NPM as a dependency, use the NPM installed on users' machine feature ### Fixed @@ -138,7 +147,7 @@ NativeScript CLI Changelog **Install and setup issues** * [Fixed #2302](https://github.com/NativeScript/nativescript-cli/issues/2302): App runs in Xcode and CLI but not on test flight -* [Fixed #1922](https://github.com/NativeScript/nativescript-cli/issues/1922): TNS doctor does not detect if JDK is not installed and throws an error +* [Fixed #1922](https://github.com/NativeScript/nativescript-cli/issues/1922): TNS doctor does not detect if JDK is not installed and throws an error * [Fixed #2312](https://github.com/NativeScript/nativescript-cli/issues/2312): Creating app w/ nativescript@next is not deploying App_Resources folder * [Fixed #2308](https://github.com/NativeScript/nativescript-cli/issues/2308): Using nativescript@next brings multiple libraries into node_modules * [Fixed #2301](https://github.com/NativeScript/nativescript-cli/issues/2301): Rework logic of handling 2 package.json files after tns create @@ -149,8 +158,8 @@ NativeScript CLI Changelog * [Fixed #2213](https://github.com/NativeScript/nativescript-cli/issues/2213): When switching build configurations plugin platform files are copied into assets * [Fixed #2177](https://github.com/NativeScript/nativescript-cli/issues/2177): Prepare does not flatten scoped dependencies -* [Fixed #2150](https://github.com/NativeScript/nativescript-cli/issues/2150): TNS run command broken with using --watch -* [Fixed #1740](https://github.com/NativeScript/nativescript-cli/issues/1740): Dev Dependencies (like Gulp, etc) getting built and build is failing because of which Gulp * [Fixed #](): integration is not working currently. +* [Fixed #2150](https://github.com/NativeScript/nativescript-cli/issues/2150): TNS run command broken with using --watch +* [Fixed #1740](https://github.com/NativeScript/nativescript-cli/issues/1740): Dev Dependencies (like Gulp, etc) getting built and build is failing because of which Gulp * [Fixed #](): integration is not working currently. * [Fixed #2270](https://github.com/NativeScript/nativescript-cli/issues/2270): App is not rebuilt after removing plugin with native dependencies bug * [Fixed #2253](https://github.com/NativeScript/nativescript-cli/issues/2253): Prepare command with bundle option doesn't copy files @@ -163,7 +172,7 @@ NativeScript CLI Changelog **Emulate/ Device issues** -* [Fixed #1522](https://github.com/NativeScript/nativescript-cli/issues/1522): Cannot specify emulator device while debugging +* [Fixed #1522](https://github.com/NativeScript/nativescript-cli/issues/1522): Cannot specify emulator device while debugging * [Fixed #2345](https://github.com/NativeScript/nativescript-cli/issues/2345): Option --device {DeviceName} not working for emulate/run/debug/deploy bug * [Fixed #2344](https://github.com/NativeScript/nativescript-cli/issues/2344): Emulate command not working for iOS @@ -186,18 +195,18 @@ NativeScript CLI Changelog * [Fixed #2228](https://github.com/NativeScript/nativescript-cli/issues/2228): Homebrew as root is no longer supported * [Fixed #2227]( https://github.com/NativeScript/nativescript-cli/issues/2227): Remove use of lib/iOS folder for native plugins * [Fixed #282]( https://github.com/NativeScript/nativescript-cli/issues/282): "tns platform add android --path TNSApp --symlink" does not work on Windows -* [Fixed #1802](https://github.com/NativeScript/nativescript-cli/issues/1802): semver copied to platforms/android even though it's a dev dependency +* [Fixed #1802](https://github.com/NativeScript/nativescript-cli/issues/1802): semver copied to platforms/android even though it's a dev dependency 2.4.2 (2016, December 8) == ### Fixed -* [Fixed #2332](https://github.com/NativeScript/nativescript-cli/pull/2332): Fixed email registration process +* [Fixed #2332](https://github.com/NativeScript/nativescript-cli/pull/2332): Fixed email registration process 2.4.1 (2016, December 4) == ### Fixed -* [Fixed #2200](https://github.com/NativeScript/nativescript-cli/pull/2200): TypeScript files don't show in {N} debugger. -* [Fixed #2273](https://github.com/NativeScript/nativescript-cli/pull/2273): Livesync should continue to work after app crash +* [Fixed #2200](https://github.com/NativeScript/nativescript-cli/pull/2200): TypeScript files don't show in {N} debugger. +* [Fixed #2273](https://github.com/NativeScript/nativescript-cli/pull/2273): Livesync should continue to work after app crash 2.4.0 (2016, November 16) == @@ -260,7 +269,7 @@ NativeScript CLI Changelog ### New * [Implemented #1959](https://github.com/NativeScript/nativescript-cli/issues/1959): XCode8/iOS10 support -* [Implemented #1948](https://github.com/NativeScript/nativescript-cli/issues/1948): npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue +* [Implemented #1948](https://github.com/NativeScript/nativescript-cli/issues/1948): npm WARN deprecated minimatch@0.2.14: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue * [Implemented #1944](https://github.com/NativeScript/nativescript-cli/issues/1944): Enhancement: tns plugin INSTALL alias * [Implemented #1565](https://github.com/NativeScript/nativescript-cli/issues/1565): Livesync debugging support * [Implemented #526](https://github.com/NativeScript/nativescript-cli/issues/526): Update plugin command diff --git a/lib/bootstrap.ts b/lib/bootstrap.ts index 0ebcc14815..ef4717e4eb 100644 --- a/lib/bootstrap.ts +++ b/lib/bootstrap.ts @@ -139,3 +139,4 @@ $injector.requireCommand("extension|uninstall", "./commands/extensibility/uninst $injector.requirePublic("extensibilityService", "./services/extensibility-service"); $injector.require("nodeModulesDependenciesBuilder", "./tools/node-modules/node-modules-dependencies-builder"); +$injector.require("subscriptionService", "./services/subscription-service"); diff --git a/lib/commands/debug.ts b/lib/commands/debug.ts index 1a9a79e12b..4d2217c508 100644 --- a/lib/commands/debug.ts +++ b/lib/commands/debug.ts @@ -29,9 +29,6 @@ this.$config.debugLivesync = true; - await this.$devicesService.initialize({ - - }); await this.$devicesService.detectCurrentlyAttachedDevices(); const devices = this.$devicesService.getDeviceInstances(); @@ -86,8 +83,7 @@ platform: this.platform, deviceId: this.$options.device, emulator: this.$options.emulator, - skipDeviceDetectionInterval: true, - skipInferPlatform: true + skipDeviceDetectionInterval: true }); // Start emulator if --emulator is selected or no devices found. if (this.$options.emulator || this.$devicesService.deviceCount === 0) { diff --git a/lib/commands/post-install.ts b/lib/commands/post-install.ts index 87263def0c..779703e5c3 100644 --- a/lib/commands/post-install.ts +++ b/lib/commands/post-install.ts @@ -1,15 +1,8 @@ import { PostInstallCommand } from "../common/commands/post-install"; -import * as emailValidator from "email-validator"; -import * as queryString from "querystring"; -import * as helpers from "../common/helpers"; export class PostInstallCliCommand extends PostInstallCommand { - private logger: ILogger; - constructor($fs: IFileSystem, - private $httpClient: Server.IHttpClient, - private $prompter: IPrompter, - private $userSettingsService: IUserSettingsService, + private $subscriptionService: ISubscriptionService, $staticConfig: Config.IStaticConfig, $commandsService: ICommandsService, $htmlHelpService: IHtmlHelpService, @@ -18,63 +11,12 @@ export class PostInstallCliCommand extends PostInstallCommand { $analyticsService: IAnalyticsService, $logger: ILogger) { super($fs, $staticConfig, $commandsService, $htmlHelpService, $options, $doctorService, $analyticsService, $logger); - this.logger = $logger; } public async execute(args: string[]): Promise { await super.execute(args); - if (await this.shouldAskForEmail()) { - this.logger.out("Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); - let email = await this.getEmail("(press Enter for blank)"); - await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true); - await this.sendEmail(email); - } - } - - private async shouldAskForEmail(): Promise { - return helpers.isInteractive() && await process.env.CLI_NOPROMPT !== "1" && !this.$userSettingsService.getSettingValue("EMAIL_REGISTERED"); - } - - private async getEmail(prompt: string, options?: IPrompterOptions): Promise { - let schema: IPromptSchema = { - message: prompt, - type: "input", - name: "inputEmail", - validate: (value: any) => { - if (value === "" || emailValidator.validate(value)) { - return true; - } - return "Please provide a valid e-mail or simply leave it blank."; - }, - default: options && options.defaultAction - }; - - let result = await this.$prompter.get([schema]); - return result.inputEmail; - } - - private async sendEmail(email: string): Promise { - if (email) { - let postData = queryString.stringify({ - 'elqFormName': "dev_uins_cli", - 'elqSiteID': '1325', - 'emailAddress': email, - 'elqCookieWrite': '0' - }); - - let options = { - url: 'https://s1325.t.eloqua.com/e/f2', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': postData.length - }, - body: postData - }; - - await this.$httpClient.httpRequest(options); - } + await this.$subscriptionService.subscribeForNewsletter(); } } diff --git a/lib/common b/lib/common index f906af7e73..d5e8675ed3 160000 --- a/lib/common +++ b/lib/common @@ -1 +1 @@ -Subproject commit f906af7e73171ca839a8313f4d33f8e6a0ae88ee +Subproject commit d5e8675ed30da6f096643ff1aac4c6d2ec18b46f diff --git a/lib/constants.ts b/lib/constants.ts index 984accc309..7da59a05a0 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -86,3 +86,4 @@ export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const VERSION_STRING = "version"; export const INSPECTOR_CACHE_DIRNAME = "ios-inspector"; +export const POST_INSTALL_COMMAND_NAME = "post-install-cli"; diff --git a/lib/definitions/subscription-service.d.ts b/lib/definitions/subscription-service.d.ts new file mode 100644 index 0000000000..4e4d0e79d1 --- /dev/null +++ b/lib/definitions/subscription-service.d.ts @@ -0,0 +1,11 @@ +/** + * Describes methods for subscribing to different NativeScript campaigns. + */ +interface ISubscriptionService { + /** + * Subscribes users for NativeScript's newsletter by asking them for their email. + * In case we've already asked the user for his email, this method will do nothing. + * @returns {Promise} + */ + subscribeForNewsletter(): Promise; +} diff --git a/lib/services/ios-project-service.ts b/lib/services/ios-project-service.ts index deec5de691..2d6d966e00 100644 --- a/lib/services/ios-project-service.ts +++ b/lib/services/ios-project-service.ts @@ -338,7 +338,7 @@ export class IOSProjectService extends projectServiceBaseLib.PlatformProjectServ if (!buildConfig.release && !buildConfig.architectures) { await this.$devicesService.initialize({ platform: this.$devicePlatformsConstants.iOS.toLowerCase(), deviceId: buildConfig.device, - isBuildForDevice: true + skipEmulatorStart: true }); let instances = this.$devicesService.getDeviceInstances(); let devicesArchitectures = _(instances) diff --git a/lib/services/subscription-service.ts b/lib/services/subscription-service.ts new file mode 100644 index 0000000000..8a9e18a6cf --- /dev/null +++ b/lib/services/subscription-service.ts @@ -0,0 +1,73 @@ +import * as emailValidator from "email-validator"; +import * as queryString from "querystring"; +import * as helpers from "../common/helpers"; + +export class SubscriptionService implements ISubscriptionService { + constructor(private $httpClient: Server.IHttpClient, + private $prompter: IPrompter, + private $userSettingsService: IUserSettingsService, + private $logger: ILogger) { + } + + public async subscribeForNewsletter(): Promise { + if (await this.shouldAskForEmail()) { + this.$logger.out("Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); + let email = await this.getEmail("(press Enter for blank)"); + await this.$userSettingsService.saveSetting("EMAIL_REGISTERED", true); + await this.sendEmail(email); + } + } + + /** + * Checks whether we should ask the current user if they want to subscribe to NativeScript newsletter. + * NOTE: This method is protected, not private, only because of our unit tests. + * @returns {Promise} + */ + protected async shouldAskForEmail(): Promise { + return helpers.isInteractive() && process.env.CLI_NOPROMPT !== "1" && !(await this.$userSettingsService.getSettingValue("EMAIL_REGISTERED")); + } + + private async getEmail(prompt: string, options?: IPrompterOptions): Promise { + let schema: IPromptSchema = { + message: prompt, + type: "input", + name: "inputEmail", + validate: (value: any) => { + if (value === "" || emailValidator.validate(value)) { + return true; + } + + return "Please provide a valid e-mail or simply leave it blank."; + } + }; + + let result = await this.$prompter.get([schema]); + return result.inputEmail; + } + + private async sendEmail(email: string): Promise { + if (email) { + let postData = queryString.stringify({ + 'elqFormName': "dev_uins_cli", + 'elqSiteID': '1325', + 'emailAddress': email, + 'elqCookieWrite': '0' + }); + + let options = { + url: 'https://s1325.t.eloqua.com/e/f2', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + }, + body: postData + }; + + await this.$httpClient.httpRequest(options); + } + } + +} + +$injector.register("subscriptionService", SubscriptionService); diff --git a/package.json b/package.json index ed70bb2d90..44623ea456 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nativescript", "preferGlobal": true, - "version": "3.1.0", + "version": "3.1.1", "author": "Telerik ", "description": "Command-line interface for building NativeScript projects", "bin": { @@ -42,7 +42,7 @@ "glob": "^7.0.3", "iconv-lite": "0.4.11", "inquirer": "0.9.0", - "ios-device-lib": "0.4.6", + "ios-device-lib": "0.4.7", "ios-mobileprovision-finder": "1.0.9", "ios-sim-portable": "~3.0.0", "lockfile": "1.0.1", diff --git a/postinstall.js b/postinstall.js index a5bb15e6a1..8c3a76fab2 100644 --- a/postinstall.js +++ b/postinstall.js @@ -1,8 +1,9 @@ "use strict"; var child_process = require("child_process"); -var commandArgs = ["bin/tns", "post-install-cli"]; var path = require("path"); +var constants = require(path.join(__dirname, "lib", "constants")); +var commandArgs = [path.join(__dirname, "bin", "tns"), constants.POST_INSTALL_COMMAND_NAME]; var nodeArgs = require(path.join(__dirname, "lib", "common", "scripts", "node-args")).getNodeArgs(); -child_process.spawn(process.argv[0], nodeArgs.concat(commandArgs), {stdio: "inherit"}); +child_process.spawn(process.argv[0], nodeArgs.concat(commandArgs), { stdio: "inherit" }); diff --git a/test/commands/post-install.ts b/test/commands/post-install.ts new file mode 100644 index 0000000000..5cadda5c0b --- /dev/null +++ b/test/commands/post-install.ts @@ -0,0 +1,59 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { PostInstallCliCommand } from "../../lib/commands/post-install"; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("fs", { + setCurrentUserAsOwner: async (path: string, owner: string): Promise => undefined + }); + + testInjector.register("subscriptionService", { + subscribeForNewsletter: async (): Promise => undefined + }); + + testInjector.register("staticConfig", {}); + + testInjector.register("commandsService", { + tryExecuteCommand: async (commandName: string, commandArguments: string[]): Promise => undefined + }); + + testInjector.register("htmlHelpService", { + generateHtmlPages: async (): Promise => undefined + }); + + testInjector.register("options", {}); + + testInjector.register("doctorService", { + printWarnings: async (configOptions?: { trackResult: boolean }): Promise => undefined + }); + + testInjector.register("analyticsService", { + checkConsent: async (): Promise => undefined, + track: async (featureName: string, featureValue: string): Promise => undefined + }); + + testInjector.register("logger", { + out: (formatStr?: any, ...args: any[]): void => undefined, + printMarkdown: (...args: any[]): void => undefined + }); + + testInjector.registerCommand("post-install-cli", PostInstallCliCommand); + + return testInjector; +}; + +describe("post-install command", () => { + it("calls subscriptionService.subscribeForNewsletter method", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve("subscriptionService"); + let isSubscribeForNewsletterCalled = false; + subscriptionService.subscribeForNewsletter = async (): Promise => { + isSubscribeForNewsletterCalled = true; + }; + const postInstallCommand = testInjector.resolveCommand("post-install-cli"); + + await postInstallCommand.execute([]); + assert.isTrue(isSubscribeForNewsletterCalled, "post-install-cli command must call subscriptionService.subscribeForNewsletter"); + }); +}); diff --git a/test/post-install.ts b/test/post-install.ts new file mode 100644 index 0000000000..2df08117b8 --- /dev/null +++ b/test/post-install.ts @@ -0,0 +1,35 @@ +import { assert } from "chai"; + +// Use require instead of import in order to replace the `spawn` method of child_process +let childProcess = require("child_process"); + +import { SpawnOptions, ChildProcess } from "child_process"; +import * as path from "path"; +import { POST_INSTALL_COMMAND_NAME } from "../lib/constants"; + +describe("postinstall.js", () => { + it("calls post-install-cli command of CLI", () => { + const originalSpawn = childProcess.spawn; + let isSpawnCalled = false; + let argsPassedToSpawn: string[] = []; + childProcess.spawn = (command: string, args?: string[], options?: SpawnOptions): ChildProcess => { + isSpawnCalled = true; + argsPassedToSpawn = args; + + return null; + }; + + require(path.join(__dirname, "..", "postinstall")); + + childProcess.spawn = originalSpawn; + + assert.isTrue(isSpawnCalled, "child_process.spawn must be called from postinstall.js"); + + const expectedPathToCliExecutable = path.join(__dirname, "..", "bin", "tns"); + + assert.isTrue(argsPassedToSpawn.indexOf(expectedPathToCliExecutable) !== -1, `The spawned args must contain path to TNS. + Expected path is: ${expectedPathToCliExecutable}, current args are: ${argsPassedToSpawn}.`); + assert.isTrue(argsPassedToSpawn.indexOf(POST_INSTALL_COMMAND_NAME) !== -1, `The spawned args must contain the name of the post-install command. + Expected path is: ${expectedPathToCliExecutable}, current args are: ${argsPassedToSpawn}.`); + }); +}); diff --git a/test/services/subscription-service.ts b/test/services/subscription-service.ts new file mode 100644 index 0000000000..da01584f13 --- /dev/null +++ b/test/services/subscription-service.ts @@ -0,0 +1,286 @@ +import { Yok } from "../../lib/common/yok"; +import { assert } from "chai"; +import { SubscriptionService } from "../../lib/services/subscription-service"; +import { LoggerStub } from "../stubs"; +import { stringify } from "querystring"; +let helpers = require("../../lib/common/helpers"); + +interface IValidateTestData { + name: string; + valuePassedToValidate: string; + expectedResult: boolean | string; +}; + +const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("logger", LoggerStub); + + testInjector.register("userSettingsService", { + getSettingValue: async (value: string) => true, + saveSetting: async (key: string, value: any): Promise => undefined + }); + + testInjector.register("prompter", { + get: async (schemas: IPromptSchema[]): Promise => ({ + inputEmail: "SomeEmail" + }) + }); + + testInjector.register("httpClient", { + httpRequest: async (options: any, proxySettings?: IProxySettings): Promise => undefined + }); + + return testInjector; +}; + +class SubscriptionServiceTester extends SubscriptionService { + public shouldAskForEmailResult: boolean = null; + + constructor($httpClient: Server.IHttpClient, + $prompter: IPrompter, + $userSettingsService: IUserSettingsService, + $logger: ILogger) { + super($httpClient, $prompter, $userSettingsService, $logger); + } + + public async shouldAskForEmail(): Promise { + if (this.shouldAskForEmailResult !== null) { + return this.shouldAskForEmailResult; + } + + return super.shouldAskForEmail(); + } +} + +describe("subscriptionService", () => { + describe("shouldAskForEmail", () => { + describe("returns false", () => { + it("when terminal is not interactive", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => false; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + + assert.isFalse(shouldAskForEmailResult, "When console is not interactive, we should not ask for email."); + }); + + it("when environment variable CLI_NOPROMPT is set to 1", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "1"; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isFalse(shouldAskForEmailResult, "When the environment variable CLI_NOPROMPT is set to 1, we should not ask for email."); + }); + + it("when user had already been asked for mail", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "random_value"; + + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isFalse(shouldAskForEmailResult, "When the user had already been asked for mail, we should not ask for email."); + }); + }); + + describe("returns true", () => { + it("when console is interactive, CLI_NOPROMPT is not 1 and we have not asked user before that", async () => { + const originalIsInteractive = helpers.isInteractive; + helpers.isInteractive = () => true; + + const originalCliNoPrompt = process.env.CLI_NOPROMPT; + process.env.CLI_NOPROMPT = "random_value"; + + const testInjector = createTestInjector(); + const userSettingsService = testInjector.resolve("userSettingsService"); + userSettingsService.getSettingValue = async (settingName: string): Promise => false; + + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + const shouldAskForEmailResult = await subscriptionService.shouldAskForEmail(); + + helpers.isInteractive = originalIsInteractive; + process.env.CLI_NOPROMPT = originalCliNoPrompt; + + assert.isTrue(shouldAskForEmailResult, "We should ask the user for email when console is interactiv, CLI_NOPROMPT is not 1 and we have never asked the user before."); + }); + }); + }); + + describe("subscribeForNewsletter", () => { + it("does nothing when shouldAskForEmail returns false", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = false; + const logger = testInjector.resolve("logger"); + let loggerOutput = ""; + logger.out = (...args: string[]): void => { + loggerOutput += args.join(" "); + }; + + await subscriptionService.subscribeForNewsletter(); + assert.deepEqual(loggerOutput, ""); + }); + + it("shows message that asks for e-mail address", async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + + const logger = testInjector.resolve("logger"); + let loggerOutput = ""; + + logger.out = (...args: string[]): void => { + loggerOutput += args.join(" "); + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.equal(loggerOutput, "Leave your e-mail address here to subscribe for NativeScript newsletter and product updates, tips and tricks:"); + }); + + const expectedMessageInPrompter = "(press Enter for blank)"; + it(`calls prompter with specific message - ${expectedMessageInPrompter}`, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + + return { inputEmail: "SomeEmail" }; + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.isNotNull(schemasPassedToPromter, "Prompter should have been called."); + assert.equal(schemasPassedToPromter.length, 1, "A single schema should have been passed to schemas."); + + assert.equal(schemasPassedToPromter[0].message, expectedMessageInPrompter); + }); + + describe("calls prompter with validate method", () => { + const testData: IValidateTestData[] = [ + { + name: "returning true when empty string is passed", + valuePassedToValidate: "", + expectedResult: true + }, + { + name: "returning true when passing valid email", + valuePassedToValidate: "abc@def.gh", + expectedResult: true + }, + { + name: "returning specific message when invalid email is passed", + valuePassedToValidate: "abcdef.gh", + expectedResult: "Please provide a valid e-mail or simply leave it blank." + } + ]; + + _.each(testData, testCase => { + it(testCase.name, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + return { inputEmail: "SomeEmail" }; + }; + + await subscriptionService.subscribeForNewsletter(); + + const schemaPassedToPromter = schemasPassedToPromter[0]; + const resultOfValidateMethod = schemaPassedToPromter.validate(testCase.valuePassedToValidate); + assert.equal(resultOfValidateMethod, testCase.expectedResult); + }); + }); + + }); + + const emailRegisteredKey = "EMAIL_REGISTERED"; + it(`persists ${emailRegisteredKey} setting with value true in user settings`, async () => { + const testInjector = createTestInjector(); + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + const userSettingsService = testInjector.resolve("userSettingsService"); + let keyPassedToSaveSetting: string = null; + let valuePassedToSaveSetting: boolean = null; + userSettingsService.saveSetting = async (key: string, value: any): Promise => { + keyPassedToSaveSetting = key; + valuePassedToSaveSetting = value; + }; + + await subscriptionService.subscribeForNewsletter(); + + assert.deepEqual(keyPassedToSaveSetting, emailRegisteredKey); + assert.deepEqual(valuePassedToSaveSetting, true); + }); + + it("calls httpRequest with concrete data", async () => { + const email = "abc@def.gh"; + + const postData = stringify({ + 'elqFormName': "dev_uins_cli", + 'elqSiteID': '1325', + 'emailAddress': email, + 'elqCookieWrite': '0' + }); + + const expectedOptions = { + url: 'https://s1325.t.eloqua.com/e/f2', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': postData.length + }, + body: postData + }; + + const testInjector = createTestInjector(); + + const prompter = testInjector.resolve("prompter"); + let schemasPassedToPromter: IPromptSchema[] = null; + prompter.get = async (schemas: IPromptSchema[]): Promise => { + schemasPassedToPromter = schemas; + return { inputEmail: email }; + }; + + const httpClient = testInjector.resolve("httpClient"); + let optionsPassedToHttpRequest: any = null; + httpClient.httpRequest = async (options: any, proxySettings?: IProxySettings): Promise => { + optionsPassedToHttpRequest = options; + return null; + }; + + const subscriptionService = testInjector.resolve(SubscriptionServiceTester); + subscriptionService.shouldAskForEmailResult = true; + + await subscriptionService.subscribeForNewsletter(); + + assert.deepEqual(optionsPassedToHttpRequest, expectedOptions); + }); + }); +});