chore: organize test files

This commit is contained in:
Igor Randjelovic
2020-12-01 19:25:29 +01:00
parent 65b214b845
commit 016ecd19a8
22 changed files with 339 additions and 140 deletions

View File

@ -166,6 +166,12 @@ exports[`angular configuration for android 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -398,6 +404,12 @@ exports[`angular configuration for ios 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{

View File

@ -32,7 +32,8 @@ exports[`javascript configuration for android 1`] = `
'.android.scss',
'.scss',
'.android.json',
'.json'
'.json',
'.xml'
]
},
resolveLoader: {
@ -170,6 +171,12 @@ exports[`javascript configuration for android 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -216,10 +223,14 @@ exports[`javascript configuration for android 1`] = `
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPluginPlugin') */
new ContextExclusionPlugin(
/__virtual_entry__\\\\.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js)$/);\\\\nglobal.registerWebpackModules(context);'
'__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);'
}
)
],
@ -265,7 +276,8 @@ exports[`javascript configuration for ios 1`] = `
'.ios.scss',
'.scss',
'.ios.json',
'.json'
'.json',
'.xml'
]
},
resolveLoader: {
@ -403,6 +415,12 @@ exports[`javascript configuration for ios 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -449,10 +467,14 @@ exports[`javascript configuration for ios 1`] = `
),
/* config.plugin('WatchStatePlugin') */
new WatchStatePlugin(),
/* config.plugin('ContextExclusionPluginPlugin') */
new ContextExclusionPlugin(
/__virtual_entry__\\\\.js$/
),
/* config.plugin('VirtualModulesPlugin') */
new VirtualModulesPlugin(
{
'__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js)$/);\\\\nglobal.registerWebpackModules(context);'
'__jest__/src/__virtual_entry__.js': 'require(\\\\'@nativescript/core/bundle-entry-points\\\\')\\\\nconst context = require.context(\\"~/\\", /* deep: */ true, /* filter: */ /.(xml|js|s?css)$/);\\\\nglobal.registerWebpackModules(context);'
}
)
],

View File

@ -175,6 +175,12 @@ exports[`react configuration > android > adds ReactRefreshWebpackPlugin when HMR
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -408,6 +414,12 @@ exports[`react configuration > android > base config 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -641,6 +653,12 @@ exports[`react configuration > ios > adds ReactRefreshWebpackPlugin when HMR ena
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -877,6 +895,12 @@ exports[`react configuration > ios > base config 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{

View File

@ -190,6 +190,12 @@ exports[`svelte configuration for android 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -436,6 +442,12 @@ exports[`svelte configuration for ios 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{

View File

@ -192,6 +192,12 @@ exports[`vue configuration for android 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'android'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{
@ -440,6 +446,12 @@ exports[`vue configuration for ios 1`] = `
verbose: false
}
),
/* config.plugin('PlatformSuffixPlugin') */
new PlatformSuffixPlugin(
{
platform: 'ios'
}
),
/* config.plugin('DefinePlugin') */
new DefinePlugin(
{

View File

@ -1,4 +1,3 @@
// @ts-ignore
import Config from 'webpack-chain';
import javascript from '../../src/configuration/javascript';
import { init } from '../../src';

View File

@ -1,6 +1,7 @@
import Config from 'webpack-chain';
import svelte from '../../src/configuration/svelte';
import { init } from '../../src';
import { mockFile } from '../../scripts/jest.mockFiles';
mockFile('./svelte.config.js', '');
// jest.mock('__jest__/svelte.config.js', () => {

View File

@ -42,7 +42,7 @@ describe('@nativescript/webpack', () => {
lastCalled = true;
expect(config.normal).toBe(true);
});
webpack.chainWebpack(chainFnLast, { last: true });
webpack.chainWebpack(chainFnLast, { order: 10 });
const chainFnNormal = jest.fn((config) => {
config.normal = true;
@ -55,6 +55,22 @@ describe('@nativescript/webpack', () => {
webpack.resolveChainableConfig();
});
it('prints plugin name that errored out', () => {
webpack.useConfig(false);
webpack.setCurrentPlugin('test-plugin');
const chainFn = jest.fn(() => {
throw new Error('something wrong');
});
webpack.chainWebpack(chainFn);
// should not throw
expect(() => webpack.resolveChainableConfig()).not.toThrow();
expect(
'Unable to apply chain function from: test-plugin'
).toHaveBeenWarned();
});
it('applies merge configs', () => {
const dummyEnv = { env: true };
webpack.init(dummyEnv);

View File

@ -5,11 +5,12 @@ module.exports = {
'^@nativescript/webpack$': '<rootDir>/src'
},
setupFiles: [
'<rootDir>/jest.setup.ts'
'<rootDir>/scripts/jest.setup.ts'
],
setupFilesAfterEnv: [
'<rootDir>/scripts/jest.mockWarn.ts'
],
globals: {
'ts-jest': {
tsconfig: 'tsconfig.jest.json'
}
__TEST__: true,
}
};

View File

@ -1,85 +0,0 @@
// define our global helpers
declare global {
function mockFile(path: string, content: string);
}
// enable TEST mode
global.__TEST__ = true;
// we are mocking the cwd for the tests, since webpack needs absolute paths
// and we don't want them in tests
import dedent from 'ts-dedent';
process.cwd = () => '__jest__';
// a virtual mock for package.json
jest.mock(
'__jest__/package.json',
() => ({
main: 'src/app.js',
devDependencies: {
typescript: '*',
},
}),
{ virtual: true }
);
jest.mock('cosmiconfig', () => ({
cosmiconfigSync(moduleName) {
return {
search() {
// no-op in tests
return null;
},
};
},
}));
jest.mock('path', () => {
const path = jest.requireActual('path');
return {
...path,
resolve(...args) {
if (args[0] === '__jest__') {
return path.join(...args);
}
const resolved = path.resolve(...args);
if (resolved.includes('__jest__')) {
const li = resolved.lastIndexOf('__jest__');
return resolved.substr(li);
}
return resolved;
},
};
});
const mockedFiles: { [path: string]: string } = {};
global.mockFile = function mockFile(path, content) {
const unionFS = require('unionfs').default;
const Volume = require('memfs').Volume;
// reset to fs
unionFS.reset();
// add mocked file
mockedFiles[path] = dedent(content);
// create new volume
const vol = Volume.fromJSON(mockedFiles, '__jest__');
// use the new volume
unionFS.use(vol as any);
};
jest.mock('fs', () => {
const fs = jest.requireActual('fs');
const unionFS = require('unionfs').default;
unionFS.reset = () => {
unionFS.fss = [fs];
};
return unionFS.use(fs);
});

View File

@ -11,12 +11,11 @@
},
"license": "Apache-2.0",
"scripts": {
"build": "tsc",
"build": "tsc --project tsconfig.build.json",
"test": "jest",
"prepack": "npm run build && cp -R src/stubs dist/stubs && chmod +x dist/bin/index.js"
},
"dependencies": {
"@babel/core": "^7.12.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.3",
"@types/sax": "^1.2.1",
"babel-loader": "^8.2.1",
@ -57,6 +56,7 @@
"@types/terser-webpack-plugin": "^5.0.2",
"@types/webpack-virtual-modules": "^0.1.0",
"jest": "^26.6.3",
"jest-matcher-utils": "^26.6.2",
"memfs": "^3.2.0",
"nativescript-vue-template-compiler": "^2.8.2",
"ts-jest": "^26.4.4",

View File

@ -0,0 +1,12 @@
// define test-specific globals here
declare namespace jest {
interface Matchers<R, T> {
toHaveBeenWarned(): R;
toHaveBeenPrinted(): R;
}
}
declare global {
function mockFile(path: string, content: string);
}

View File

@ -0,0 +1,42 @@
import dedent from 'ts-dedent';
const mockedFiles: { [path: string]: string } = {};
export function mockFile(path, content) {
const unionFS = require('unionfs').default;
const Volume = require('memfs').Volume;
// reset to fs
unionFS.reset();
// add mocked file
mockedFiles[path] = dedent(content);
// create new volume
const vol = Volume.fromJSON(mockedFiles, '__jest__');
// use the new volume
unionFS.use(vol as any);
}
// a virtual mock for package.json
jest.mock(
'__jest__/package.json',
() => ({
main: 'src/app.js',
devDependencies: {
typescript: '*',
},
}),
{ virtual: true }
);
jest.mock('fs', () => {
const fs = jest.requireActual('fs');
const unionFS = require('unionfs').default;
unionFS.reset = () => {
unionFS.fss = [fs];
};
return unionFS.use(fs);
});

View File

@ -0,0 +1,64 @@
import { printExpected, printReceived } from 'jest-matcher-utils';
import dedent from 'ts-dedent';
expect.extend({
toHaveBeenWarned(received: string) {
asserted.add(received);
const passed = warnSpy.mock.calls
.map((args) => args[1])
.some((arg) => arg.indexOf(received) > -1);
if (passed) {
return {
pass: true,
message() {
return `expected ${printReceived(received)} not to have been warned`;
},
};
}
const warnings = warnSpy.mock.calls.map((args) => args[1]).join('\n\n');
return {
pass: false,
message() {
return dedent`
expected ${printExpected(received)} to have been warned.
Actual warnings:
${warnings}
`;
},
};
},
});
let warnSpy: any;
let asserted = new Set([]);
beforeEach(() => {
asserted.clear();
warnSpy = jest.spyOn(console, 'warn');
warnSpy.mockImplementation(() => {});
});
afterEach(() => {
const assertedArray = Array.from(asserted);
const nonAssertedWarns = warnSpy.mock.calls
.map((args) => args[1])
.filter((received) => {
return !assertedArray.some((assertedMessage) => {
return received.indexOf(assertedMessage) > -1;
});
});
warnSpy.mockRestore();
if (nonAssertedWarns.length) {
throw new Error(dedent`
Test case printed unexpected warnings:
${printReceived(nonAssertedWarns.join('\n\n'))}
`);
}
});

View File

@ -0,0 +1,35 @@
import './jest.mockFiles';
// we are mocking the cwd for the tests, since webpack needs absolute paths
// and we don't want them in tests
process.cwd = () => '__jest__';
jest.mock('cosmiconfig', () => ({
cosmiconfigSync(moduleName) {
return {
search() {
// no-op in tests
return null;
},
};
},
}));
jest.mock('path', () => {
const path = jest.requireActual('path');
return {
...path,
resolve(...args) {
if (args[0] === '__jest__') {
return path.join(...args);
}
const resolved = path.resolve(...args);
if (resolved.includes('__jest__')) {
const li = resolved.lastIndexOf('__jest__');
return resolved.substr(li);
}
return resolved;
},
};
});

View File

@ -1,8 +1,10 @@
import path from 'path';
import fs from 'fs';
import * as lib from '../index';
import { error, info, warn } from './log';
import { getAllDependencies, getDependencyPath } from './dependencies';
import { info, warn } from './log';
import * as lib from '../index';
import { clearCurrentPlugin, setCurrentPlugin } from '../index';
export function applyExternalConfigs() {
getAllDependencies().forEach((dependency) => {
@ -11,7 +13,7 @@ export function applyExternalConfigs() {
if (fs.existsSync(configPath)) {
info(`Discovered config: ${configPath}`);
setCurrentPlugin(dependency);
try {
const externalConfig = require(configPath);
@ -27,14 +29,13 @@ export function applyExternalConfigs() {
);
}
} catch (err) {
error(
`
warn(`
Unable to apply config: ${configPath}.
Error is:
`,
err
);
Error is: ${err}
`);
}
}
});
clearCurrentPlugin();
}

View File

@ -11,7 +11,7 @@ function cleanup(data: any[]) {
}
export function error(...data: any): Error {
console.error(`[@nativescript/webpack] Error: \n`, ...cleanup(data));
console.warn(`[@nativescript/webpack] Error: \n`, ...cleanup(data));
// we return the error - the caller can throw or ignore
if (typeof data[0] === 'string') {
@ -26,7 +26,7 @@ export function warn(...data: any): void {
}
export function info(...data: any): void {
console.info(`[@nativescript/webpack] Info: \n`, ...cleanup(data));
console.log(`[@nativescript/webpack] Info: \n`, ...cleanup(data));
}
// todo: refine

View File

@ -30,20 +30,36 @@ export interface IWebpackEnv {
// todo: add others
}
let webpackChains = {
base: [],
normal: [],
last: [],
};
interface IChainEntry {
chainFn: any;
order?: number;
plugin?: string;
}
let webpackChains: IChainEntry[] = [];
let webpackMerges: any[] = [];
let explicitUseConfig = false;
let hasInitialized = false;
let currentPlugin: string | undefined;
/**
* @internal
*/
export let env: IWebpackEnv = {};
/**
* @internal
*/
export function setCurrentPlugin(plugin: string) {
currentPlugin = plugin;
}
/**
* @internal
*/
export function clearCurrentPlugin() {
currentPlugin = undefined;
}
////// PUBLIC API
export const defaultConfigs = configs;
export const Utils = helpers;
@ -58,16 +74,22 @@ export function init(_env: IWebpackEnv) {
export function useConfig(config: keyof typeof defaultConfigs | false) {
explicitUseConfig = true;
if (config) {
webpackChains.base.push(configs[config]);
webpackChains.push({
order: -1,
chainFn: configs[config],
});
}
}
export function chainWebpack(
chainFn: (config: Config, env: IWebpackEnv) => any,
options?: { last?: boolean }
options?: { order?: number }
) {
const type = options?.last ? 'last' : 'normal';
webpackChains[type].push(chainFn);
webpackChains.push({
order: options?.order || 0,
chainFn,
plugin: currentPlugin,
});
}
export function mergeWebpack(
@ -90,19 +112,24 @@ export function resolveChainableConfig(): Config {
// todo: allow opt-out
applyExternalConfigs();
const applyChains = (chains) => {
// this applies the chain configs
chains.forEach((chainFn) => {
return chainFn(config, env);
webpackChains
.splice(0)
.sort((a, b) => {
return a.order - b.order;
})
.forEach(({ chainFn, plugin }) => {
try {
chainFn(config, env);
} catch (err) {
if (plugin) {
// print error with plugin name that causes it
error(`
Unable to apply chain function from: ${plugin}.
Error is: ${err}
`);
}
}
});
};
// first we apply base configs
applyChains(webpackChains.base);
// then regular configs
applyChains(webpackChains.normal);
// finally configs that opted to be called last
applyChains(webpackChains.last);
if (env.verbose) {
info('Resolved chainable config (before merges):');

View File

@ -29,6 +29,7 @@ export class PlatformSuffixPlugin {
);
const platformRE = new RegExp(`\.${this.platform}\.`);
// require.context
compiler.hooks.contextModuleFactory.tap(id, (cmf) => {
cmf.hooks.alternativeRequests.tap(id, (modules, options) => {
const additionalModules = [];

View File

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"rootDir": "./src"
},
"include": ["src"]
}

View File

@ -1,4 +0,0 @@
{
"extends": "./tsconfig.json",
"include": ["src", "__tests__", "jest.setup.ts"]
}

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"rootDir": "./src",
"rootDir": ".",
"baseUrl": ".",
"target": "es2017",
"module": "commonjs",
@ -20,6 +20,6 @@
"allowSyntheticDefaultImports": true,
"stripInternal": true
},
"include": ["src"],
"include": ["src", "scripts", "__tests__"],
"exclude": ["node_modules"]
}