From 50ab07495016765e0eb8963ecb1a065179a60804 Mon Sep 17 00:00:00 2001 From: Vasil Chimev Date: Thu, 21 Nov 2019 14:07:23 +0200 Subject: [PATCH] setup --- app/tests/example.ts | 9 +++ karma.conf.js | 109 ++++++++++++++++++++++++++++ package.json | 17 ++++- tsconfig.tns.json | 7 ++ webpack.config.js | 166 ++++++++++++++++++++----------------------- 5 files changed, 215 insertions(+), 93 deletions(-) create mode 100644 app/tests/example.ts create mode 100644 karma.conf.js create mode 100644 tsconfig.tns.json diff --git a/app/tests/example.ts b/app/tests/example.ts new file mode 100644 index 0000000..a287a2e --- /dev/null +++ b/app/tests/example.ts @@ -0,0 +1,9 @@ +// A sample Mocha test +describe('Array', function () { + describe('#indexOf()', function () { + it('should return -1 when the value is not present', function () { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); + }); +}); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..c96d330 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,109 @@ +module.exports = function (config) { + const options = { + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha', 'chai'], + + + // list of files / patterns to load in the browser + files: ['app/tests/**/*.ts'], + + + // list of files to exclude + exclude: [ + ], + + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + }, + + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['progress'], + + + // web server port + port: 9876, + + + // enable / disable colors in the output (reporters and logs) + colors: true, + + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: [], + + customLaunchers: { + android: { + base: 'NS', + platform: 'android' + }, + ios: { + base: 'NS', + platform: 'ios' + }, + ios_simulator: { + base: 'NS', + platform: 'ios', + arguments: ['--emulator'] + } + }, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false + }; + + setWebpackPreprocessor(config, options); + setWebpack(config, options); + + config.set(options); +} + +function setWebpackPreprocessor(config, options) { + if (config && config.bundle) { + if (!options.preprocessors) { + options.preprocessors = {}; + } + + options.files.forEach(file => { + if (!options.preprocessors[file]) { + options.preprocessors[file] = []; + } + options.preprocessors[file].push('webpack'); + }); + } +} + +function setWebpack(config, options) { + if (config && config.bundle) { + const env = {}; + env[config.platform] = true; + env.sourceMap = config.debugBrk; + env.appPath = config.appPath; + options.webpack = require('./webpack.config')(env); + delete options.webpack.entry; + delete options.webpack.output.libraryTarget; + const invalidPluginsForUnitTesting = ["GenerateBundleStarterPlugin", "GenerateNativeScriptEntryPointsPlugin"]; + options.webpack.plugins = options.webpack.plugins.filter(p => !invalidPluginsForUnitTesting.includes(p.constructor.name)); + } +} diff --git a/package.json b/package.json index 18c4706..226bdb3 100644 --- a/package.json +++ b/package.json @@ -24,23 +24,34 @@ "@nativescript/core": "~6.2.1", "@nativescript/theme": "./src", "bootstrap": "4.3.1", - "nativescript-picker": "~2.1.2", "nativescript-datetimepicker": "~1.2.2", + "nativescript-picker": "~2.1.2", "nativescript-themes": "2.0.1", "nativescript-ui-autocomplete": "~6.0.0", - "nativescript-ui-sidedrawer": "~8.0.0", "nativescript-ui-dataform": "~6.0.0", "nativescript-ui-listview": "~8.0.1", - "tns-core-modules": "~6.2.1" + "nativescript-ui-sidedrawer": "~8.0.0", + "nativescript-unit-test-runner": "^0.7.0", + "tns-core-modules": "~6.2.1", + "typescript": "^3.7.2" }, "devDependencies": { "@babel/core": "7.6.4", "@babel/plugin-transform-modules-commonjs": "7.6.0", + "@types/karma-chai": "0.1.2", + "@types/mocha": "5.2.7", "babel-eslint": "10.0.3", "cache-loader": "4.1.0", + "chai": "4.2.0", "eslint": "6.4.0", "glob": "7.1.4", + "karma": "4.4.1", + "karma-chai": "0.1.0", + "karma-mocha": "1.3.0", + "karma-nativescript-launcher": "0.4.0", + "karma-webpack": "3.0.5", "lazy": "1.0.11", + "mocha": "6.2.2", "nativescript-dev-webpack": "~1.3.0", "resolve-url-loader": "3.1.0", "sass": "1.23.0", diff --git a/tsconfig.tns.json b/tsconfig.tns.json new file mode 100644 index 0000000..9ce50ed --- /dev/null +++ b/tsconfig.tns.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig", + "compilerOptions": { + "module": "esNext", + "moduleResolution": "node" + } +} diff --git a/webpack.config.js b/webpack.config.js index 0e384b1..e11e111 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,24 +3,23 @@ const { join, relative, resolve, sep } = require("path"); const webpack = require("webpack"); const nsWebpack = require("nativescript-dev-webpack"); const nativescriptTarget = require("nativescript-dev-webpack/nativescript-target"); +const { getNoEmitOnErrorFromTSConfig } = require("nativescript-dev-webpack/utils/tsconfig-utils"); const CleanWebpackPlugin = require("clean-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); +const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); const { NativeScriptWorkerPlugin } = require("nativescript-worker-loader/NativeScriptWorkerPlugin"); const TerserPlugin = require("terser-webpack-plugin"); const hashSalt = Date.now().toString(); -const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); -const smp = new SpeedMeasurePlugin(); - -module.exports = smp.wrap((env) => { - // Add your custom Activities, Services and other android app components here. +module.exports = env => { + // Add your custom Activities, Services and other Android app components here. const appComponents = [ "tns-core-modules/ui/frame", - "tns-core-modules/ui/frame/activity" + "tns-core-modules/ui/frame/activity", ]; - const platform = env && ((env.android && "android") || (env.ios && "ios")); + const platform = env && (env.android && "android" || env.ios && "ios"); if (!platform) { throw new Error("You need to provide a target platform!"); } @@ -31,11 +30,10 @@ module.exports = smp.wrap((env) => { // Default destination inside platforms//... const dist = resolve(projectRoot, nsWebpack.getAppPath(platform, projectRoot)); - const appPath = env.forceAppPath || env.appPath || "app"; - const { // The 'appPath' and 'appResourcesPath' values are fetched from // the nsconfig.json configuration file. + appPath = "app", appResourcesPath = "app/App_Resources", // You can provide the following flags when running 'tns run android|ios' @@ -56,19 +54,22 @@ module.exports = smp.wrap((env) => { const useLibs = compileSnapshot; const isAnySourceMapEnabled = !!sourceMap || !!hiddenSourceMap; const externals = nsWebpack.getConvertedExternals(env.externals); + const appFullPath = resolve(projectRoot, appPath); const appResourcesFullPath = resolve(projectRoot, appResourcesPath); const entryModule = nsWebpack.getEntryModule(appFullPath, platform); - const entryPath = `.${sep}${entryModule}.js`; + const entryPath = `.${sep}${entryModule}.ts`; const entries = { bundle: entryPath }; - const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some((e) => e.indexOf("tns-core-modules") > -1); + const tsConfigPath = resolve(projectRoot, "tsconfig.tns.json"); + + const areCoreModulesExternal = Array.isArray(env.externals) && env.externals.some(e => e.indexOf("tns-core-modules") > -1); if (platform === "ios" && !areCoreModulesExternal) { entries["tns_modules/tns-core-modules/inspector_modules"] = "inspector_modules"; - } + }; - const sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist); + let sourceMapFilename = nsWebpack.getSourceMapFilename(hiddenSourceMap, __dirname, dist); const itemsToClean = [`${dist}/**/*`]; if (platform === "android") { @@ -76,6 +77,8 @@ module.exports = smp.wrap((env) => { itemsToClean.push(`${join(projectRoot, "platforms", "android", "app", "build", "configurations", "nativescript-android-snapshot")}`); } + const noEmitOnErrorFromTSConfig = getNoEmitOnErrorFromTSConfig(tsConfigPath); + nsWebpack.processAppComponents(appComponents, platform); const config = { mode: production ? "production" : "development", @@ -85,7 +88,7 @@ module.exports = smp.wrap((env) => { ignored: [ appResourcesFullPath, // Don't watch hidden files - "**/.*" + "**/.*", ] }, target: nativescriptTarget, @@ -100,16 +103,16 @@ module.exports = smp.wrap((env) => { hashSalt }, resolve: { - extensions: [".js", ".scss", ".css"], + extensions: [".ts", ".js", ".scss", ".css"], // Resolve {N} system modules from tns-core-modules modules: [ resolve(__dirname, "node_modules/tns-core-modules"), resolve(__dirname, "node_modules"), "node_modules/tns-core-modules", - "node_modules" + "node_modules", ], alias: { - "~": appFullPath + '~': appFullPath }, // resolve symlinks to symlinked modules symlinks: true @@ -124,32 +127,25 @@ module.exports = smp.wrap((env) => { "timers": false, "setImmediate": false, "fs": "empty", - "__dirname": false + "__dirname": false, }, - // eslint-disable-next-line no-nested-ternary devtool: hiddenSourceMap ? "hidden-source-map" : (sourceMap ? "inline-source-map" : "none"), optimization: { runtimeChunk: "single", - noEmitOnErrors: true, + noEmitOnErrors: noEmitOnErrorFromTSConfig, splitChunks: { cacheGroups: { - styles: { - name: "styles", - test: /\.scss$/, - chunks: "all", - enforce: true - }, vendor: { name: "vendor", chunks: "all", - test: (module) => { - const moduleName = module.nameForCondition ? module.nameForCondition() : ""; - return (/[\\/]node_modules[\\/]/).test(moduleName) || - appComponents.some((comp) => comp === moduleName); + test: (module, chunks) => { + const moduleName = module.nameForCondition ? module.nameForCondition() : ''; + return /[\\/]node_modules[\\/]/.test(moduleName) || + appComponents.some(comp => comp === moduleName); }, - enforce: true - } + enforce: true, + }, } }, minimize: !!uglify, @@ -166,12 +162,12 @@ module.exports = smp.wrap((env) => { compress: { // The Android SBG has problems parsing the output // when these options are enabled - "collapse_vars": platform !== "android", - sequences: platform !== "android" + 'collapse_vars': platform !== "android", + sequences: platform !== "android", } } }) - ] + ], }, module: { rules: [ @@ -187,99 +183,89 @@ module.exports = smp.wrap((env) => { { loader: "nativescript-dev-webpack/bundle-config-loader", options: { - registerModules: /\.(xml|css|js|ts)$/, loadCss: !snapshot, // load the application css if in debug mode unitTesting, appFullPath, projectRoot, ignoredFiles: nsWebpack.getUserDefinedEntries(entries, platform) } - } - ].filter((loader) => !!loader) + }, + ].filter(loader => !!loader) }, { - test: /\.(js|css|scss|html|xml)$/, - use: [ - "cache-loader", - "nativescript-dev-webpack/hmr/hot-loader" - ] + test: /\.(ts|css|scss|html|xml)$/, + use: "nativescript-dev-webpack/hmr/hot-loader" }, - { - test: /\.(html|xml)$/, - use: "nativescript-dev-webpack/xml-namespace-loader" - }, + { test: /\.(html|xml)$/, use: "nativescript-dev-webpack/xml-namespace-loader" }, { test: /\.css$/, - use: { - loader: "css-loader", - options: { url: false } - } + use: "nativescript-dev-webpack/css2json-loader" }, { test: /\.scss$/, use: [ - { - loader: "css-loader", - options: { - url: false + "nativescript-dev-webpack/css2json-loader", + "sass-loader" + ] + }, + + { + test: /\.ts$/, + use: { + loader: "ts-loader", + options: { + configFile: tsConfigPath, + // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds + // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement + transpileOnly: true, + allowTsInNodeModules: true, + compilerOptions: { + sourceMap: isAnySourceMapEnabled, + declaration: false } }, - { - loader: "sass-loader", - options: { - implementation: require("sass") - } - } - ] - } + } + }, ] }, plugins: [ // Define useful constants like TNS_WEBPACK new webpack.DefinePlugin({ "global.TNS_WEBPACK": "true", - "process": "global.process" + "process": "global.process", }), // Remove all files from the out dir. new CleanWebpackPlugin(itemsToClean, { verbose: !!verbose }), // Copy assets to out dir. Add your own globs as needed. new CopyWebpackPlugin([ - { from: { glob: "assets/**" } }, { from: { glob: "fonts/**" } }, { from: { glob: "**/*.jpg" } }, - { from: { glob: "**/*.png" } } - ], { - ignore: [ - `${relative(appPath, appResourcesFullPath)}/**`, - "assets/fonts/**" - ] - }), - new CopyWebpackPlugin([ - { - from: { - glob: "assets/fonts/**" - }, - to: "fonts/", - flatten: true - } - ]), - + { from: { glob: "**/*.png" } }, + ], { ignore: [`${relative(appPath, appResourcesFullPath)}/**`] }), new nsWebpack.GenerateNativeScriptEntryPointsPlugin("bundle"), - // For instructions on how to set up workers with webpack // check out https://github.com/nativescript/worker-loader new NativeScriptWorkerPlugin(), new nsWebpack.PlatformFSPlugin({ platform, - platforms + platforms, }), // Does IPC communication with the {N} CLI to notify events when running in watch mode. - new nsWebpack.WatchStateLoggerPlugin() - ] + new nsWebpack.WatchStateLoggerPlugin(), + // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#faster-builds + // https://github.com/TypeStrong/ts-loader/blob/ea2fcf925ec158d0a536d1e766adfec6567f5fb4/README.md#hot-module-replacement + new ForkTsCheckerWebpackPlugin({ + tsconfig: tsConfigPath, + async: false, + useTypescriptIncrementalApi: true, + checkSyntacticErrors: true, + memoryLimit: 4096 + }) + ], }; if (report) { @@ -288,8 +274,8 @@ module.exports = smp.wrap((env) => { analyzerMode: "static", openAnalyzer: false, generateStatsFile: true, - reportFilename: resolve(projectRoot, "report", "report.html"), - statsFilename: resolve(projectRoot, "report", "stats.json") + reportFilename: resolve(projectRoot, "report", `report.html`), + statsFilename: resolve(projectRoot, "report", `stats.json`), })); } @@ -297,7 +283,7 @@ module.exports = smp.wrap((env) => { config.plugins.push(new nsWebpack.NativeScriptSnapshotPlugin({ chunk: "vendor", requireModules: [ - "tns-core-modules/bundle-entry-points" + "tns-core-modules/bundle-entry-points", ], projectRoot, webpackConfig: config, @@ -313,4 +299,4 @@ module.exports = smp.wrap((env) => { return config; -}); +};