diff --git a/lib/common/declarations.d.ts b/lib/common/declarations.d.ts index cd9c9e5b94..a132c8d25b 100644 --- a/lib/common/declarations.d.ts +++ b/lib/common/declarations.d.ts @@ -1168,6 +1168,13 @@ interface IDoctorService { * @returns {Promise} true if the environment is properly configured for local builds */ canExecuteLocalBuild(platform?: string, projectDir?: string, runtimeVersion?: string): Promise; + + /** + * Checks and notifies users for deprecated short imports in their applications. + * @param {string} projectDir Path to the application. + * @returns {void} + */ + checkForDeprecatedShortImportsInAppDir(projectDir: string): void; } interface IUtils { diff --git a/lib/common/helpers.ts b/lib/common/helpers.ts index 1152026caf..29089ee08f 100644 --- a/lib/common/helpers.ts +++ b/lib/common/helpers.ts @@ -8,6 +8,12 @@ import * as crypto from "crypto"; import * as _ from "lodash"; const Table = require("cli-table"); +const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; + +export function stripComments(content: string): string { + const newContent = content.replace(STRIP_COMMENTS, ""); + return newContent; +} export function doesCurrentNpmCommandMatch(patterns?: RegExp[]): boolean { const currentNpmCommandArgv = getCurrentNpmCommandArgv(); @@ -682,7 +688,6 @@ const CONSTRUCTOR_ARGS = /constructor\s*([^\(]*)\(\s*([^\)]*)\)/m; const FN_NAME_AND_ARGS = /^(?:function)?\s*([^\(]*)\(\s*([^\)]*)\)\s*(=>)?\s*[{_]/m; const FN_ARG_SPLIT = /,/; const FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; -const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; export function annotate(fn: any) { let $inject: any, diff --git a/lib/common/test/unit-tests/helpers.ts b/lib/common/test/unit-tests/helpers.ts index b7f3a1f34e..f21c1db2ec 100644 --- a/lib/common/test/unit-tests/helpers.ts +++ b/lib/common/test/unit-tests/helpers.ts @@ -825,4 +825,40 @@ describe("helpers", () => { }); }); }); + + describe("stripComments", () => { + const testData: ITestData[] = [ + { + input: `// this is comment, +const test = require("./test");`, + expectedResult: `\nconst test = require("./test");` + }, + { + input: `/* this is multiline +comment */ +const test = require("./test");`, + expectedResult: `\nconst test = require("./test");` + }, + { + input: `/* this is multiline +comment +// with inner one line comment inside it +the multiline comment finishes here +*/ +const test = require("./test");`, + expectedResult: `\nconst test = require("./test");` + }, + + { + input: `const test /*inline comment*/ = require("./test");`, + expectedResult: `const test = require("./test");` + }, + ]; + + it("strips comments correctly", () => { + testData.forEach(testCase => { + assertTestData(testCase, helpers.stripComments); + }); + }); + }); }); diff --git a/lib/constants.ts b/lib/constants.ts index 2e82a9c2ea..ffc869fcba 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -116,6 +116,12 @@ export const NgFlavorName = "Angular"; export const VueFlavorName = "Vue.js"; export const TsFlavorName = "Plain TypeScript"; export const JsFlavorName = "Plain JavaScript"; +export class ProjectTypes { + public static NgFlavorName = NgFlavorName; + public static VueFlavorName = VueFlavorName; + public static TsFlavorName = "Pure TypeScript"; + public static JsFlavorName = "Pure JavaScript"; +} export const BUILD_OUTPUT_EVENT_NAME = "buildOutput"; export const CONNECTION_ERROR_EVENT_NAME = "connectionError"; export const USER_INTERACTION_NEEDED_EVENT_NAME = "userInteractionNeeded"; diff --git a/lib/definitions/project.d.ts b/lib/definitions/project.d.ts index 4af5382abc..017253cbfc 100644 --- a/lib/definitions/project.d.ts +++ b/lib/definitions/project.d.ts @@ -172,6 +172,13 @@ interface IProjectDataService { * @returns {Promise} An object describing the current asset structure for Android. */ getAndroidAssetsStructure(opts: IProjectDir): Promise; + + /** + * Returns array with paths to all `.js` or `.ts` files in application's app directory. + * @param {string} projectDir Path to application. + * @returns {string[]} Array of paths to `.js` or `.ts` files. + */ + getAppExecutableFiles(projectDir: string): string[]; } interface IAssetItem { @@ -535,9 +542,9 @@ interface ICocoaPodsService { /** * Merges pod's xcconfig file into project's xcconfig file - * @param projectData - * @param platformData - * @param opts + * @param projectData + * @param platformData + * @param opts */ mergePodXcconfigFile(projectData: IProjectData, platformData: IPlatformData, opts: IRelease): Promise; } diff --git a/lib/project-data.ts b/lib/project-data.ts index 2fe31b0f2f..07a73475f8 100644 --- a/lib/project-data.ts +++ b/lib/project-data.ts @@ -16,19 +16,19 @@ export class ProjectData implements IProjectData { */ private static PROJECT_TYPES: IProjectType[] = [ { - type: "Pure JavaScript", + type: constants.ProjectTypes.JsFlavorName, isDefaultProjectType: true }, { - type: constants.NgFlavorName, + type: constants.ProjectTypes.NgFlavorName, requiredDependencies: ["@angular/core", "nativescript-angular"] }, { - type: constants.VueFlavorName, + type: constants.ProjectTypes.VueFlavorName, requiredDependencies: ["nativescript-vue"] }, { - type: "Pure TypeScript", + type: constants.ProjectTypes.TsFlavorName, requiredDependencies: ["typescript", "nativescript-dev-typescript"] } ]; diff --git a/lib/services/doctor-service.ts b/lib/services/doctor-service.ts index 4e93ff0124..0e9d6bae5e 100644 --- a/lib/services/doctor-service.ts +++ b/lib/services/doctor-service.ts @@ -1,10 +1,10 @@ import { EOL } from "os"; import * as path from "path"; import * as helpers from "../common/helpers"; -import { TrackActionNames } from "../constants"; +import { TrackActionNames, NODE_MODULES_FOLDER_NAME, TNS_CORE_MODULES_NAME } from "../constants"; import { doctor, constants } from "nativescript-doctor"; -class DoctorService implements IDoctorService { +export class DoctorService implements IDoctorService { private static DarwinSetupScriptLocation = path.join(__dirname, "..", "..", "setup", "mac-startup-shell-script.sh"); private static WindowsSetupScriptExecutable = "powershell.exe"; private static WindowsSetupScriptArguments = ["start-process", "-FilePath", "PowerShell.exe", "-NoNewWindow", "-Wait", "-ArgumentList", '"-NoProfile -ExecutionPolicy Bypass -Command iex ((new-object net.webclient).DownloadString(\'https://www.nativescript.org/setup/win\'))"']; @@ -14,10 +14,12 @@ class DoctorService implements IDoctorService { private $logger: ILogger, private $childProcess: IChildProcess, private $injector: IInjector, + private $projectDataService: IProjectDataService, + private $fs: IFileSystem, private $terminalSpinnerService: ITerminalSpinnerService, private $versionsService: IVersionsService) { } - public async printWarnings(configOptions?: { trackResult: boolean , projectDir?: string, runtimeVersion?: string, options?: IOptions }): Promise { + public async printWarnings(configOptions?: { trackResult: boolean, projectDir?: string, runtimeVersion?: string, options?: IOptions }): Promise { const infos = await this.$terminalSpinnerService.execute({ text: `Getting environment information ${EOL}` }, () => doctor.getInfos({ projectDir: configOptions && configOptions.projectDir, androidRuntimeVersion: configOptions && configOptions.runtimeVersion })); @@ -48,6 +50,8 @@ class DoctorService implements IDoctorService { this.$logger.error("Cannot get the latest versions information from npm. Please try again later."); } + this.checkForDeprecatedShortImportsInAppDir(configOptions.projectDir); + await this.$injector.resolve("platformEnvironmentRequirements").checkEnvironmentRequirements({ platform: null, projectDir: configOptions && configOptions.projectDir, @@ -113,6 +117,59 @@ class DoctorService implements IDoctorService { return !hasWarnings; } + public checkForDeprecatedShortImportsInAppDir(projectDir: string): void { + if (projectDir) { + try { + const files = this.$projectDataService.getAppExecutableFiles(projectDir); + const shortImports = this.getDeprecatedShortImportsInFiles(files, projectDir); + if (shortImports.length) { + this.$logger.printMarkdown("Detected short imports in your application. Please note that `short imports are deprecated` since NativeScript 5.2.0. More information can be found in this blogpost https://www.nativescript.org/blog/say-goodbye-to-short-imports-in-nativescript"); + shortImports.forEach(shortImport => { + this.$logger.printMarkdown(`In file \`${shortImport.file}\` line \`${shortImport.line}\` is short import. Add \`tns-core-modules/\` in front of the required/imported module.`); + }); + } + } catch (err) { + this.$logger.trace(`Unable to validate if project has short imports. Error is`, err); + } + } + } + + protected getDeprecatedShortImportsInFiles(files: string[], projectDir: string): { file: string, line: string }[] { + const shortImportRegExps = this.getShortImportRegExps(projectDir); + const shortImports: { file: string, line: string }[] = []; + + for (const file of files) { + const fileContent = this.$fs.readText(file); + const strippedComments = helpers.stripComments(fileContent); + const linesWithRequireStatements = strippedComments + .split(/\r?\n/) + .filter(line => /\btns-core-modules\b/.exec(line) === null && (/\bimport\b/.exec(line) || /\brequire\b/.exec(line))); + + for (const line of linesWithRequireStatements) { + for (const regExp of shortImportRegExps) { + const matches = line.match(regExp); + + if (matches && matches.length) { + shortImports.push({ file, line }); + break; + } + } + } + } + + return shortImports; + } + + private getShortImportRegExps(projectDir: string): RegExp[] { + const pathToTnsCoreModules = path.join(projectDir, NODE_MODULES_FOLDER_NAME, TNS_CORE_MODULES_NAME); + const contents = this.$fs.readDirectory(pathToTnsCoreModules) + .filter(entry => this.$fs.getFsStats(path.join(pathToTnsCoreModules, entry)).isDirectory()); + + const regExps = contents.map(c => new RegExp(`[\"\']${c}[\"\'/]`, "g")); + + return regExps; + } + private async runSetupScriptCore(executablePath: string, setupScriptArgs: string[]): Promise { return this.$childProcess.spawnFromEvent(executablePath, setupScriptArgs, "close", { stdio: "inherit" }); } diff --git a/lib/services/platform-service.ts b/lib/services/platform-service.ts index eda6449b9f..58cf1ea65d 100644 --- a/lib/services/platform-service.ts +++ b/lib/services/platform-service.ts @@ -27,6 +27,7 @@ export class PlatformService extends EventEmitter implements IPlatformService { private $errors: IErrors, private $fs: IFileSystem, private $logger: ILogger, + private $doctorService: IDoctorService, private $packageInstallationManager: IPackageInstallationManager, private $platformsData: IPlatformsData, private $projectDataService: IProjectDataService, @@ -237,6 +238,8 @@ export class PlatformService extends EventEmitter implements IPlatformService { await this.cleanDestinationApp(platformInfo); } + this.$doctorService.checkForDeprecatedShortImportsInAppDir(platformInfo.projectData.projectDir); + await this.preparePlatformCore( platformInfo.platform, platformInfo.appFilesUpdaterOptions, diff --git a/lib/services/project-data-service.ts b/lib/services/project-data-service.ts index de5e873d44..65578062ee 100644 --- a/lib/services/project-data-service.ts +++ b/lib/services/project-data-service.ts @@ -1,7 +1,7 @@ import * as path from "path"; import { ProjectData } from "../project-data"; import { exported } from "../common/decorators"; -import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, AssetConstants, SRC_DIR, RESOURCES_DIR, MAIN_DIR, CLI_RESOURCES_DIR_NAME } from "../constants"; +import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, AssetConstants, SRC_DIR, RESOURCES_DIR, MAIN_DIR, CLI_RESOURCES_DIR_NAME, ProjectTypes } from "../constants"; interface IProjectFileData { projectData: any; @@ -116,6 +116,32 @@ export class ProjectDataService implements IProjectDataService { }; } + public getAppExecutableFiles(projectDir: string): string[] { + const projectData = this.getProjectData(projectDir); + + let supportedFileExtension = ".js"; + if (projectData.projectType === ProjectTypes.NgFlavorName || projectData.projectType === ProjectTypes.TsFlavorName) { + supportedFileExtension = ".ts"; + } + + const files = this.$fs.enumerateFilesInDirectorySync( + projectData.appDirectoryPath, + (filePath, fstat) => { + if (filePath.indexOf(projectData.appResourcesDirectoryPath) !== -1) { + return false; + } + + if (fstat.isDirectory()) { + return true; + } + + return path.extname(filePath) === supportedFileExtension; + } + ); + + return files; + } + private getImageDefinitions(): IImageDefinitionsStructure { const pathToImageDefinitions = path.join(__dirname, "..", "..", CLI_RESOURCES_DIR_NAME, AssetConstants.assets, AssetConstants.imageDefinitionsFileName); const imageDefinitions = this.$fs.readJson(pathToImageDefinitions); diff --git a/test/npm-support.ts b/test/npm-support.ts index cb6bb86eed..7092a306f4 100644 --- a/test/npm-support.ts +++ b/test/npm-support.ts @@ -107,6 +107,9 @@ function createTestInjector(): IInjector { extractPackage: async (packageName: string, destinationDirectory: string, options?: IPacoteExtractOptions): Promise => undefined }); testInjector.register("usbLiveSyncService", () => ({})); + testInjector.register("doctorService", { + checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined + }); return testInjector; } diff --git a/test/platform-commands.ts b/test/platform-commands.ts index dd2d2074e7..fa19182278 100644 --- a/test/platform-commands.ts +++ b/test/platform-commands.ts @@ -176,6 +176,9 @@ function createTestInjector() { trackOptions: () => Promise.resolve(null) }); testInjector.register("usbLiveSyncService", ({})); + testInjector.register("doctorService", { + checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined + }); return testInjector; } diff --git a/test/platform-service.ts b/test/platform-service.ts index 98a725657b..9b85af1675 100644 --- a/test/platform-service.ts +++ b/test/platform-service.ts @@ -118,6 +118,9 @@ function createTestInjector() { } }); testInjector.register("usbLiveSyncService", () => ({})); + testInjector.register("doctorService", { + checkForDeprecatedShortImportsInAppDir: (projectDir: string): void => undefined + }); return testInjector; } diff --git a/test/services/doctor-service.ts b/test/services/doctor-service.ts new file mode 100644 index 0000000000..31617b009a --- /dev/null +++ b/test/services/doctor-service.ts @@ -0,0 +1,190 @@ +import { DoctorService } from "../../lib/services/doctor-service"; +import { Yok } from "../../lib/common/yok"; +import { LoggerStub } from "../stubs"; +import { assert } from "chai"; +import * as path from "path"; + +class DoctorServiceInheritor extends DoctorService { + constructor($analyticsService: IAnalyticsService, + $hostInfo: IHostInfo, + $logger: ILogger, + $childProcess: IChildProcess, + $injector: IInjector, + $projectDataService: IProjectDataService, + $fs: IFileSystem, + $terminalSpinnerService: ITerminalSpinnerService, + $versionsService: IVersionsService) { + super($analyticsService, $hostInfo, $logger, $childProcess, $injector, $projectDataService, $fs, $terminalSpinnerService, $versionsService); + } + + public getDeprecatedShortImportsInFiles(files: string[], projectDir: string): { file: string, line: string }[] { + return super.getDeprecatedShortImportsInFiles(files, projectDir); + } +} + +describe("doctorService", () => { + const createTestInjector = (): IInjector => { + const testInjector = new Yok(); + testInjector.register("doctorService", DoctorServiceInheritor); + testInjector.register("analyticsService", {}); + testInjector.register("hostInfo", {}); + testInjector.register("logger", LoggerStub); + testInjector.register("childProcess", {}); + testInjector.register("projectDataService", {}); + testInjector.register("fs", {}); + testInjector.register("terminalSpinnerService", {}); + testInjector.register("versionsService", {}); + + return testInjector; + }; + + describe("checkForDeprecatedShortImportsInAppDir", () => { + const tnsCoreModulesDirs = [ + "application", + "data" + ]; + + const testData: { filesContents: IStringDictionary, expectedShortImports: any[] }[] = [ + { + filesContents: { + file1: 'const application = require("application");' + }, + expectedShortImports: [{ file: "file1", line: 'const application = require("application");' }] + }, + { + filesContents: { + file1: 'const application = require("tns-core-modules/application");' + }, + expectedShortImports: [] + }, + { + filesContents: { + file1: 'const Observable = require("data/observable").Observable;' + }, + expectedShortImports: [{ file: "file1", line: 'const Observable = require("data/observable").Observable;' }] + }, + { + filesContents: { + file1: 'const Observable = require("tns-core-modules/data/observable").Observable;' + }, + expectedShortImports: [] + }, + { + filesContents: { + file1: 'import * as application from "application";' + }, + expectedShortImports: [{ file: "file1", line: 'import * as application from "application";' }] + }, + { + filesContents: { + file1: 'import * as application from "tns-core-modules/application";' + }, + expectedShortImports: [] + }, + { + filesContents: { + file1: 'import { run } from "application";' + }, + expectedShortImports: [{ file: "file1", line: 'import { run } from "application";' }] + }, + { + filesContents: { + file1: 'import { run } from "tns-core-modules/application";' + }, + expectedShortImports: [] + }, + { + // Using single quotes + filesContents: { + file1: "import { run } from 'application';" + }, + expectedShortImports: [{ file: "file1", line: "import { run } from 'application';" }] + }, + { + // Using single quotes + filesContents: { + file1: "import { run } from 'tns-core-modules/application';" + }, + expectedShortImports: [] + }, + { + filesContents: { + file1: `const application = require("application"); +const Observable = require("data/observable").Observable; +` + }, + expectedShortImports: [ + { file: "file1", line: 'const application = require("application");' }, + { file: "file1", line: 'const Observable = require("data/observable").Observable;' }, + ] + }, + { + filesContents: { + file1: `const application = require("application"); +const Observable = require("data/observable").Observable; +` + }, + expectedShortImports: [ + { file: "file1", line: 'const application = require("application");' }, + { file: "file1", line: 'const Observable = require("data/observable").Observable;' }, + ] + }, + { + filesContents: { + file1: `const application = require("application"); +const Observable = require("tns-core-modules/data/observable").Observable; +` + }, + expectedShortImports: [ + { file: "file1", line: 'const application = require("application");' }, + ] + }, + { + filesContents: { + file1: `const application = require("application"); +const Observable = require("tns-core-modules/data/observable").Observable; +`, + file2: `const application = require("tns-core-modules/application"); +const Observable = require("data/observable").Observable;` + }, + expectedShortImports: [ + { file: "file1", line: 'const application = require("application");' }, + { file: "file2", line: 'const Observable = require("data/observable").Observable;' }, + ] + }, + { + filesContents: { + // this is not from tns-core-modules + file1: `const application = require("application1"); +const Observable = require("tns-core-modules/data/observable").Observable; +`, + file2: `const application = require("some-name-tns-core-modules-widgets/application"); +const Observable = require("tns-core-modules-widgets/data/observable").Observable;` + }, + expectedShortImports: [ ] + }, + ]; + + it("getDeprecatedShortImportsInFiles returns correct results", () => { + const testInjector = createTestInjector(); + const doctorService = testInjector.resolve("doctorService"); + const fs = testInjector.resolve("fs"); + fs.getFsStats = (file) => ({ + isDirectory: () => true + }); + + fs.readDirectory = (dirPath) => { + if (dirPath.indexOf(path.join("node_modules", "tns-core-modules"))) { + return tnsCoreModulesDirs; + } + }; + + testData.forEach(({ filesContents, expectedShortImports }) => { + fs.readText = (filePath) => filesContents[filePath]; + + const shortImports = doctorService.getDeprecatedShortImportsInFiles(_.keys(filesContents), "projectDir"); + assert.deepEqual(shortImports, expectedShortImports); + }); + }); + }); +}); diff --git a/test/services/project-data-service.ts b/test/services/project-data-service.ts index e1f68149fb..414276b1e8 100644 --- a/test/services/project-data-service.ts +++ b/test/services/project-data-service.ts @@ -2,9 +2,10 @@ import { Yok } from "../../lib/common/yok"; import { assert } from "chai"; import { ProjectDataService } from "../../lib/services/project-data-service"; import { LoggerStub } from "../stubs"; -import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, PACKAGE_JSON_FILE_NAME, AssetConstants } from '../../lib/constants'; +import { NATIVESCRIPT_PROPS_INTERNAL_DELIMITER, PACKAGE_JSON_FILE_NAME, AssetConstants, ProjectTypes } from '../../lib/constants'; import { DevicePlatformsConstants } from "../../lib/common/mobile/device-platforms-constants"; -import { basename } from "path"; +import { basename, join } from "path"; +import { FileSystem } from "../../lib/common/file-system"; const CLIENT_NAME_KEY_IN_PROJECT_FILE = "nativescript"; @@ -299,4 +300,162 @@ describe("projectDataService", () => { assert.deepEqual(assetStructure, { ios: emptyAssetStructure, android: _.merge(_.cloneDeep(emptyAssetStructure), { splashImages: null }) }); }); }); + + describe("getAppExecutableFiles", () => { + const appDirectoryPath = "appDirPath"; + const appResourcesDirectoryPath = join(appDirectoryPath, "App_Resources"); + + const getAppExecutableFilesTestData = [ + { + projectType: ProjectTypes.NgFlavorName, + appFiles: [ + "component1.ts", + "component1.js", + "component2.ts", + "App_Resources" + ], + expectedResult: [ + join(appDirectoryPath, "component1.ts"), + join(appDirectoryPath, "component2.ts"), + ] + }, + { + projectType: ProjectTypes.TsFlavorName, + appFiles: [ + "component1.ts", + "component1.js", + "component2.ts", + "App_Resources" + ], + expectedResult: [ + join(appDirectoryPath, "component1.ts"), + join(appDirectoryPath, "component2.ts"), + ] + }, + { + projectType: ProjectTypes.JsFlavorName, + appFiles: [ + "component1.ts", + "component1.js", + "component2.ts", + "App_Resources" + ], + expectedResult: [ + join(appDirectoryPath, "component1.js"), + ] + }, + { + projectType: ProjectTypes.VueFlavorName, + appFiles: [ + "component1.ts", + "component1.js", + "component2.ts", + "App_Resources" + ], + expectedResult: [ + join(appDirectoryPath, "component1.js"), + ] + } + ]; + + const setupTestCase = (testCase: any): { projectDataService: IProjectDataService, testInjector: IInjector } => { + const testInjector = createTestInjector(); + const fs = testInjector.resolve("fs"); + const realFileSystemInstance = testInjector.resolve(FileSystem); + fs.enumerateFilesInDirectorySync = realFileSystemInstance.enumerateFilesInDirectorySync; + + const appResourcesContent: string[] = [ + "file1.ts", + "file1.js" + ]; + + fs.exists = (filePath: string) => true; + fs.getFsStats = (filePath: string) => { + if (filePath === appDirectoryPath || filePath === appResourcesDirectoryPath) { + return { isDirectory: () => true }; + } + + return { isDirectory: () => false }; + }; + + fs.readDirectory = (dirPath: string) => { + if (dirPath === appDirectoryPath) { + return testCase.appFiles; + } + + if (dirPath === appResourcesDirectoryPath) { + return appResourcesContent; + } + + return []; + }; + + const projectDataService = testInjector.resolve("projectDataService"); + projectDataService.getProjectData = () => ({ + appDirectoryPath, + appResourcesDirectoryPath, + projectType: testCase.projectType + }); + + return { projectDataService, testInjector }; + }; + + getAppExecutableFilesTestData.forEach(testCase => { + it(`returns correct files for application type ${testCase.projectType}`, () => { + const { projectDataService } = setupTestCase(testCase); + const appExecutableFiles = projectDataService.getAppExecutableFiles("projectDir"); + assert.deepEqual(appExecutableFiles, testCase.expectedResult); + }); + }); + + it("returns correct files when inner dirs exist in app dir", () => { + const innerDirName = "innerDir"; + const innerDirPath = join(appDirectoryPath, innerDirName); + const testCase = { + projectType: ProjectTypes.NgFlavorName, + appFiles: [ + "component1.ts", + "component1.js", + "component2.ts", + "App_Resources", + innerDirName + ], + expectedResult: [ + join(appDirectoryPath, "component1.ts"), + join(appDirectoryPath, "component2.ts"), + join(innerDirPath, "subcomponent1.ts"), + join(innerDirPath, "subcomponent2.ts"), + ] + }; + const { projectDataService, testInjector } = setupTestCase(testCase); + const fs = testInjector.resolve("fs"); + const baseFsReadDirectory = fs.readDirectory; + const innerDirContents = [ + "subcomponent1.ts", + "subcomponent1.js", + "subcomponent2.ts", + "subcomponent2.js", + ]; + + fs.readDirectory = (dirPath) => { + if (dirPath === innerDirPath) { + return innerDirContents; + } + + return baseFsReadDirectory(dirPath); + }; + + const baseFsGetFsStats = fs.getFsStats; + fs.getFsStats = (filePath: string) => { + if (filePath === innerDirPath) { + return { isDirectory: () => true }; + } + + return baseFsGetFsStats(filePath); + }; + + const appExecutableFiles = projectDataService.getAppExecutableFiles("projectDir"); + assert.deepEqual(appExecutableFiles, testCase.expectedResult); + }); + }); }); diff --git a/test/stubs.ts b/test/stubs.ts index 45c04dd3c9..dc24d3e6d8 100644 --- a/test/stubs.ts +++ b/test/stubs.ts @@ -527,6 +527,10 @@ export class ProjectDataService implements IProjectDataService { async getAndroidAssetsStructure(opts: IProjectDir): Promise { return null; } + + getAppExecutableFiles(projectDir: string): string[] { + return []; + } } export class ProjectHelperStub implements IProjectHelper { @@ -913,7 +917,7 @@ export class AndroidBundleValidatorHelper implements IAndroidBundleValidatorHelp export class PerformanceService implements IPerformanceService { now(): number { return 10; } - processExecutionData() {} + processExecutionData() { } } export class InjectorStub extends Yok implements IInjector {