// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for details. import { EventEmitter } from "events"; import assert = require("assert"); import * as path from "path"; import * as fs from "fs"; import * as semver from "semver"; import * as sinon from "sinon"; import { CommandExecutor } from "../../src/common/commandExecutor"; import { ConsoleLogger } from "../../src/extension/log/ConsoleLogger"; import { Node } from "../../src/common/node/node"; import { ChildProcess } from "../../src/common/node/childProcess"; import { Crypto } from "../../src/common/node/crypto"; import { AppLauncher } from "../../src/extension/appLauncher"; import { HostPlatform } from "../../src/common/hostPlatform"; // import { HostPlatform } from "../../src/common/hostPlatform"; suite("commandExecutor", function () { suite("extensionContext", function () { const childProcessStubInstance = new ChildProcess(); let childProcessStub: Sinon.SinonStub & ChildProcess; let appLauncherStub: Sinon.SinonStub; const Log = new ConsoleLogger(); const sampleReactNativeProjectDir = path.join( __dirname, "..", "resources", "sampleReactNativeProject", ); let nodeModulesRoot: string; teardown(function () { const mockedMethods = [Log.log, ...Object.keys(childProcessStubInstance)]; mockedMethods.forEach(method => { if (method.hasOwnProperty("restore")) { (method).restore(); } }); childProcessStub.restore(); appLauncherStub.restore(); }); setup(() => { childProcessStub = sinon .stub(Node, "ChildProcess") .returns(childProcessStubInstance) as ChildProcess & Sinon.SinonStub; appLauncherStub = sinon.stub( AppLauncher, "getNodeModulesRootByProjectPath", (projectRoot: string) => nodeModulesRoot, ); nodeModulesRoot = sampleReactNativeProjectDir; }); test("should execute a command", async function () { const ce = new CommandExecutor(nodeModulesRoot, process.cwd(), Log); let loggedOutput = ""; sinon.stub(Log, "log", function (message: string, formatMessage: boolean = true) { loggedOutput += semver.clean(message) || ""; console.log(message); }); await ce.execute("node -v"); assert(loggedOutput); }).timeout(10000); test("should reject on bad command", async () => { const ce = new CommandExecutor(nodeModulesRoot); try { await ce.execute("bar"); assert.fail(null, null, "bar should not be a valid command"); } catch (reason) { console.log((reason as Error).message); assert.strictEqual((reason as any).errorCode, 101); assert.strictEqual((reason as any).errorLevel, 0); } }).timeout(10000); test("should reject on good command that fails", async () => { const ce = new CommandExecutor(nodeModulesRoot); try { await ce.execute("node install bad-package"); assert.fail(null, null, "node should not be able to install bad-package"); } catch (reason) { console.log((reason as Error).message); assert.strictEqual((reason as any).errorCode, 101); assert.strictEqual((reason as any).errorLevel, 0); } }).timeout(10000); test("should spawn a command", async () => { const ce = new CommandExecutor(nodeModulesRoot); sinon.stub(Log, "log", function (message: string, formatMessage: boolean = true) { console.log(message); }); return await ce.spawn("node", ["-v"]); }).timeout(10000); test("spawn should reject a bad command", async () => { const ce = new CommandExecutor(nodeModulesRoot); sinon.stub(Log, "log", function (message: string, formatMessage: boolean = true) { console.log(message); }); try { return await ce.spawn("bar", ["-v"]); } catch (reason) { console.log((reason as Error).message); assert.strictEqual((reason as any).errorCode, 101); assert.strictEqual((reason as any).errorLevel, 0); } }).timeout(10000); test("should return correct CLI react command", async () => { const ce = new CommandExecutor(nodeModulesRoot); // const expected = // "test\\resources\\sampleReactNativeProject\\node_modules\\.bin\react-native.cmd"; const command = HostPlatform.getNpmCliCommand(ce.selectReactNativeCLI()); assert.ok(command.includes("react-native")); }).timeout(10000); test("should return correct CLI Expo command", async () => { const ce = new CommandExecutor(nodeModulesRoot); // const expected = // "test\\resources\\sampleReactNativeProject\\node_modules\\.bin\\expo.cmd"; const command = HostPlatform.getNpmCliCommand(ce.selectExpoCLI()); assert.ok(command.includes("expo")); }).timeout(10000); test("should not fail on react-native command without arguments", async () => { (sinon.stub(childProcessStubInstance, "spawn") as Sinon.SinonStub).returns({ stdout: new EventEmitter(), stderr: new EventEmitter(), outcome: Promise.resolve(), }); try { await new CommandExecutor(nodeModulesRoot).spawnReactCommand("run-ios").outcome; } catch (error) { assert.fail("react-native command was not expected to fail"); } }).timeout(10000); suite("getReactNativeVersion", () => { const reactNativePackageDir = path.join( sampleReactNativeProjectDir, "node_modules", "react-native", ); const fsHelper = new Node.FileSystem(); suiteSetup(() => { fsHelper.makeDirectoryRecursiveSync(reactNativePackageDir); }); suiteTeardown(() => { fsHelper.removePathRecursivelySync( path.join(sampleReactNativeProjectDir, "node_modules"), ); }); test("getReactNativeVersion should return version string if 'version' field is found in react-native package package.json file from node_modules", async () => { const commandExecutor: CommandExecutor = new CommandExecutor( nodeModulesRoot, sampleReactNativeProjectDir, ); const versionObj = { version: "0.65.0", }; fs.writeFileSync( path.join(reactNativePackageDir, "package.json"), JSON.stringify(versionObj, null, 2), ); const version = await commandExecutor.getReactNativeVersion(); assert.strictEqual(version, "0.65.0"); }); test("getReactNativeVersion should return version string if there isn't 'version' field in react-native package's package.json file", async () => { const commandExecutor: CommandExecutor = new CommandExecutor( nodeModulesRoot, sampleReactNativeProjectDir, ); const testObj = { test: "test", }; fs.writeFileSync( path.join(reactNativePackageDir, "package.json"), JSON.stringify(testObj, null, 2), ); const version = await commandExecutor.getReactNativeVersion(); assert.strictEqual(version, "0.65.0"); }); }); suite("ReactNativeClIApproaches", function () { const RNGlobalCLINameContent: any = { "react-native-tools.reactNativeGlobalCommandName": "", }; test("selectReactNativeCLI should return local CLI", (done: Mocha.Done) => { const localCLIPath = path.join( sampleReactNativeProjectDir, "node_modules", ".bin", "react-native", ); const commandExecutor: CommandExecutor = new CommandExecutor( nodeModulesRoot, sampleReactNativeProjectDir, ); CommandExecutor.ReactNativeCommand = RNGlobalCLINameContent["react-native-tools.reactNativeGlobalCommandName"]; assert.strictEqual(commandExecutor.selectReactNativeCLI(), localCLIPath); done(); }); test("selectReactNativeCLI should return global CLI", (done: Mocha.Done) => { const randomHash = new Crypto().hash(Math.random().toString(36).substring(2, 15)); RNGlobalCLINameContent["react-native-tools.reactNativeGlobalCommandName"] = randomHash; const commandExecutor: CommandExecutor = new CommandExecutor( nodeModulesRoot, sampleReactNativeProjectDir, ); CommandExecutor.ReactNativeCommand = RNGlobalCLINameContent["react-native-tools.reactNativeGlobalCommandName"]; assert.strictEqual(commandExecutor.selectReactNativeCLI(), randomHash); done(); }); }); }); });