From 92edab33326cacf2503efa3ce26db828ae272664 Mon Sep 17 00:00:00 2001 From: evilebottnawi Date: Fri, 19 Jul 2019 21:18:02 +0300 Subject: [PATCH] refactor: tests --- README.md | 36 +-- test/CopyPlugin.test.js | 386 +----------------------------- test/transformPath-option.test.js | 213 +++++++++++++++++ test/utils/mocks.js | 59 +++++ test/utils/run.js | 194 +++++++++++++++ 5 files changed, 487 insertions(+), 401 deletions(-) create mode 100644 test/transformPath-option.test.js create mode 100644 test/utils/mocks.js create mode 100644 test/utils/run.js diff --git a/README.md b/README.md index 9623e651..4237189d 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ module.exports = { | [`force`](#force) | `{Boolean}` | `false` | Overwrites files already in `compilation.assets` (usually added by other plugins/loaders). | | [`ignore`](#ignore) | `{Array}` | `[]` | Globs to ignore files. | | [`flatten`](#flatten) | `{Boolean}` | `false` | Removes all directory references and only copies file names. | -| [`transform`](#transform) | `{Function\|Promise}` | `undefined` | Allows to modify the file contents. | | [`cache`](#cache) | `{Boolean\|Object}` | `false` | Enable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. | +| [`transform`](#transform) | `{Function\|Promise}` | `undefined` | Allows to modify the file contents. | | [`transformPath`](#transformPath) | `{Function\|Promise}` | `undefined` | Allows to modify the writing path. | #### `from` @@ -324,14 +324,13 @@ module.exports = { }; ``` -#### `transform` - -Type: `Function|Promise` -Default: `undefined` +#### `cache` -Allows to modify the file contents. +Type: `Boolean|Object` +Default: `false` -##### `{Function}` +Enable/disable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. +Default path to cache directory: `node_modules/.cache/copy-webpack-plugin`. **webpack.config.js** @@ -345,13 +344,21 @@ module.exports = { transform(content, path) { return optimize(content); }, + cache: true, }, ]), ], }; ``` -##### `{Promise}` +#### `transform` + +Type: `Function|Promise` +Default: `undefined` + +Allows to modify the file contents. + +##### `{Function}` **webpack.config.js** @@ -363,7 +370,7 @@ module.exports = { from: 'src/*.png', to: 'dest/', transform(content, path) { - return Promise.resolve(optimize(content)); + return optimize(content); }, }, ]), @@ -371,13 +378,7 @@ module.exports = { }; ``` -#### `cache` - -Type: `Boolean|Object` -Default: `false` - -Enable/disable `transform` caching. You can use `{ cache: { key: 'my-cache-key' } }` to invalidate the cache. -Default path to cache directory: `node_modules/.cache/copy-webpack-plugin`. +##### `{Promise}` **webpack.config.js** @@ -389,9 +390,8 @@ module.exports = { from: 'src/*.png', to: 'dest/', transform(content, path) { - return optimize(content); + return Promise.resolve(optimize(content)); }, - cache: true, }, ]), ], diff --git a/test/CopyPlugin.test.js b/test/CopyPlugin.test.js index a9519edf..d54b8420 100644 --- a/test/CopyPlugin.test.js +++ b/test/CopyPlugin.test.js @@ -2,9 +2,6 @@ import fs from 'fs'; import path from 'path'; import zlib from 'zlib'; -import NodeJsInputFileSystem from 'enhanced-resolve/lib/NodeJsInputFileSystem'; -import CachedInputFileSystem from 'enhanced-resolve/lib/CachedInputFileSystem'; - import findCacheDir from 'find-cache-dir'; import cacache from 'cacache'; import isGzip from 'is-gzip'; @@ -14,244 +11,14 @@ import CopyPlugin from '../src/index'; import removeIllegalCharacterForWindows from './utils/removeIllegalCharacterForWindows'; +import { MockCompiler, MockCompilerNoStat } from './utils/mocks'; +import { run, runEmit, runForce, runChange } from './utils/run'; + const BUILD_DIR = path.join(__dirname, 'build'); const HELPER_DIR = path.join(__dirname, 'helpers'); const TEMP_DIR = path.join(__dirname, 'tempdir'); -class MockCompiler { - constructor(options = {}) { - this.options = { - context: HELPER_DIR, - output: { - path: options.outputPath || BUILD_DIR, - }, - }; - - if (options.devServer && options.devServer.outputPath) { - this.options.devServer = { - outputPath: options.devServer.outputPath, - }; - } - - this.inputFileSystem = new CachedInputFileSystem( - new NodeJsInputFileSystem(), - 0 - ); - - this.hooks = { - emit: { - tapAsync: (plugin, fn) => { - this.hooks.emit = fn; - }, - }, - afterEmit: { - tapAsync: (plugin, fn) => { - this.hooks.afterEmit = fn; - }, - }, - }; - - this.outputFileSystem = { - constructor: { - name: 'NotMemoryFileSystem', - }, - }; - } -} - -class MockCompilerNoStat extends MockCompiler { - constructor(options = {}) { - super(options); - - // eslint-disable-next-line no-undefined - this.inputFileSystem.stat = (file, cb) => cb(undefined, undefined); - } -} - describe('apply function', () => { - // Ideally we pass in patterns and confirm the resulting assets - const run = (opts) => - new Promise((resolve, reject) => { - if (Array.isArray(opts.patterns)) { - opts.patterns.forEach((pattern) => { - if (pattern.context) { - // eslint-disable-next-line no-param-reassign - pattern.context = removeIllegalCharacterForWindows(pattern.context); - } - }); - } - - // Get a mock compiler to pass to plugin.apply - const compiler = opts.compiler || new MockCompiler(); - - const isWin = process.platform === 'win32'; - - if (!opts.symlink || isWin) { - if (!opts.options) { - // eslint-disable-next-line no-param-reassign - opts.options = {}; - } - - if (!opts.options.ignore) { - // eslint-disable-next-line no-param-reassign - opts.options.ignore = []; - } - - opts.options.ignore.push('symlink/**/*', 'file-ln.txt', 'directory-ln'); - } - - new CopyPlugin(opts.patterns, opts.options).apply(compiler); - - // Call the registered function with a mock compilation and callback - const compilation = Object.assign( - { - assets: {}, - errors: [], - warnings: [], - fileDependencies: new Set(), - contextDependencies: new Set(), - }, - opts.compilation - ); - - // Execute the functions in series - return Promise.resolve() - .then( - () => - new Promise((res, rej) => { - try { - compiler.hooks.emit(compilation, res); - } catch (error) { - rej(error); - } - }) - ) - .then( - () => - new Promise((res, rej) => { - try { - compiler.hooks.afterEmit(compilation, res); - } catch (error) { - rej(error); - } - }) - ) - .then(() => { - if (opts.expectedErrors) { - expect(compilation.errors).toEqual(opts.expectedErrors); - } else if (compilation.errors.length > 0) { - throw compilation.errors[0]; - } - - if (opts.expectedWarnings) { - expect(compilation.warnings).toEqual(opts.expectedWarnings); - } else if (compilation.warnings.length > 0) { - throw compilation.warnings[0]; - } - - resolve(compilation); - }) - .catch(reject); - }); - - const runEmit = (opts) => - run(opts).then((compilation) => { - if (opts.skipAssetsTesting) { - return; - } - - if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { - expect(Object.keys(compilation.assets).sort()).toEqual( - opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows) - ); - } else { - expect(compilation.assets).toEqual({}); - } - - if (opts.expectedAssetContent) { - // eslint-disable-next-line guard-for-in - for (const assetName in opts.expectedAssetContent) { - expect(compilation.assets[assetName]).toBeDefined(); - - if (compilation.assets[assetName]) { - let expectedContent = opts.expectedAssetContent[assetName]; - - if (!Buffer.isBuffer(expectedContent)) { - expectedContent = Buffer.from(expectedContent); - } - - let compiledContent = compilation.assets[assetName].source(); - - if (!Buffer.isBuffer(compiledContent)) { - compiledContent = Buffer.from(compiledContent); - } - - expect(Buffer.compare(expectedContent, compiledContent)).toBe(0); - } - } - } - }); - - const runForce = (opts) => { - // eslint-disable-next-line no-param-reassign - opts.compilation = { - assets: {}, - }; - // eslint-disable-next-line no-param-reassign - opts.compilation.assets[opts.existingAsset] = { - source() { - return 'existing'; - }, - }; - - return run(opts).then(() => {}); - }; - - const runChange = (opts) => { - // Create two test files - fs.writeFileSync(opts.newFileLoc1, 'file1contents'); - fs.writeFileSync(opts.newFileLoc2, 'file2contents'); - - // Create a reference to the compiler - const compiler = new MockCompiler(); - const compilation = { - assets: {}, - errors: [], - warnings: [], - fileDependencies: new Set(), - contextDependencies: new Set(), - }; - - return run({ - compiler, - options: opts.options, - patterns: opts.patterns, - }) - .then(() => { - // Change a file - fs.appendFileSync(opts.newFileLoc1, 'extra'); - - // Trigger another compile - return new Promise((res) => { - compiler.hooks.emit(compilation, res); - }); - }) - .then(() => { - if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { - expect(Object.keys(compilation.assets).sort()).toEqual( - opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows) - ); - } else { - expect(compilation.assets).toEqual({}); - } - }) - .then(() => { - // Todo finally and check file exists - fs.unlinkSync(opts.newFileLoc1); - fs.unlinkSync(opts.newFileLoc2); - }); - }; - const specialFiles = { '[special?directory]/nested/nestedfile.txt': '', '[special?directory]/(special-*file).txt': 'special', @@ -434,72 +201,6 @@ describe('apply function', () => { .catch(done); }); - it('can transform target path of every file in glob', (done) => { - runEmit({ - expectedAssetKeys: [ - '/some/path/(special-*file).txt.tst', - '/some/path/binextension.bin.tst', - '/some/path/deepnested.txt.tst', - '/some/path/deepnesteddir.txt.tst', - '/some/path/file.txt.tst', - '/some/path/file.txt.gz.tst', - '/some/path/directoryfile.txt.tst', - '/some/path/nestedfile.txt.tst', - '/some/path/noextension.tst', - '/some/path/hello.txt.tst', - ], - patterns: [ - { - from: '**/*', - transformPath(targetPath, absoluteFrom) { - expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); - - return `/some/path/${path.basename(targetPath)}.tst`; - }, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can transform target path of every file in glob after applying template', (done) => { - runEmit({ - expectedAssetKeys: [ - 'transformed/[!]/hello-d41d8c.txt', - 'transformed/[special?directory]/directoryfile-22af64.txt', - 'transformed/[special?directory]/(special-*file)-0bd650.txt', - 'transformed/[special?directory]/nested/nestedfile-d41d8c.txt', - 'transformed/binextension-d41d8c.bin', - 'transformed/dir (86)/file-d41d8c.txt', - 'transformed/dir (86)/nesteddir/deepnesteddir/deepnesteddir-d41d8c.txt', - 'transformed/dir (86)/nesteddir/nestedfile-d41d8c.txt', - 'transformed/file-22af64.txt', - 'transformed/file.txt-5b311c.gz', - 'transformed/directory/directoryfile-22af64.txt', - 'transformed/directory/nested/deep-nested/deepnested-d41d8c.txt', - 'transformed/directory/nested/nestedfile-d41d8c.txt', - 'transformed/noextension-d41d8c', - ], - patterns: [ - { - from: '**/*', - to: 'nested/[path][name]-[hash:6].[ext]', - transformPath(targetPath, absoluteFrom) { - expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); - - return targetPath.replace( - `nested${path.sep}`, - `transformed${path.sep}` - ); - }, - }, - ], - }) - .then(done) - .catch(done); - }); - it('can use a glob to move multiple files in a different relative context to a non-root directory', (done) => { runEmit({ expectedAssetKeys: [ @@ -780,24 +481,6 @@ describe('apply function', () => { .catch(done); }); - it('can transform target path', (done) => { - runEmit({ - expectedAssetKeys: ['subdir/test.txt'], - patterns: [ - { - from: 'file.txt', - transformPath(targetPath, absoluteFrom) { - expect(absoluteFrom).toBe(path.join(HELPER_DIR, 'file.txt')); - - return targetPath.replace('file.txt', 'subdir/test.txt'); - }, - }, - ], - }) - .then(done) - .catch(done); - }); - it('warns when file not found', (done) => { runEmit({ expectedAssetKeys: [], @@ -855,24 +538,6 @@ describe('apply function', () => { .catch(done); }); - it('warns when tranformPath failed', (done) => { - runEmit({ - expectedAssetKeys: [], - expectedErrors: ['a failure happened'], - patterns: [ - { - from: 'file.txt', - transformPath() { - // eslint-disable-next-line no-throw-literal - throw 'a failure happened'; - }, - }, - ], - }) - .then(done) - .catch(done); - }); - it('warns when pattern is empty', (done) => { runEmit({ expectedAssetKeys: [ @@ -1386,26 +1051,6 @@ describe('apply function', () => { .catch(done); }); - it('transformPath with promise', (done) => { - runEmit({ - expectedAssetKeys: ['/some/path/file.txt'], - patterns: [ - { - from: 'file.txt', - transformPath(targetPath, absoluteFrom) { - expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); - - return new Promise((resolve) => { - resolve(`/some/path/${path.basename(targetPath)}`); - }); - }, - }, - ], - }) - .then(done) - .catch(done); - }); - it('same file to multiple targets', (done) => { runEmit({ expectedAssetKeys: ['first/file.txt', 'second/file.txt'], @@ -1459,31 +1104,6 @@ describe('apply function', () => { .catch(done); }); - it('can transform target path of every file in directory', (done) => { - runEmit({ - expectedAssetKeys: [ - '/some/path/.dottedfile', - '/some/path/deepnested.txt', - '/some/path/directoryfile.txt', - '/some/path/nestedfile.txt', - ], - patterns: [ - { - from: 'directory', - transformPath(targetPath, absoluteFrom) { - expect( - absoluteFrom.includes(path.join(HELPER_DIR, 'directory')) - ).toBe(true); - - return `/some/path/${path.basename(targetPath)}`; - }, - }, - ], - }) - .then(done) - .catch(done); - }); - it("can move a directory's contents to the root directory using from with special characters", (done) => { runEmit({ expectedAssetKeys: [ diff --git a/test/transformPath-option.test.js b/test/transformPath-option.test.js new file mode 100644 index 00000000..3ab1b161 --- /dev/null +++ b/test/transformPath-option.test.js @@ -0,0 +1,213 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('transformPath option', () => { + it('should transform target path when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: ['subdir/test.txt'], + patterns: [ + { + from: 'file.txt', + transformPath(targetPath, absoluteFrom) { + expect(absoluteFrom).toBe(path.join(HELPER_DIR, 'file.txt')); + + return targetPath.replace('file.txt', 'subdir/test.txt'); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path of every when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + '/some/path/.dottedfile', + '/some/path/deepnested.txt', + '/some/path/directoryfile.txt', + '/some/path/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + transformPath(targetPath, absoluteFrom) { + expect( + absoluteFrom.includes(path.join(HELPER_DIR, 'directory')) + ).toBe(true); + + return `/some/path/${path.basename(targetPath)}`; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path of every file when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + '/some/path/(special-*file).txt.tst', + '/some/path/binextension.bin.tst', + '/some/path/deepnested.txt.tst', + '/some/path/deepnesteddir.txt.tst', + '/some/path/file.txt.tst', + '/some/path/file.txt.gz.tst', + '/some/path/directoryfile.txt.tst', + '/some/path/nestedfile.txt.tst', + '/some/path/noextension.tst', + '/some/path/hello.txt.tst', + ], + patterns: [ + { + from: '**/*', + transformPath(targetPath, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return `/some/path/${path.basename(targetPath)}.tst`; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path when function return Promise', (done) => { + runEmit({ + expectedAssetKeys: ['/some/path/file.txt'], + patterns: [ + { + from: 'file.txt', + transformPath(targetPath, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return new Promise((resolve) => { + resolve(`/some/path/${path.basename(targetPath)}`); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path when async function used', (done) => { + runEmit({ + expectedAssetKeys: ['/some/path/file.txt'], + patterns: [ + { + from: 'file.txt', + async transformPath(targetPath, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + const newPath = await new Promise((resolve) => { + resolve(`/some/path/${path.basename(targetPath)}`); + }); + + return newPath; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warns when function throw error', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + transformPath() { + throw new Error('a failure happened'); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warns when Promise was rejected', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + transformPath() { + return new Promise((resolve, reject) => { + return reject(new Error('a failure happened')); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warns when async function throw error', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + async transformPath() { + await new Promise((resolve, reject) => { + reject(new Error('a failure happened')); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path of every file in glob after applying template', (done) => { + runEmit({ + expectedAssetKeys: [ + 'transformed/[!]/hello-d41d8c.txt', + 'transformed/[special?directory]/directoryfile-22af64.txt', + 'transformed/[special?directory]/(special-*file)-0bd650.txt', + 'transformed/[special?directory]/nested/nestedfile-d41d8c.txt', + 'transformed/binextension-d41d8c.bin', + 'transformed/dir (86)/file-d41d8c.txt', + 'transformed/dir (86)/nesteddir/deepnesteddir/deepnesteddir-d41d8c.txt', + 'transformed/dir (86)/nesteddir/nestedfile-d41d8c.txt', + 'transformed/file-22af64.txt', + 'transformed/file.txt-5b311c.gz', + 'transformed/directory/directoryfile-22af64.txt', + 'transformed/directory/nested/deep-nested/deepnested-d41d8c.txt', + 'transformed/directory/nested/nestedfile-d41d8c.txt', + 'transformed/noextension-d41d8c', + ], + patterns: [ + { + from: '**/*', + to: 'nested/[path][name]-[hash:6].[ext]', + transformPath(targetPath, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return targetPath.replace( + `nested${path.sep}`, + `transformed${path.sep}` + ); + }, + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/utils/mocks.js b/test/utils/mocks.js new file mode 100644 index 00000000..358d2e53 --- /dev/null +++ b/test/utils/mocks.js @@ -0,0 +1,59 @@ +import path from 'path'; + +import CachedInputFileSystem from 'enhanced-resolve/lib/CachedInputFileSystem'; +import NodeJsInputFileSystem from 'enhanced-resolve/lib/NodeJsInputFileSystem'; + +const BUILD_DIR = path.join(__dirname, '../build'); +const HELPER_DIR = path.join(__dirname, '../helpers'); + +class MockCompiler { + constructor(options = {}) { + this.options = { + context: HELPER_DIR, + output: { + path: options.outputPath || BUILD_DIR, + }, + }; + + if (options.devServer && options.devServer.outputPath) { + this.options.devServer = { + outputPath: options.devServer.outputPath, + }; + } + + this.inputFileSystem = new CachedInputFileSystem( + new NodeJsInputFileSystem(), + 0 + ); + + this.hooks = { + emit: { + tapAsync: (plugin, fn) => { + this.hooks.emit = fn; + }, + }, + afterEmit: { + tapAsync: (plugin, fn) => { + this.hooks.afterEmit = fn; + }, + }, + }; + + this.outputFileSystem = { + constructor: { + name: 'NotMemoryFileSystem', + }, + }; + } +} + +class MockCompilerNoStat extends MockCompiler { + constructor(options = {}) { + super(options); + + // eslint-disable-next-line no-undefined + this.inputFileSystem.stat = (file, cb) => cb(undefined, undefined); + } +} + +export { MockCompiler, MockCompilerNoStat }; diff --git a/test/utils/run.js b/test/utils/run.js new file mode 100644 index 00000000..17cf14f1 --- /dev/null +++ b/test/utils/run.js @@ -0,0 +1,194 @@ +// Ideally we pass in patterns and confirm the resulting assets +import fs from 'fs'; + +import CopyPlugin from '../../src'; + +import removeIllegalCharacterForWindows from './removeIllegalCharacterForWindows'; + +import { MockCompiler } from './mocks'; + +function run(opts) { + return new Promise((resolve, reject) => { + if (Array.isArray(opts.patterns)) { + opts.patterns.forEach((pattern) => { + if (pattern.context) { + // eslint-disable-next-line no-param-reassign + pattern.context = removeIllegalCharacterForWindows(pattern.context); + } + }); + } + + // Get a mock compiler to pass to plugin.apply + const compiler = opts.compiler || new MockCompiler(); + + const isWin = process.platform === 'win32'; + + if (!opts.symlink || isWin) { + if (!opts.options) { + // eslint-disable-next-line no-param-reassign + opts.options = {}; + } + + if (!opts.options.ignore) { + // eslint-disable-next-line no-param-reassign + opts.options.ignore = []; + } + + opts.options.ignore.push('symlink/**/*', 'file-ln.txt', 'directory-ln'); + } + + new CopyPlugin(opts.patterns, opts.options).apply(compiler); + + // Call the registered function with a mock compilation and callback + const compilation = Object.assign( + { + assets: {}, + errors: [], + warnings: [], + fileDependencies: new Set(), + contextDependencies: new Set(), + }, + opts.compilation + ); + + // Execute the functions in series + return Promise.resolve() + .then( + () => + new Promise((res, rej) => { + try { + compiler.hooks.emit(compilation, res); + } catch (error) { + rej(error); + } + }) + ) + .then( + () => + new Promise((res, rej) => { + try { + compiler.hooks.afterEmit(compilation, res); + } catch (error) { + rej(error); + } + }) + ) + .then(() => { + if (opts.expectedErrors) { + expect(compilation.errors).toEqual(opts.expectedErrors); + } else if (compilation.errors.length > 0) { + throw compilation.errors[0]; + } + + if (opts.expectedWarnings) { + expect(compilation.warnings).toEqual(opts.expectedWarnings); + } else if (compilation.warnings.length > 0) { + throw compilation.warnings[0]; + } + + resolve(compilation); + }) + .catch(reject); + }); +} + +function runEmit(opts) { + return run(opts).then((compilation) => { + if (opts.skipAssetsTesting) { + return; + } + + if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { + expect(Object.keys(compilation.assets).sort()).toEqual( + opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows) + ); + } else { + expect(compilation.assets).toEqual({}); + } + + if (opts.expectedAssetContent) { + // eslint-disable-next-line guard-for-in + for (const assetName in opts.expectedAssetContent) { + expect(compilation.assets[assetName]).toBeDefined(); + + if (compilation.assets[assetName]) { + let expectedContent = opts.expectedAssetContent[assetName]; + + if (!Buffer.isBuffer(expectedContent)) { + expectedContent = Buffer.from(expectedContent); + } + + let compiledContent = compilation.assets[assetName].source(); + + if (!Buffer.isBuffer(compiledContent)) { + compiledContent = Buffer.from(compiledContent); + } + + expect(Buffer.compare(expectedContent, compiledContent)).toBe(0); + } + } + } + }); +} + +function runForce(opts) { + // eslint-disable-next-line no-param-reassign + opts.compilation = { + assets: {}, + }; + // eslint-disable-next-line no-param-reassign + opts.compilation.assets[opts.existingAsset] = { + source() { + return 'existing'; + }, + }; + + return run(opts).then(() => {}); +} + +function runChange(opts) { + // Create two test files + fs.writeFileSync(opts.newFileLoc1, 'file1contents'); + fs.writeFileSync(opts.newFileLoc2, 'file2contents'); + + // Create a reference to the compiler + const compiler = new MockCompiler(); + const compilation = { + assets: {}, + errors: [], + warnings: [], + fileDependencies: new Set(), + contextDependencies: new Set(), + }; + + return run({ + compiler, + options: opts.options, + patterns: opts.patterns, + }) + .then(() => { + // Change a file + fs.appendFileSync(opts.newFileLoc1, 'extra'); + + // Trigger another compile + return new Promise((res) => { + compiler.hooks.emit(compilation, res); + }); + }) + .then(() => { + if (opts.expectedAssetKeys && opts.expectedAssetKeys.length > 0) { + expect(Object.keys(compilation.assets).sort()).toEqual( + opts.expectedAssetKeys.sort().map(removeIllegalCharacterForWindows) + ); + } else { + expect(compilation.assets).toEqual({}); + } + }) + .then(() => { + // Todo finally and check file exists + fs.unlinkSync(opts.newFileLoc1); + fs.unlinkSync(opts.newFileLoc2); + }); +} + +export { run, runChange, runEmit, runForce };