From 5b2a7a8a4716d7f9c5ff1e33f1c72df396bae49b Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sat, 16 May 2020 17:27:08 +0300 Subject: [PATCH 1/2] test: refactor tests --- test/__snapshots__/loader.test.js.snap | 140 ++++++++++++++ test/helpers/compile.js | 11 ++ test/helpers/execute.js | 19 ++ test/helpers/getCodeFromBundle.js | 32 ++++ test/helpers/getCompiler.js | 49 +++++ test/helpers/getErrors.js | 5 + test/helpers/getWarnings.js | 5 + test/helpers/index.js | 21 +++ test/helpers/normalizeErrors.js | 21 +++ test/helpers/readAsset.js | 23 +++ test/helpers/readAssets.js | 11 ++ test/helpers/testLoader.js | 11 ++ test/loader.test.js | 252 +++++++++++++++++++++++++ 13 files changed, 600 insertions(+) create mode 100644 test/__snapshots__/loader.test.js.snap create mode 100644 test/helpers/compile.js create mode 100644 test/helpers/execute.js create mode 100644 test/helpers/getCodeFromBundle.js create mode 100644 test/helpers/getCompiler.js create mode 100644 test/helpers/getErrors.js create mode 100644 test/helpers/getWarnings.js create mode 100644 test/helpers/index.js create mode 100644 test/helpers/normalizeErrors.js create mode 100644 test/helpers/readAsset.js create mode 100644 test/helpers/readAssets.js create mode 100644 test/helpers/testLoader.js create mode 100644 test/loader.test.js diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap new file mode 100644 index 0000000..ea5c876 --- /dev/null +++ b/test/__snapshots__/loader.test.js.snap @@ -0,0 +1,140 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`source-map-loader should leave normal files untouched: css 1`] = `"without SourceMap"`; + +exports[`source-map-loader should leave normal files untouched: errors 1`] = `Array []`; + +exports[`source-map-loader should leave normal files untouched: warnings 1`] = `Array []`; + +exports[`source-map-loader should process external SourceMaps (external sources): css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should process external SourceMaps (external sources): errors 1`] = `Array []`; + +exports[`source-map-loader should process external SourceMaps (external sources): warnings 1`] = `Array []`; + +exports[`source-map-loader should process external SourceMaps: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should process external SourceMaps: errors 1`] = `Array []`; + +exports[`source-map-loader should process external SourceMaps: warnings 1`] = `Array []`; + +exports[`source-map-loader should process inlined SourceMaps with charset: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should process inlined SourceMaps with charset: errors 1`] = `Array []`; + +exports[`source-map-loader should process inlined SourceMaps with charset: warnings 1`] = `Array []`; + +exports[`source-map-loader should process inlined SourceMaps: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should process inlined SourceMaps: errors 1`] = `Array []`; + +exports[`source-map-loader should process inlined SourceMaps: warnings 1`] = `Array []`; + +exports[`source-map-loader should skip invalid base64 SourceMap: css 1`] = ` +"without SourceMap +// @sourceMappingURL=data:application/source-map;base64,\\"something invalid\\" +// comment" +`; + +exports[`source-map-loader should skip invalid base64 SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should skip invalid base64 SourceMap: warnings 1`] = `Array []`; + +exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: errors 1`] = `Array []`; + +exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: warnings 1`] = `Array []`; + +exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: errors 1`] = `Array []`; + +exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: warnings 1`] = `Array []`; + +exports[`source-map-loader should use last SourceMap directive: css 1`] = ` +"with SourceMap +anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\\"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+\\" */\\"; +// comment" +`; + +exports[`source-map-loader should use last SourceMap directive: errors 1`] = `Array []`; + +exports[`source-map-loader should use last SourceMap directive: warnings 1`] = `Array []`; + +exports[`source-map-loader should warn on invalid SourceMap: css 1`] = ` +"with SourceMap +//#sourceMappingURL=invalid-source-map.map +// comment" +`; + +exports[`source-map-loader should warn on invalid SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should warn on invalid SourceMap: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +(Emitted value instead of an instance of Error) Cannot parse SourceMap 'invalid-source-map.map': SyntaxError: Unexpected string in JSON at position 102", +] +`; + +exports[`source-map-loader should warn on invalid base64 SourceMap: css 1`] = ` +"without SourceMap +// @sourceMappingURL=data:application/source-map;base64,invalid/base64= +// comment" +`; + +exports[`source-map-loader should warn on invalid base64 SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should warn on invalid base64 SourceMap: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +(Emitted value instead of an instance of Error) Cannot parse inline SourceMap 'invalid/base64=': SyntaxError: Unexpected token � in JSON at position 0", +] +`; + +exports[`source-map-loader should warn on missing SourceMap: css 1`] = ` +"with SourceMap +//#sourceMappingURL=missing-source-map.map +// comment" +`; + +exports[`source-map-loader should warn on missing SourceMap: errors 1`] = `Array []`; + +exports[`source-map-loader should warn on missing SourceMap: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +(Emitted value instead of an instance of Error) Cannot find SourceMap 'missing-source-map.map': Error: Can't resolve './missing-source-map.map' in '/test/fixtures'", +] +`; + +exports[`source-map-loader should warn on missing source file: css 1`] = ` +"with SourceMap +// comment" +`; + +exports[`source-map-loader should warn on missing source file: errors 1`] = `Array []`; + +exports[`source-map-loader should warn on missing source file: warnings 1`] = ` +Array [ + "ModuleWarning: Module Warning (from \`replaced original path\`): +(Emitted value instead of an instance of Error) Cannot find source file 'missing-source-map2.txt': Error: Can't resolve './missing-source-map2.txt' in '/test/fixtures'", +] +`; diff --git a/test/helpers/compile.js b/test/helpers/compile.js new file mode 100644 index 0000000..066873a --- /dev/null +++ b/test/helpers/compile.js @@ -0,0 +1,11 @@ +export default (compiler) => { + return new Promise((resolve, reject) => { + compiler.run((error, stats) => { + if (error) { + return reject(error); + } + + return resolve(stats); + }); + }); +}; diff --git a/test/helpers/execute.js b/test/helpers/execute.js new file mode 100644 index 0000000..4cb101d --- /dev/null +++ b/test/helpers/execute.js @@ -0,0 +1,19 @@ +import Module from 'module'; +import path from 'path'; + +const parentModule = module; + +export default (code) => { + const resource = 'test.js'; + const module = new Module(resource, parentModule); + // eslint-disable-next-line no-underscore-dangle + module.paths = Module._nodeModulePaths( + path.resolve(__dirname, '../fixtures') + ); + module.filename = resource; + + // eslint-disable-next-line no-underscore-dangle + module._compile(code, resource); + + return module.exports; +}; diff --git a/test/helpers/getCodeFromBundle.js b/test/helpers/getCodeFromBundle.js new file mode 100644 index 0000000..8e3b445 --- /dev/null +++ b/test/helpers/getCodeFromBundle.js @@ -0,0 +1,32 @@ +import vm from 'vm'; + +import readAsset from './readAsset'; + +function getCodeFromBundle(stats, compiler, asset) { + let code = null; + + if ( + stats && + stats.compilation && + stats.compilation.assets && + stats.compilation.assets[asset || 'main.bundle.js'] + ) { + code = readAsset(asset || 'main.bundle.js', compiler, stats); + } + + if (!code) { + throw new Error("Can't find compiled code"); + } + + const result = vm.runInNewContext( + `${code};\nmodule.exports = sourceMapLoaderExport;`, + { + module: {}, + } + ); + + // eslint-disable-next-line no-underscore-dangle + return result.__esModule ? result.default : result; +} + +export default getCodeFromBundle; diff --git a/test/helpers/getCompiler.js b/test/helpers/getCompiler.js new file mode 100644 index 0000000..b391311 --- /dev/null +++ b/test/helpers/getCompiler.js @@ -0,0 +1,49 @@ +import path from 'path'; + +import webpack from 'webpack'; +import { createFsFromVolume, Volume } from 'memfs'; + +export default (fixture, loaderOptions = {}, config = {}) => { + const fullConfig = { + mode: 'development', + devtool: config.devtool || 'source-map', + context: path.resolve(__dirname, '../fixtures'), + entry: path.resolve(__dirname, '../fixtures', fixture), + output: { + path: path.resolve(__dirname, '../outputs'), + filename: '[name].bundle.js', + chunkFilename: '[name].chunk.js', + library: 'sourceMapLoaderExport', + }, + module: { + rules: [ + { + test: /\.js/i, + rules: [ + { + loader: require.resolve('./testLoader'), + }, + { + loader: path.resolve(__dirname, '../../src'), + options: loaderOptions || {}, + }, + ], + }, + ], + }, + plugins: [], + ...config, + }; + + const compiler = webpack(fullConfig); + + if (!config.outputFileSystem) { + const outputFileSystem = createFsFromVolume(new Volume()); + // Todo remove when we drop webpack@4 support + outputFileSystem.join = path.join.bind(path); + + compiler.outputFileSystem = outputFileSystem; + } + + return compiler; +}; diff --git a/test/helpers/getErrors.js b/test/helpers/getErrors.js new file mode 100644 index 0000000..71d940b --- /dev/null +++ b/test/helpers/getErrors.js @@ -0,0 +1,5 @@ +import normalizeErrors from './normalizeErrors'; + +export default (stats) => { + return normalizeErrors(stats.compilation.errors.sort()); +}; diff --git a/test/helpers/getWarnings.js b/test/helpers/getWarnings.js new file mode 100644 index 0000000..f5e5ab1 --- /dev/null +++ b/test/helpers/getWarnings.js @@ -0,0 +1,5 @@ +import normalizeErrors from './normalizeErrors'; + +export default (stats) => { + return normalizeErrors(stats.compilation.warnings.sort()); +}; diff --git a/test/helpers/index.js b/test/helpers/index.js new file mode 100644 index 0000000..576c60a --- /dev/null +++ b/test/helpers/index.js @@ -0,0 +1,21 @@ +import compile from './compile'; +import execute from './execute'; +import getCodeFromBundle from './getCodeFromBundle'; +import getCompiler from './getCompiler'; +import getErrors from './getErrors'; +import getWarnings from './getWarnings'; +import normalizeErrors from './normalizeErrors'; +import readAsset from './readAsset'; +import readsAssets from './readAssets'; + +export { + compile, + execute, + getCodeFromBundle, + getCompiler, + getErrors, + getWarnings, + normalizeErrors, + readAsset, + readsAssets, +}; diff --git a/test/helpers/normalizeErrors.js b/test/helpers/normalizeErrors.js new file mode 100644 index 0000000..1b41f3f --- /dev/null +++ b/test/helpers/normalizeErrors.js @@ -0,0 +1,21 @@ +function removeCWD(str) { + const isWin = process.platform === 'win32'; + let cwd = process.cwd(); + + if (isWin) { + // eslint-disable-next-line no-param-reassign + str = str.replace(/\\/g, '/'); + // eslint-disable-next-line no-param-reassign + cwd = cwd.replace(/\\/g, '/'); + } + + return str + .replace(/\(from .*?\)/, '(from `replaced original path`)') + .replace(new RegExp(cwd, 'g'), ''); +} + +export default (errors) => { + return errors.map((error) => + removeCWD(error.toString().split('\n').slice(0, 2).join('\n')) + ); +}; diff --git a/test/helpers/readAsset.js b/test/helpers/readAsset.js new file mode 100644 index 0000000..8f4699f --- /dev/null +++ b/test/helpers/readAsset.js @@ -0,0 +1,23 @@ +import path from 'path'; + +export default (asset, compiler, stats) => { + const usedFs = compiler.outputFileSystem; + const outputPath = stats.compilation.outputOptions.path; + + let data = ''; + let targetFile = asset; + + const queryStringIdx = targetFile.indexOf('?'); + + if (queryStringIdx >= 0) { + targetFile = targetFile.substr(0, queryStringIdx); + } + + try { + data = usedFs.readFileSync(path.join(outputPath, targetFile)).toString(); + } catch (error) { + data = error.toString(); + } + + return data; +}; diff --git a/test/helpers/readAssets.js b/test/helpers/readAssets.js new file mode 100644 index 0000000..a2fb783 --- /dev/null +++ b/test/helpers/readAssets.js @@ -0,0 +1,11 @@ +import readAsset from './readAsset'; + +export default function readAssets(compiler, stats) { + const assets = {}; + + Object.keys(stats.compilation.assets).forEach((asset) => { + assets[asset] = readAsset(asset, compiler, stats); + }); + + return assets; +} diff --git a/test/helpers/testLoader.js b/test/helpers/testLoader.js new file mode 100644 index 0000000..d2a696c --- /dev/null +++ b/test/helpers/testLoader.js @@ -0,0 +1,11 @@ +function testLoader(content, sourceMap) { + const result = { css: content }; + + if (sourceMap) { + result.map = sourceMap; + } + + return `export default ${JSON.stringify(result)}`; +} + +module.exports = testLoader; diff --git a/test/loader.test.js b/test/loader.test.js new file mode 100644 index 0000000..2f0e4eb --- /dev/null +++ b/test/loader.test.js @@ -0,0 +1,252 @@ +import path from 'path'; +import fs from 'fs'; + +import { + compile, + getCodeFromBundle, + getCompiler, + getErrors, + getWarnings, +} from './helpers'; + +describe('source-map-loader', () => { + it('should leave normal files untouched', async () => { + const testId = 'normal-file.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should process inlined SourceMaps', async () => { + const testId = 'inline-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should process external SourceMaps', async () => { + const testId = 'external-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [ + path.resolve(__dirname, 'fixtures', 'external-source-map.map'), + ]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should process external SourceMaps (external sources)', async () => { + const testId = 'external-source-map2.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [ + path.resolve(__dirname, 'fixtures', 'data', 'external-source-map2.map'), + path.resolve(__dirname, 'fixtures', 'external-source-map2.txt'), + ]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should use last SourceMap directive', async () => { + const testId = 'multi-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should skip invalid base64 SourceMap', async () => { + const testId = 'invalid-inline-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should warn on invalid base64 SourceMap', async () => { + const testId = 'invalid-inline-source-map2.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should warn on invalid SourceMap', async () => { + const testId = 'invalid-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [ + path.resolve(__dirname, 'fixtures', 'invalid-source-map.map'), + ]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should warn on missing SourceMap', async () => { + const testId = 'missing-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeUndefined(); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should warn on missing source file', async () => { + const testId = 'missing-source-map2.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [ + path.resolve(__dirname, 'fixtures', 'missing-source-map2.map'), + ]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should process inlined SourceMaps with charset', async () => { + const testId = 'charset-inline-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should support absolute sourceRoot paths in sourcemaps', async () => { + const sourceRoot = path.resolve(__dirname, 'fixtures'); + const javaScriptFilename = 'absolute-sourceRoot-source-map.js'; + const sourceFilename = 'absolute-sourceRoot-source-map.txt'; + const rootRelativeSourcePath = path.join(sourceRoot, sourceFilename); + const sourceMapPath = path.join( + sourceRoot, + 'absolute-sourceRoot-source-map.map' + ); + + // Create the sourcemap file + const rawSourceMap = { + version: 3, + file: javaScriptFilename, + sourceRoot, + sources: [sourceFilename], + mappings: 'AAAA', + }; + fs.writeFileSync(sourceMapPath, JSON.stringify(rawSourceMap)); + + const compiler = getCompiler(javaScriptFilename); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [sourceMapPath, rootRelativeSourcePath]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); + + it('should support relative sourceRoot paths in sourcemaps', async () => { + const sourceFilename = 'relative-sourceRoot-source-map.txt'; + const rootRelativeSourcePath = path.join( + __dirname, + 'fixtures', + 'data', + sourceFilename + ); + const sourceMapPath = path.join( + __dirname, + 'fixtures', + 'relative-sourceRoot-source-map.map' + ); + + const testId = 'relative-sourceRoot-source-map.js'; + const compiler = getCompiler(testId); + const stats = await compile(compiler); + const codeFromBundle = getCodeFromBundle(stats, compiler); + const deps = stats.compilation.fileDependencies; + + const dependencies = [sourceMapPath, rootRelativeSourcePath]; + + dependencies.forEach((fixture) => { + expect(deps.has(fixture)).toBe(true); + }); + expect(codeFromBundle.map).toBeDefined(); + // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(codeFromBundle.css).toMatchSnapshot('css'); + expect(getWarnings(stats)).toMatchSnapshot('warnings'); + expect(getErrors(stats)).toMatchSnapshot('errors'); + }); +}); From 7c8c456ae42f260607354d9d63c9d0342504a17a Mon Sep 17 00:00:00 2001 From: cap-Bernardito Date: Sat, 16 May 2020 19:15:50 +0300 Subject: [PATCH 2/2] test: refactor tests --- test/__snapshots__/loader.test.js.snap | 116 ++++++++- test/helpers/index.js | 2 + test/helpers/normalizeMap.js | 48 ++++ test/index.test.js | 339 ------------------------- test/loader.test.js | 17 +- 5 files changed, 174 insertions(+), 348 deletions(-) create mode 100644 test/helpers/normalizeMap.js delete mode 100644 test/index.test.js diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap index ea5c876..2d7d17e 100644 --- a/test/__snapshots__/loader.test.js.snap +++ b/test/__snapshots__/loader.test.js.snap @@ -13,6 +13,20 @@ exports[`source-map-loader should process external SourceMaps (external sources) exports[`source-map-loader should process external SourceMaps (external sources): errors 1`] = `Array []`; +exports[`source-map-loader should process external SourceMaps (external sources): map 1`] = ` +Object { + "file": "external-source-map2.js", + "mappings": "AAAA", + "sources": Array [ + "/test/fixtures/external-source-map2.txt - (normalized for test)", + ], + "sourcesContent": Array [ + "with SourceMap", + ], + "version": 3, +} +`; + exports[`source-map-loader should process external SourceMaps (external sources): warnings 1`] = `Array []`; exports[`source-map-loader should process external SourceMaps: css 1`] = ` @@ -22,6 +36,20 @@ exports[`source-map-loader should process external SourceMaps: css 1`] = ` exports[`source-map-loader should process external SourceMaps: errors 1`] = `Array []`; +exports[`source-map-loader should process external SourceMaps: map 1`] = ` +Object { + "file": "external-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "external-source-map.txt", + ], + "sourcesContent": Array [ + "with SourceMap", + ], + "version": 3, +} +`; + exports[`source-map-loader should process external SourceMaps: warnings 1`] = `Array []`; exports[`source-map-loader should process inlined SourceMaps with charset: css 1`] = ` @@ -31,6 +59,20 @@ exports[`source-map-loader should process inlined SourceMaps with charset: css 1 exports[`source-map-loader should process inlined SourceMaps with charset: errors 1`] = `Array []`; +exports[`source-map-loader should process inlined SourceMaps with charset: map 1`] = ` +Object { + "file": "charset-inline-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "charset-inline-source-map.txt", + ], + "sourcesContent": Array [ + "with SourceMap", + ], + "version": 3, +} +`; + exports[`source-map-loader should process inlined SourceMaps with charset: warnings 1`] = `Array []`; exports[`source-map-loader should process inlined SourceMaps: css 1`] = ` @@ -40,6 +82,20 @@ exports[`source-map-loader should process inlined SourceMaps: css 1`] = ` exports[`source-map-loader should process inlined SourceMaps: errors 1`] = `Array []`; +exports[`source-map-loader should process inlined SourceMaps: map 1`] = ` +Object { + "file": "inline-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "inline-source-map.txt", + ], + "sourcesContent": Array [ + "with SourceMap", + ], + "version": 3, +} +`; + exports[`source-map-loader should process inlined SourceMaps: warnings 1`] = `Array []`; exports[`source-map-loader should skip invalid base64 SourceMap: css 1`] = ` @@ -59,6 +115,21 @@ exports[`source-map-loader should support absolute sourceRoot paths in sourcemap exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: errors 1`] = `Array []`; +exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: map 1`] = ` +Object { + "file": "absolute-sourceRoot-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "/test/fixtures/absolute-sourceRoot-source-map.txt - (normalized for test)", + ], + "sourcesContent": Array [ + "with SourceMap +// comment", + ], + "version": 3, +} +`; + exports[`source-map-loader should support absolute sourceRoot paths in sourcemaps: warnings 1`] = `Array []`; exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: css 1`] = ` @@ -68,6 +139,21 @@ exports[`source-map-loader should support relative sourceRoot paths in sourcemap exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: errors 1`] = `Array []`; +exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: map 1`] = ` +Object { + "file": "relative-sourceRoot-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "/test/fixtures/data/relative-sourceRoot-source-map.txt - (normalized for test)", + ], + "sourcesContent": Array [ + "with SourceMap +// comment", + ], + "version": 3, +} +`; + exports[`source-map-loader should support relative sourceRoot paths in sourcemaps: warnings 1`] = `Array []`; exports[`source-map-loader should use last SourceMap directive: css 1`] = ` @@ -78,6 +164,20 @@ anInvalidDirective = \\"\\\\n/*# sourceMappingURL=data:application/json;base64,\ exports[`source-map-loader should use last SourceMap directive: errors 1`] = `Array []`; +exports[`source-map-loader should use last SourceMap directive: map 1`] = ` +Object { + "file": "inline-source-map.js", + "mappings": "AAAA", + "sources": Array [ + "inline-source-map.txt", + ], + "sourcesContent": Array [ + "with SourceMap", + ], + "version": 3, +} +`; + exports[`source-map-loader should use last SourceMap directive: warnings 1`] = `Array []`; exports[`source-map-loader should warn on invalid SourceMap: css 1`] = ` @@ -106,7 +206,7 @@ exports[`source-map-loader should warn on invalid base64 SourceMap: errors 1`] = exports[`source-map-loader should warn on invalid base64 SourceMap: warnings 1`] = ` Array [ "ModuleWarning: Module Warning (from \`replaced original path\`): -(Emitted value instead of an instance of Error) Cannot parse inline SourceMap 'invalid/base64=': SyntaxError: Unexpected token � in JSON at position 0", +(Emitted value instead of an instance of Error) Cannot parse inline SourceMap: data:application/source-map;base64,invalid/base64=", ] `; @@ -132,6 +232,20 @@ exports[`source-map-loader should warn on missing source file: css 1`] = ` exports[`source-map-loader should warn on missing source file: errors 1`] = `Array []`; +exports[`source-map-loader should warn on missing source file: map 1`] = ` +Object { + "file": "missing-source-map2.js", + "mappings": "AAAA", + "sources": Array [ + "missing-source-map2.txt", + ], + "sourcesContent": Array [ + null, + ], + "version": 3, +} +`; + exports[`source-map-loader should warn on missing source file: warnings 1`] = ` Array [ "ModuleWarning: Module Warning (from \`replaced original path\`): diff --git a/test/helpers/index.js b/test/helpers/index.js index 576c60a..6d3e778 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -3,6 +3,7 @@ import execute from './execute'; import getCodeFromBundle from './getCodeFromBundle'; import getCompiler from './getCompiler'; import getErrors from './getErrors'; +import normalizeMap from './normalizeMap'; import getWarnings from './getWarnings'; import normalizeErrors from './normalizeErrors'; import readAsset from './readAsset'; @@ -14,6 +15,7 @@ export { getCodeFromBundle, getCompiler, getErrors, + normalizeMap, getWarnings, normalizeErrors, readAsset, diff --git a/test/helpers/normalizeMap.js b/test/helpers/normalizeMap.js new file mode 100644 index 0000000..e0ea26f --- /dev/null +++ b/test/helpers/normalizeMap.js @@ -0,0 +1,48 @@ +export default (map) => { + const result = map; + + if (result.sources) { + result.sources = normilizeArr(result.sources); + } + + if (result.file) { + [result.file] = normilizeArr([result.file]); + } + + if (result.sourceRoot) { + [result.sourceRoot] = normilizeArr([result.sourceRoot]); + } + + return result; +}; + +function normilizeArr(arr) { + return arr.map((str) => { + const normilized = removeCWD(str); + + if (str === normilized) { + return str; + } + + return `${normilized} - (normalized for test)`; + }); +} + +function removeCWD(str) { + const isWin = process.platform === 'win32'; + let cwd = process.cwd(); + + if (isWin) { + if (str.includes('/')) { + throw new Error( + 'There should not be a forward slash in the Windows path' + ); + } + + // eslint-disable-next-line no-param-reassign + str = str.replace(/\\/g, '/'); + cwd = cwd.replace(/\\/g, '/'); + } + + return str.replace(new RegExp(cwd, 'g'), ''); +} diff --git a/test/index.test.js b/test/index.test.js deleted file mode 100644 index 1dd8183..0000000 --- a/test/index.test.js +++ /dev/null @@ -1,339 +0,0 @@ -import path from 'path'; -import fs from 'fs'; - -import loader from '../src'; - -// eslint-disable-next-line consistent-return -function execLoader(filename, callback) { - let async = false; - const deps = []; - const warns = []; - const context = { - context: path.dirname(filename), - // eslint-disable-next-line no-shadow - resolve(context, request, callback) { - process.nextTick(() => { - const p = path.isAbsolute(request) - ? request - : path.resolve(context, request); - - if (fs.existsSync(p)) { - callback(null, p); - } else { - callback(new Error('File not found')); - } - }); - }, - addDependency(dep) { - deps.push(dep); - }, - emitWarning(warn) { - warns.push(warn); - }, - callback(err, res, map) { - async = true; - callback(err, res, map, deps, warns); - }, - async() { - async = true; - return this.callback; - }, - }; - - // Remove CRs to make test line ending invariant - const fixtureContent = fs.readFileSync(filename, 'utf-8').replace(/\r/g, ''); - const res = loader.call(context, fixtureContent); - - if (!async) { - return callback(null, res, null, deps, warns); - } -} - -describe('source-map-loader', () => { - const fixturesPath = path.resolve(__dirname, 'fixtures'); - const dataPath = path.resolve(fixturesPath, 'data'); - - it('should leave normal files untouched', (done) => { - execLoader( - path.join(fixturesPath, 'normal-file.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('without SourceMap'); - expect(map).toBeUndefined(); - expect(deps).toEqual([]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should process inlined SourceMaps', (done) => { - execLoader( - path.join(fixturesPath, 'inline-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: 'inline-source-map.js', - sources: ['inline-source-map.txt'], - sourcesContent: ['with SourceMap'], - mappings: 'AAAA', - }); - expect(deps).toEqual([]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should process external SourceMaps', (done) => { - execLoader( - path.join(fixturesPath, 'external-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: 'external-source-map.js', - sources: ['external-source-map.txt'], - sourcesContent: ['with SourceMap'], - mappings: 'AAAA', - }); - expect(deps).toEqual([ - path.join(fixturesPath, 'external-source-map.map'), - ]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should process external SourceMaps (external sources)', (done) => { - execLoader( - path.join(fixturesPath, 'external-source-map2.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: 'external-source-map2.js', - sources: [path.join(fixturesPath, 'external-source-map2.txt')], - sourcesContent: ['with SourceMap'], - mappings: 'AAAA', - }); - expect(deps).toEqual([ - path.join(dataPath, 'external-source-map2.map'), - path.join(fixturesPath, 'external-source-map2.txt'), - ]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should use last SourceMap directive', (done) => { - execLoader( - path.join(fixturesPath, 'multi-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe( - 'with SourceMap\nanInvalidDirective = "\\n/*# sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))))+" */";\n// comment' - ); - expect(map).toEqual({ - version: 3, - file: 'inline-source-map.js', - sources: ['inline-source-map.txt'], - sourcesContent: ['with SourceMap'], - mappings: 'AAAA', - }); - expect(deps).toEqual([]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should skip invalid base64 SourceMap', (done) => { - execLoader( - path.join(fixturesPath, 'invalid-inline-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe( - 'without SourceMap\n// @sourceMappingURL=data:application/source-map;base64,"something invalid"\n// comment' - ); - expect(map).toBeUndefined(); - expect(deps).toEqual([]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should warn on invalid base64 SourceMap', (done) => { - execLoader( - path.join(fixturesPath, 'invalid-inline-source-map2.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe( - 'without SourceMap\n// @sourceMappingURL=data:application/source-map;base64,invalid/base64=\n// comment' - ); - expect(map).toBeUndefined(); - expect(deps).toEqual([]); - expect(warns).toEqual([ - 'Cannot parse inline SourceMap: data:application/source-map;base64,invalid/base64=', - ]); - done(); - } - ); - }); - - it('should warn on invalid SourceMap', (done) => { - execLoader( - path.join(fixturesPath, 'invalid-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe( - 'with SourceMap\n//#sourceMappingURL=invalid-source-map.map\n// comment' - ); - expect(map).toBeUndefined(); - expect(deps).toEqual([ - path.join(fixturesPath, 'invalid-source-map.map'), - ]); - expect(warns).toEqual([ - "Cannot parse SourceMap 'invalid-source-map.map': SyntaxError: Unexpected string in JSON at position 102", - ]); - done(); - } - ); - }); - - it('should warn on missing SourceMap', (done) => { - execLoader( - path.join(fixturesPath, 'missing-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe( - 'with SourceMap\n//#sourceMappingURL=missing-source-map.map\n// comment' - ); - expect(map).toBeUndefined(); - expect(deps).toEqual([]); - expect(warns).toEqual([ - "Cannot find SourceMap 'missing-source-map.map': Error: File not found", - ]); - done(); - } - ); - }); - - it('should warn on missing source file', (done) => { - execLoader( - path.join(fixturesPath, 'missing-source-map2.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: 'missing-source-map2.js', - sources: ['missing-source-map2.txt'], - sourcesContent: [null], - mappings: 'AAAA', - }); - expect(deps).toEqual([ - path.join(fixturesPath, 'missing-source-map2.map'), - ]); - expect(warns).toEqual([ - "Cannot find source file 'missing-source-map2.txt': Error: File not found", - ]); - done(); - } - ); - }); - - it('should process inlined SourceMaps with charset', (done) => { - execLoader( - path.join(fixturesPath, 'charset-inline-source-map.js'), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: 'charset-inline-source-map.js', - sources: ['charset-inline-source-map.txt'], - sourcesContent: ['with SourceMap'], - mappings: 'AAAA', - }); - expect(deps).toEqual([]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should support absolute sourceRoot paths in sourcemaps', (done) => { - const sourceRoot = fixturesPath; - const javaScriptFilename = 'absolute-sourceRoot-source-map.js'; - const sourceFilename = 'absolute-sourceRoot-source-map.txt'; - const rootRelativeSourcePath = path.posix.join(sourceRoot, sourceFilename); - const sourceMapPath = path.join( - sourceRoot, - 'absolute-sourceRoot-source-map.map' - ); - - // Create the sourcemap file - const rawSourceMap = { - version: 3, - file: javaScriptFilename, - sourceRoot, - sources: [sourceFilename], - mappings: 'AAAA', - }; - fs.writeFileSync(sourceMapPath, JSON.stringify(rawSourceMap)); - - execLoader( - path.join(fixturesPath, javaScriptFilename), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: javaScriptFilename, - sources: [rootRelativeSourcePath], - sourcesContent: ['with SourceMap\n// comment'], - mappings: 'AAAA', - }); - expect(deps).toEqual([sourceMapPath, rootRelativeSourcePath]); - expect(warns).toEqual([]); - done(); - } - ); - }); - - it('should support relative sourceRoot paths in sourcemaps', (done) => { - const javaScriptFilename = 'relative-sourceRoot-source-map.js'; - const sourceFilename = 'relative-sourceRoot-source-map.txt'; - const rootRelativeSourcePath = path.join(dataPath, sourceFilename); - const sourceMapPath = path.join( - fixturesPath, - 'relative-sourceRoot-source-map.map' - ); - - execLoader( - path.join(fixturesPath, javaScriptFilename), - (err, res, map, deps, warns) => { - expect(err).toBeNull(); - expect(res).toBe('with SourceMap\n// comment'); - expect(map).toEqual({ - version: 3, - file: javaScriptFilename, - sources: [rootRelativeSourcePath], - sourcesContent: ['with SourceMap\n// comment'], - mappings: 'AAAA', - }); - expect(deps).toEqual([sourceMapPath, rootRelativeSourcePath]); - expect(warns).toEqual([]); - done(); - } - ); - }); -}); diff --git a/test/loader.test.js b/test/loader.test.js index 2f0e4eb..bbfcb8d 100644 --- a/test/loader.test.js +++ b/test/loader.test.js @@ -6,6 +6,7 @@ import { getCodeFromBundle, getCompiler, getErrors, + normalizeMap, getWarnings, } from './helpers'; @@ -29,7 +30,7 @@ describe('source-map-loader', () => { const codeFromBundle = getCodeFromBundle(stats, compiler); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -50,7 +51,7 @@ describe('source-map-loader', () => { expect(deps.has(fixture)).toBe(true); }); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -72,7 +73,7 @@ describe('source-map-loader', () => { expect(deps.has(fixture)).toBe(true); }); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -85,7 +86,7 @@ describe('source-map-loader', () => { const codeFromBundle = getCodeFromBundle(stats, compiler); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -162,7 +163,7 @@ describe('source-map-loader', () => { expect(deps.has(fixture)).toBe(true); }); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -175,7 +176,7 @@ describe('source-map-loader', () => { const codeFromBundle = getCodeFromBundle(stats, compiler); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -212,7 +213,7 @@ describe('source-map-loader', () => { expect(deps.has(fixture)).toBe(true); }); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors'); @@ -244,7 +245,7 @@ describe('source-map-loader', () => { expect(deps.has(fixture)).toBe(true); }); expect(codeFromBundle.map).toBeDefined(); - // expect(codeFromBundle.map).toMatchSnapshot('map'); + expect(normalizeMap(codeFromBundle.map)).toMatchSnapshot('map'); expect(codeFromBundle.css).toMatchSnapshot('css'); expect(getWarnings(stats)).toMatchSnapshot('warnings'); expect(getErrors(stats)).toMatchSnapshot('errors');