import * as proxyquire from 'proxyquire'; import * as nsWebpackIndex from '../index'; import { join } from 'path'; // With noCallThru enabled, `proxyquire` will not fall back to requiring the real module to populate properties that are not mocked. // This allows us to mock packages that are not available in node_modules. // In case you want to enable fallback for a specific object, just add `'@noCallThru': false`. proxyquire.noCallThru(); class EmptyClass {} let angularCompilerOptions: any; class AngularCompilerStub { constructor(options) { angularCompilerOptions = options; } } let terserOptions: any; class TerserJsStub { constructor(options) { terserOptions = options; } } const nativeScriptDevWebpack = { GenerateBundleStarterPlugin: EmptyClass, GenerateNativeScriptEntryPointsPlugin: EmptyClass, WatchStateLoggerPlugin: EmptyClass, PlatformFSPlugin: EmptyClass, getAppPath: () => 'app', getEntryModule: () => 'EntryModule', hasRootLevelScopedModules: () => false, hasRootLevelScopedAngular: () => false, processTsPathsForScopedModules: () => false, processTsPathsForScopedAngular: () => false, getResolver: () => null, getConvertedExternals: nsWebpackIndex.getConvertedExternals, getSourceMapFilename: nsWebpackIndex.getSourceMapFilename, processAppComponents: nsWebpackIndex.processAppComponents, getUserDefinedEntries: nsWebpackIndex.getUserDefinedEntries, }; const emptyObject = {}; const FakeHmrTransformerFlag = 'hmr'; const FakeNativeClassTransformerFlag = 'NativeClass'; const FakeLazyTransformerFlag = 'lazy'; const webpackConfigAngular = proxyquire('./webpack.angular', { '@nativescript/webpack': nativeScriptDevWebpack, '@nativescript/webpack/nativescript-target': emptyObject, '@nativescript/webpack/transformers/ns-support-hmr-ng': { nsSupportHmrNg: () => { return FakeHmrTransformerFlag; }, }, '@nativescript/webpack/transformers/ns-transform-native-classes-ng': { nsTransformNativeClassesNg: () => { return FakeNativeClassTransformerFlag; }, }, '@nativescript/webpack/helpers/angular-config-parser': { parseWorkspaceConfig: (platform, envConfigs, rootPath = '') => { return { fileReplacements: {}, copyReplacements: [], }; }, hasConfigurations: (envConfigs) => { return false; }, }, '@nativescript/webpack/utils/ast-utils': { getMainModulePath: () => { return 'fakePath'; }, }, '@nativescript/webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; }, getCompilerOptionsFromTSConfig: () => { return false; }, }, '@nativescript/webpack/plugins/NativeScriptAngularCompilerPlugin': { getAngularCompilerPlugin: () => { return AngularCompilerStub; }, }, '@ngtools/webpack': { AngularCompilerPlugin: AngularCompilerStub, }, 'terser-webpack-plugin': TerserJsStub, }); const webpackConfigTypeScript = proxyquire('./webpack.typescript', { '@nativescript/webpack': nativeScriptDevWebpack, '@nativescript/webpack/nativescript-target': emptyObject, '@nativescript/webpack/transformers/ns-transform-native-classes': emptyObject, '@nativescript/webpack/utils/tsconfig-utils': { getNoEmitOnErrorFromTSConfig: () => { return false; }, getCompilerOptionsFromTSConfig: () => { return false; }, }, 'terser-webpack-plugin': TerserJsStub, }); const webpackConfigJavaScript = proxyquire('./webpack.javascript', { '@nativescript/webpack': nativeScriptDevWebpack, '@nativescript/webpack/nativescript-target': emptyObject, 'terser-webpack-plugin': TerserJsStub, }); const webpackConfigVue = proxyquire('./webpack.vue', { '@nativescript/webpack': nativeScriptDevWebpack, '@nativescript/webpack/nativescript-target': emptyObject, '@nativescript/webpack/transformers/ns-transform-native-classes': emptyObject, 'vue-loader/lib/plugin': EmptyClass, 'nativescript-vue-template-compiler': emptyObject, 'terser-webpack-plugin': TerserJsStub, }); describe('webpack.config.js', () => { const getInput = (options: { platform: string; hmr?: boolean; externals?: string[]; sourceMap?: boolean; hiddenSourceMap?: boolean | string }) => { const input: any = Object.assign({}, options); input[options.platform] = true; return input; }; [ { type: 'javascript', webpackConfig: webpackConfigJavaScript }, { type: 'typescript', webpackConfig: webpackConfigTypeScript }, { type: 'angular', webpackConfig: webpackConfigAngular }, { type: 'vue', webpackConfig: webpackConfigVue }, ].forEach((element) => { const { type, webpackConfig } = element; ['android', 'ios'].forEach((platform) => { describe(`verify externals for webpack.${type}.js (${platform})`, () => { afterEach(() => { nativeScriptDevWebpack.getConvertedExternals = nsWebpackIndex.getConvertedExternals; }); it('returns empty array when externals are not passed', () => { const input = getInput({ platform }); const config = webpackConfig(input); expect(config.externals).toEqual([]); }); it('calls getConvertedExternals to convert externals', () => { let isCalled = false; nativeScriptDevWebpack.getConvertedExternals = () => { isCalled = true; return []; }; const input = getInput({ platform, externals: ['nativescript-vue'] }); webpackConfig(input); expect(isCalled).toBe(true, 'Webpack.config.js must use the getConvertedExternals method'); }); // if (platform === "ios") { // it('has inspector_modules entry when tns-core-modules are not externals', () => { // const input = getInput({ platform, externals: ['nativescript-vue'] }); // const config = webpackConfig(input); // expect(config.entry["tns_modules/@nativescript/core/inspector_modules"]).toEqual("inspector_modules"); // }); // it('does not have inspector_modules entry when @nativescript/core are externals', () => { // const input = getInput({ platform, externals: ['@nativescript'] }); // const config = webpackConfig(input); // expect(config.entry["tns_modules/@nativescript/core/inspector_modules"]).toBeUndefined(); // }); // } [ { input: ['nativescript-vue'], expectedOutput: [/^nativescript-vue((\/.*)|$)/], }, { input: ['nativescript-vue', 'nativescript-angular'], expectedOutput: [/^nativescript-vue((\/.*)|$)/, /^nativescript-angular((\/.*)|$)/], }, ].forEach((testCase) => { const input = getInput({ platform, externals: testCase.input }); it(`are correct regular expressions, for input ${testCase.input}`, () => { const config = webpackConfig(input); expect(config.externals).toEqual(testCase.expectedOutput); }); }); }); if (type === 'angular') { describe(`angular transformers for webpack.${type}.js (${platform})`, () => { beforeEach(() => { angularCompilerOptions = null; }); it('should contain only NativeClass transformer by default', () => { const input = getInput({ platform }); webpackConfig(input); expect(angularCompilerOptions).toBeDefined(); expect(angularCompilerOptions.platformTransformers).toBeDefined(); expect(angularCompilerOptions.platformTransformers.length).toEqual(1); expect(angularCompilerOptions.platformTransformers[0]).toEqual(FakeNativeClassTransformerFlag); }); it('should contain the HMR transformer when the HMR flag is passed', () => { const input = getInput({ platform, hmr: true }); webpackConfig(input); expect(angularCompilerOptions).toBeDefined(); expect(angularCompilerOptions.platformTransformers).toBeDefined(); expect(angularCompilerOptions.platformTransformers.length).toEqual(2); expect(angularCompilerOptions.platformTransformers[1]).toEqual(FakeHmrTransformerFlag); }); }); } describe(`source map for webpack.${type}.js (${platform})`, () => { beforeEach(() => { terserOptions = null; }); it('should not set source maps without the flag', () => { const input = getInput({ platform, sourceMap: false }); const config = webpackConfig(input); expect(config.devtool).toEqual('none'); expect(terserOptions.sourceMap).toBeFalsy(); expect(terserOptions.terserOptions.output.semicolons).toBeTruthy(); expect(config.output.sourceMapFilename).toEqual('[file].map'); }); it('should set inline-source-map devtool', () => { const input = getInput({ platform, sourceMap: true }); const config = webpackConfig(input); expect(config.devtool).toEqual('inline-source-map'); expect(terserOptions.sourceMap).toBeTruthy(); expect(terserOptions.terserOptions.output.semicolons).toBeFalsy(); expect(config.output.sourceMapFilename).toEqual('[file].map'); }); }); describe(`hidden source map for webpack.${type}.js (${platform})`, () => { beforeEach(() => { terserOptions = null; }); it('should not set source maps without the flag', () => { const input = getInput({ platform, hiddenSourceMap: false }); const config = webpackConfig(input); expect(config.devtool).toEqual('none'); expect(terserOptions.sourceMap).toBeFalsy(); expect(terserOptions.terserOptions.output.semicolons).toBeTruthy(); expect(config.output.sourceMapFilename).toEqual('[file].map'); }); it('should set hidden-source-map devtool and the default sourceMap folder', () => { const input = getInput({ platform, hiddenSourceMap: true }); const config = webpackConfig(input); expect(config.devtool).toEqual('hidden-source-map'); expect(terserOptions.sourceMap).toBeTruthy(); expect(terserOptions.terserOptions.output.semicolons).toBeFalsy(); expect(config.output.sourceMapFilename).toEqual(join('..', 'sourceMap', '[file].map')); }); it('should override the sourceMap property and the default sourceMap folder', () => { const input = getInput({ platform, sourceMap: true, hiddenSourceMap: true }); const config = webpackConfig(input); expect(config.devtool).toEqual('hidden-source-map'); expect(terserOptions.sourceMap).toBeTruthy(); expect(terserOptions.terserOptions.output.semicolons).toBeFalsy(); expect(config.output.sourceMapFilename).toEqual(join('..', 'sourceMap', '[file].map')); }); it('should set hidden-source-map devtool and override the sourceMapFilename', () => { const newSourceMapFolder = 'myCoolSourceMapFolder'; const input = getInput({ platform, sourceMap: true, hiddenSourceMap: newSourceMapFolder }); const config = webpackConfig(input); expect(config.devtool).toEqual('hidden-source-map'); expect(terserOptions.sourceMap).toBeTruthy(); expect(terserOptions.terserOptions.output.semicolons).toBeFalsy(); expect(config.output.sourceMapFilename).toEqual(join('..', newSourceMapFolder, '[file].map')); }); }); }); }); });