diff --git a/lib/common b/lib/common
index c9fb84ccb9..62f64d02db 160000
--- a/lib/common
+++ b/lib/common
@@ -1 +1 @@
-Subproject commit c9fb84ccb92eb57f335b8005abcd54ee9f1ab3cf
+Subproject commit 62f64d02db5363f8d464222559ed73aad9b8861d
diff --git a/lib/constants.ts b/lib/constants.ts
index d2824b15e2..73ffa7610c 100644
--- a/lib/constants.ts
+++ b/lib/constants.ts
@@ -14,6 +14,7 @@ export let DEFAULT_APP_IDENTIFIER_PREFIX = "org.nativescript";
export var LIVESYNC_EXCLUDED_DIRECTORIES = ["app_resources"];
export var TESTING_FRAMEWORKS = ['jasmine', 'mocha', 'qunit'];
export let TEST_RUNNER_NAME = "nativescript-unit-test-runner";
+export let LIVESYNC_EXCLUDED_FILE_PATTERNS = ["**/*.js.map", "**/*.ts"];
export class ReleaseType {
static MAJOR = "major";
diff --git a/lib/declarations.ts b/lib/declarations.ts
index 3b19fa6252..7616b92968 100644
--- a/lib/declarations.ts
+++ b/lib/declarations.ts
@@ -65,7 +65,6 @@ interface IOptions extends ICommonOptions {
frameworkVersion: string;
copyFrom: string;
linkTo: string;
- release: boolean;
emulator: boolean;
symlink: boolean;
forDevice: boolean;
diff --git a/lib/options.ts b/lib/options.ts
index ace6a35ef9..17a58c567c 100644
--- a/lib/options.ts
+++ b/lib/options.ts
@@ -18,7 +18,6 @@ export class Options extends commonOptionsLibPath.OptionsBase {
frameworkVersion: { type: OptionType.String },
copyFrom: { type: OptionType.String },
linkTo: { type: OptionType.String },
- release: { type: OptionType.Boolean },
symlink: { type: OptionType.Boolean },
forDevice: { type: OptionType.Boolean },
client: { type: OptionType.Boolean, default: true},
diff --git a/lib/providers/project-files-provider.ts b/lib/providers/project-files-provider.ts
index 78ab3af109..ff5159c924 100644
--- a/lib/providers/project-files-provider.ts
+++ b/lib/providers/project-files-provider.ts
@@ -4,26 +4,33 @@
import minimatch = require("minimatch");
import * as constants from "../constants";
import * as path from "path";
+import { ProjectFilesProviderBase } from "../common/services/project-files-provider-base";
-export class ProjectFilesProvider implements IProjectFilesProvider {
+export class ProjectFilesProvider extends ProjectFilesProviderBase {
constructor(private $platformsData: IPlatformsData,
- private $projectData: IProjectData) { }
+ private $projectData: IProjectData,
+ $mobileHelper: Mobile.IMobileHelper,
+ $options:IOptions) {
+ super($mobileHelper, $options);
+ }
private static INTERNAL_NONPROJECT_FILES = [ "**/*.ts" ];
public mapFilePath(filePath: string, platform: string): string {
let platformData = this.$platformsData.getPlatformData(platform.toLowerCase());
let projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
- let mappedFilePath = path.join(projectFilesPath, path.relative(path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME), filePath));
+ let parsedFilePath = this.getPreparedFilePath(filePath);
+ let mappedFilePath = path.join(projectFilesPath, path.relative(path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME), parsedFilePath));
let appResourcesDirectoryPath = path.join(constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME);
let platformSpecificAppResourcesDirectoryPath = path.join(appResourcesDirectoryPath, platformData.normalizedPlatformName);
- if (filePath.indexOf(appResourcesDirectoryPath) > -1 && filePath.indexOf(platformSpecificAppResourcesDirectoryPath) === -1) {
+ if (parsedFilePath.indexOf(appResourcesDirectoryPath) > -1 && parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) === -1) {
return null;
}
- if (filePath.indexOf(platformSpecificAppResourcesDirectoryPath) > -1) {
+
+ if (parsedFilePath.indexOf(platformSpecificAppResourcesDirectoryPath) > -1) {
let appResourcesRelativePath = path.relative(path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME, constants.APP_RESOURCES_FOLDER_NAME,
- platformData.normalizedPlatformName), filePath);
+ platformData.normalizedPlatformName), parsedFilePath);
mappedFilePath = path.join(platformData.platformProjectService.getAppResourcesDestinationDirectoryPath().wait(), appResourcesRelativePath);
}
diff --git a/lib/services/karma-execution.ts b/lib/services/karma-execution.ts
new file mode 100644
index 0000000000..23ba20f8d1
--- /dev/null
+++ b/lib/services/karma-execution.ts
@@ -0,0 +1,15 @@
+///
+
+"use strict";
+
+import * as path from "path";
+
+process.on("message", (data: any) => {
+ if(data.karmaConfig) {
+ let pathToKarma = path.join(data.karmaConfig.projectDir, 'node_modules/karma'),
+ KarmaServer = require(path.join(pathToKarma, 'lib/server')),
+ karma = new KarmaServer(data.karmaConfig);
+
+ karma.start();
+ }
+});
diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts
index 6a81215557..9795c809b7 100644
--- a/lib/services/livesync/livesync-service.ts
+++ b/lib/services/livesync/livesync-service.ts
@@ -58,7 +58,7 @@ class LiveSyncService implements ILiveSyncService {
appIdentifier: this.$projectData.projectId,
projectFilesPath: path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME),
syncWorkingDirectory: path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME),
- excludedProjectDirsAndFiles: ["**/*.js.map", "**/*.ts"]
+ excludedProjectDirsAndFiles: constants.LIVESYNC_EXCLUDED_FILE_PATTERNS
};
this.$liveSyncServiceBase.sync(liveSyncData).wait();
}).future()();
diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts
index b7d2b33c2e..abdbcdafcc 100644
--- a/lib/services/platform-service.ts
+++ b/lib/services/platform-service.ts
@@ -11,11 +11,6 @@ import Future = require("fibers/future");
let clui = require("clui");
export class PlatformService implements IPlatformService {
- private static TNS_MODULES_FOLDER_NAME = "tns_modules";
- private static EXCLUDE_FILES_PATTERN = [
- "**/*.js.map",
- "**/*.ts"
- ];
constructor(private $devicesService: Mobile.IDevicesService,
private $errors: IErrors,
@@ -226,7 +221,7 @@ export class PlatformService implements IPlatformService {
this.$xmlValidator.validateXmlFiles(sourceFiles).wait();
// Remove .ts and .js.map files
- PlatformService.EXCLUDE_FILES_PATTERN.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, {nocase: true})));
+ constants.LIVESYNC_EXCLUDED_FILE_PATTERNS.forEach(pattern => sourceFiles = sourceFiles.filter(file => !minimatch(file, pattern, {nocase: true})));
let copyFileFutures = sourceFiles.map(source => {
let destinationPath = path.join(appDestinationDirectoryPath, path.relative(appSourceDirectoryPath, source));
if (this.$fs.getFsStats(source).wait().isDirectory()) {
@@ -250,7 +245,7 @@ export class PlatformService implements IPlatformService {
// Process node_modules folder
let appDir = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
try {
- let tnsModulesDestinationPath = path.join(appDir, PlatformService.TNS_MODULES_FOLDER_NAME);
+ let tnsModulesDestinationPath = path.join(appDir, constants.TNS_MODULES_FOLDER_NAME);
this.$broccoliBuilder.prepareNodeModules(tnsModulesDestinationPath, platform, lastModifiedTime).wait();
} catch(error) {
this.$logger.debug(error);
diff --git a/lib/services/test-execution-service.ts b/lib/services/test-execution-service.ts
index 254d6f5eb5..a782816ac3 100644
--- a/lib/services/test-execution-service.ts
+++ b/lib/services/test-execution-service.ts
@@ -32,11 +32,17 @@ class TestExecutionService implements ITestExecutionService {
private $options: IOptions,
private $pluginsService: IPluginsService,
private $errors: IErrors,
- private $devicesService: Mobile.IDevicesService) {
+ private $androidDebugService:IDebugService,
+ private $iOSDebugService: IDebugService,
+ private $devicesService: Mobile.IDevicesService,
+ private $childProcess: IChildProcess) {
}
+ public platform: string;
+
public startTestRunner(platform: string) : IFuture {
return (() => {
+ this.platform = platform;
this.$options.justlaunch = true;
let blockingOperationFuture = new Future();
process.on('message', (launcherConfig: any) => {
@@ -50,7 +56,7 @@ class TestExecutionService implements ITestExecutionService {
let configOptions: IKarmaConfigOptions = JSON.parse(launcherConfig);
this.$options.debugBrk = configOptions.debugBrk;
this.$options.debugTransport = configOptions.debugTransport;
- let configJs = this.generateConfig(configOptions);
+ let configJs = this.generateConfig(this.$options.port.toString(), configOptions);
this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();
let socketIoJsUrl = `http://localhost:${this.$options.port}/socket.io/socket.io.js`;
@@ -93,37 +99,47 @@ class TestExecutionService implements ITestExecutionService {
public startKarmaServer(platform: string): IFuture {
return (() => {
platform = platform.toLowerCase();
- this.$pluginsService.ensureAllDependenciesAreInstalled().wait();
- let pathToKarma = path.join(this.$projectData.projectDir, 'node_modules/karma');
- let KarmaServer = require(path.join(pathToKarma, 'lib/server'));
- if (platform === 'ios' && this.$options.emulator) {
- platform = 'ios_simulator';
- }
- let karmaConfig: any = {
- browsers: [platform],
- configFile: path.join(this.$projectData.projectDir, 'karma.conf.js'),
- _NS: {
- log: this.$logger.getLevel(),
- path: this.$options.path,
- tns: process.argv[1],
- node: process.execPath,
- options: {
- debugTransport: this.$options.debugTransport,
- debugBrk: this.$options.debugBrk,
- }
- },
- };
- if (this.$config.DEBUG || this.$logger.getLevel() === 'TRACE') {
- karmaConfig.logLevel = 'DEBUG';
- }
- if (!this.$options.watch) {
- karmaConfig.singleRun = true;
+ this.platform = platform;
+
+ if(this.$options.debugBrk && this.$options.watch) {
+ this.$errors.failWithoutHelp("You cannot use --watch and --debug-brk simultaneously. Remove one of the flags and try again.");
}
- if (this.$options.debugBrk) {
- karmaConfig.browserNoActivityTimeout = 1000000000;
+
+ if (!this.$platformService.preparePlatform(platform).wait()) {
+ this.$errors.failWithoutHelp("Verify that listed files are well-formed and try again the operation.");
}
- this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
- new KarmaServer(karmaConfig).start();
+
+ let projectDir = this.$projectData.projectDir;
+ this.$devicesService.initialize({ platform: platform, deviceId: this.$options.device }).wait();
+
+ let karmaConfig = this.getKarmaConfiguration(platform),
+ karmaRunner = this.$childProcess.fork(path.join(__dirname, "karma-execution.js"));
+
+ karmaRunner.send({karmaConfig: karmaConfig});
+ karmaRunner.on("message", (karmaData: any) => {
+ fiberBootstrap.run(() => {
+ this.$logger.trace("## Unit-testing: Parent process received message", karmaData);
+ let port: string;
+ if(karmaData.url) {
+ port = karmaData.url.port;
+ let socketIoJsUrl = `http://${karmaData.url.host}/socket.io/socket.io.js`;
+ let socketIoJs = this.$httpClient.httpRequest(socketIoJsUrl).wait().body;
+ this.$fs.writeFile(path.join(projectDir, TestExecutionService.SOCKETIO_JS_FILE_NAME), socketIoJs).wait();
+ }
+
+ if(karmaData.launcherConfig) {
+ let configOptions: IKarmaConfigOptions = JSON.parse(karmaData.launcherConfig);
+ let configJs = this.generateConfig(port, configOptions);
+ this.$fs.writeFile(path.join(projectDir, TestExecutionService.CONFIG_FILE_NAME), configJs).wait();
+ }
+
+ if(this.$options.debugBrk) {
+ this.getDebugService(platform).debug().wait();
+ } else {
+ this.liveSyncProject(platform).wait();
+ }
+ });
+ });
}).future()();
}
@@ -138,8 +154,7 @@ class TestExecutionService implements ITestExecutionService {
}).future()();
}
- private generateConfig(options: any): string {
- let port = this.$options.port;
+ private generateConfig(port: string, options: any): string {
let nics = os.networkInterfaces();
let ips = Object.keys(nics)
.map(nicName => nics[nicName].filter((binding: any) => binding.family === 'IPv4' && !binding.internal)[0])
@@ -154,5 +169,65 @@ class TestExecutionService implements ITestExecutionService {
return 'module.exports = ' + JSON.stringify(config);
}
+
+ private getDebugService(platform: string): IDebugService {
+ let lowerCasedPlatform = platform.toLowerCase();
+ if(lowerCasedPlatform === this.$devicePlatformsConstants.iOS.toLowerCase()) {
+ return this.$iOSDebugService;
+ } else if(lowerCasedPlatform === this.$devicePlatformsConstants.Android.toLowerCase()) {
+ return this.$androidDebugService;
+ }
+
+ throw new Error(`Invalid platform ${platform}. Valid platforms are ${this.$devicePlatformsConstants.iOS} and ${this.$devicePlatformsConstants.Android}`);
+ }
+
+ private getKarmaConfiguration(platform: string): any {
+ let karmaConfig: any = {
+ browsers: [platform],
+ configFile: path.join(this.$projectData.projectDir, 'karma.conf.js'),
+ _NS: {
+ log: this.$logger.getLevel(),
+ path: this.$options.path,
+ tns: process.argv[1],
+ node: process.execPath,
+ options: {
+ debugTransport: this.$options.debugTransport,
+ debugBrk: this.$options.debugBrk,
+ }
+ },
+ };
+ if (this.$config.DEBUG || this.$logger.getLevel() === 'TRACE') {
+ karmaConfig.logLevel = 'DEBUG';
+ }
+ if (!this.$options.watch) {
+ karmaConfig.singleRun = true;
+ }
+ if (this.$options.debugBrk) {
+ karmaConfig.browserNoActivityTimeout = 1000000000;
+ }
+
+ karmaConfig.projectDir = this.$projectData.projectDir;
+ this.$logger.debug(JSON.stringify(karmaConfig, null, 4));
+
+ return karmaConfig;
+ }
+
+ private liveSyncProject(platform: string): IFuture {
+ return (() => {
+ let platformData = this.$platformsData.getPlatformData(platform.toLowerCase()),
+ projectFilesPath = path.join(platformData.appDestinationDirectoryPath, constants.APP_FOLDER_NAME);
+
+ let liveSyncData: ILiveSyncData = {
+ platform: platform,
+ appIdentifier: this.$projectData.projectId,
+ projectFilesPath: projectFilesPath,
+ syncWorkingDirectory: path.join(this.$projectData.projectDir, constants.APP_FOLDER_NAME),
+ canExecuteFastSync: false, // Always restart the application when change is detected, so tests will be rerun.
+ excludedProjectDirsAndFiles: constants.LIVESYNC_EXCLUDED_FILE_PATTERNS
+ };
+
+ this.$liveSyncServiceBase.sync(liveSyncData).wait();
+ }).future()();
+ }
}
$injector.register('testExecutionService', TestExecutionService);
diff --git a/test/project-files-provider.ts b/test/project-files-provider.ts
new file mode 100644
index 0000000000..b57b81395e
--- /dev/null
+++ b/test/project-files-provider.ts
@@ -0,0 +1,92 @@
+///
+"use strict";
+
+import { Yok } from "../lib/common/yok";
+import { ProjectFilesProvider } from "../lib/providers/project-files-provider";
+import { assert } from "chai";
+import * as path from "path";
+import Future = require("fibers/future");
+
+let projectDir = "projectDir",
+ appDestinationDirectoryPath = "appDestinationDirectoryPath",
+ appResourcesDestinationDirectoryPath = "appResourcesDestinationDirectoryPath",
+ appSourceDir = path.join(projectDir, "app");
+
+function createTestInjector(): IInjector {
+ let testInjector = new Yok();
+ testInjector.register("mobileHelper", {
+ platformNames: ["Android", "iOS"]
+ });
+
+ testInjector.register("platformsData", {
+ getPlatformData: (platform: string) => {
+ return {
+ appDestinationDirectoryPath: appDestinationDirectoryPath,
+ normalizedPlatformName: platform.toLowerCase(),
+ platformProjectService: {
+ getAppResourcesDestinationDirectoryPath: () => Future.fromResult(appResourcesDestinationDirectoryPath)
+ }
+ };
+ },
+ });
+
+ testInjector.register("projectData", {
+ projectDir: projectDir
+ });
+
+ testInjector.register("options", { release: false });
+
+ return testInjector;
+}
+
+describe("project-files-provider", () => {
+ let testInjector: IInjector,
+ projectFilesProvider: IProjectFilesProvider;
+
+ beforeEach(() => {
+ testInjector = createTestInjector();
+ projectFilesProvider = testInjector.resolve(ProjectFilesProvider);
+ });
+
+ describe("isFileExcluded", () => {
+ it("returns true for .ts files", () => {
+ assert.isTrue(projectFilesProvider.isFileExcluded("test.ts"));
+ });
+
+ it("returns false for .js files", () => {
+ assert.isFalse(projectFilesProvider.isFileExcluded("test.js"));
+ });
+ });
+
+ describe("mapFilePath", () => {
+ it("returns file path from prepared project when path from app dir is passed", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.js"), "android");
+ assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js"));
+ });
+
+ it("returns file path from prepared project when path from app/App_Resources/platform dir is passed", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "android");
+ assert.deepEqual(mappedFilePath, path.join(appResourcesDestinationDirectoryPath, "test.js"));
+ });
+
+ it("returns null when path from app/App_Resources/android dir is passed and iOS platform is specified", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "android", "test.js"), "iOS");
+ assert.deepEqual(mappedFilePath, null);
+ });
+
+ it("returns null when path from app/App_Resources/ dir (not platform specific) is passed", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "App_Resources", "test.js"), "android");
+ assert.deepEqual(mappedFilePath, null);
+ });
+
+ it("returns file path from prepared project when path from app dir is passed and it contains platform in its name", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.android.js"), "android");
+ assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js"));
+ });
+
+ it("returns file path from prepared project when path from app dir is passed and it contains configuration in its name", () => {
+ let mappedFilePath = projectFilesProvider.mapFilePath(path.join(appSourceDir, "test.debug.js"), "android");
+ assert.deepEqual(mappedFilePath, path.join(appDestinationDirectoryPath, "app", "test.js"));
+ });
+ });
+});