From f848140605ee3ff96d723b2e8d63da28a6be5657 Mon Sep 17 00:00:00 2001 From: Evilebot Tnawi Date: Fri, 26 Jul 2019 13:23:30 +0300 Subject: [PATCH] test: refactor (#394) --- .gitignore | 1 + globalSetup.js | 26 + jest.config.js | 3 + src/processPattern.js | 2 + test/CopyPlugin.test.js | 2052 ++++--------------------- test/cache-option.test.js | 250 +++ test/context-option.test.js | 269 ++++ test/flatten-option.test.js | 147 ++ test/force-option.test.js | 237 +++ test/from-option.test.js | 522 +++++++ test/globOptions-option.test.js | 35 + test/helpers/watch/.gitkeep | 0 test/helpers/watch/directory/.gitkeep | 0 test/ignore-option.test.js | 186 +++ test/test-option.test.js | 73 + test/to-option.test.js | 452 ++++++ test/toType-option.test.js | 107 ++ test/transform-option.test.js | 184 +++ test/transformPath-option.test.js | 28 +- test/utils/run.js | 26 +- 20 files changed, 2819 insertions(+), 1781 deletions(-) create mode 100644 globalSetup.js create mode 100644 jest.config.js create mode 100644 test/cache-option.test.js create mode 100644 test/context-option.test.js create mode 100644 test/flatten-option.test.js create mode 100644 test/force-option.test.js create mode 100644 test/from-option.test.js create mode 100644 test/globOptions-option.test.js create mode 100644 test/helpers/watch/.gitkeep create mode 100644 test/helpers/watch/directory/.gitkeep create mode 100644 test/ignore-option.test.js create mode 100644 test/test-option.test.js create mode 100644 test/to-option.test.js create mode 100644 test/toType-option.test.js create mode 100644 test/transform-option.test.js diff --git a/.gitignore b/.gitignore index 9b615cde..953a7f6a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ npm-debug.log* /local /reports /node_modules +/test/helpers/\[special\?directory\] .DS_Store Thumbs.db diff --git a/globalSetup.js b/globalSetup.js new file mode 100644 index 00000000..f28c4be3 --- /dev/null +++ b/globalSetup.js @@ -0,0 +1,26 @@ +const path = require('path'); +const fs = require('fs'); + +// eslint-disable-next-line import/no-extraneous-dependencies +const mkdirp = require('mkdirp'); + +const removeIllegalCharacterForWindows = require('./test/utils/removeIllegalCharacterForWindows'); + +const baseDir = path.resolve(__dirname, 'test/helpers'); + +const specialFiles = { + '[special?directory]/nested/nestedfile.txt': '', + '[special?directory]/(special-*file).txt': 'special', + '[special?directory]/directoryfile.txt': 'new', +}; + +module.exports = () => { + Object.keys(specialFiles).forEach((originFile) => { + const file = removeIllegalCharacterForWindows(originFile); + const dir = path.dirname(file); + + mkdirp.sync(path.join(baseDir, dir)); + + fs.writeFileSync(path.join(baseDir, file), specialFiles[originFile]); + }); +}; diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..cd7a966d --- /dev/null +++ b/jest.config.js @@ -0,0 +1,3 @@ +module.exports = { + globalSetup: '/globalSetup.js', +}; diff --git a/src/processPattern.js b/src/processPattern.js index 6cec72ba..447f3aa2 100644 --- a/src/processPattern.js +++ b/src/processPattern.js @@ -12,6 +12,8 @@ export default function processPattern(globalRef, pattern) { { cwd: pattern.context, follow: true, + // Todo in next major release + // dot: false }, pattern.globOptions || {} ); diff --git a/test/CopyPlugin.test.js b/test/CopyPlugin.test.js index d54b8420..69a98152 100644 --- a/test/CopyPlugin.test.js +++ b/test/CopyPlugin.test.js @@ -1,111 +1,21 @@ -import fs from 'fs'; import path from 'path'; -import zlib from 'zlib'; - -import findCacheDir from 'find-cache-dir'; -import cacache from 'cacache'; -import isGzip from 'is-gzip'; -import mkdirp from 'mkdirp'; import CopyPlugin from '../src/index'; -import removeIllegalCharacterForWindows from './utils/removeIllegalCharacterForWindows'; - -import { MockCompiler, MockCompilerNoStat } from './utils/mocks'; -import { run, runEmit, runForce, runChange } from './utils/run'; +import { MockCompiler } from './utils/mocks'; +import { run, runEmit, 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'); describe('apply function', () => { - const specialFiles = { - '[special?directory]/nested/nestedfile.txt': '', - '[special?directory]/(special-*file).txt': 'special', - '[special?directory]/directoryfile.txt': 'new', - }; - - const baseDir = path.join(__dirname, 'helpers'); - - beforeAll(() => { - Object.keys(specialFiles).forEach((originFile) => { - const file = removeIllegalCharacterForWindows(originFile); - const dir = path.dirname(file); - - mkdirp.sync(path.join(baseDir, dir)); - - fs.writeFileSync(path.join(baseDir, file), specialFiles[originFile]); - }); - }); - - // Use then and catch explicitly, so errors - // aren't seen as unhandled exceptions - describe('error handling', () => { - it("doesn't throw an error if no patterns are passed", (done) => { - runEmit({ - expectedAssetKeys: [], - patterns: undefined, // eslint-disable-line no-undefined - }) - .then(done) - .catch(done); - }); - - it('throws an error if the patterns are an object', () => { - const createPluginWithObject = () => { - // eslint-disable-next-line no-new - new CopyPlugin({}); - }; - - expect(createPluginWithObject).toThrow(Error); - }); - - it('throws an error if the patterns are null', () => { - const createPluginWithNull = () => { - // eslint-disable-next-line no-new - new CopyPlugin(null); - }; - - expect(createPluginWithNull).toThrow(Error); - }); - - it('throws an error if the "from" path is an empty string', () => { - const createPluginWithNull = () => { - // eslint-disable-next-line no-new - new CopyPlugin({ - from: '', - }); - }; - - expect(createPluginWithNull).toThrow(Error); - }); - }); - - describe('with glob in from', () => { - it('can use a glob to move a file to the root directory', (done) => { + describe('basic', () => { + it('should copy a file', (done) => { runEmit({ expectedAssetKeys: ['file.txt'], patterns: [ { - from: '*.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can use a bracketed glob to move a file to the root directory', (done) => { - runEmit({ - expectedAssetKeys: [ - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - 'file.txt', - 'noextension', - ], - patterns: [ - { - from: '{file.txt,noextension,directory/**/*}', + from: 'file.txt', }, ], }) @@ -113,14 +23,13 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob object to move a file to the root directory', (done) => { + it('should copy a file to a new file', (done) => { runEmit({ - expectedAssetKeys: ['file.txt'], + expectedAssetKeys: ['newfile.txt'], patterns: [ { - from: { - glob: '*.txt', - }, + from: 'file.txt', + to: 'newfile.txt', }, ], }) @@ -128,15 +37,14 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob object to move a file to the root directory and respect glob options', (done) => { + it('should copy a file to a new file with context', (done) => { runEmit({ - expectedAssetKeys: ['file.txt'], + expectedAssetKeys: ['newfile.txt'], patterns: [ { - from: { - glob: '*.txt', - dot: false, - }, + from: 'directoryfile.txt', + context: 'directory', + to: 'newfile.txt', }, ], }) @@ -144,27 +52,17 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to move multiple files to the root directory', (done) => { + it('should copy files', (done) => { runEmit({ expectedAssetKeys: [ - '[!]/hello.txt', - 'binextension.bin', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - 'file.txt', - 'file.txt.gz', - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/nested/nestedfile.txt', - 'noextension', + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', ], patterns: [ { - from: '**/*', + from: 'directory', }, ], }) @@ -172,28 +70,18 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to move multiple files to a non-root directory', (done) => { + it('should copy files to new directory', (done) => { runEmit({ expectedAssetKeys: [ - 'nested/[!]/hello.txt', - 'nested/binextension.bin', - 'nested/dir (86)/file.txt', - 'nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'nested/dir (86)/nesteddir/nestedfile.txt', - 'nested/file.txt', - 'nested/file.txt.gz', - 'nested/directory/directoryfile.txt', - 'nested/directory/nested/deep-nested/deepnested.txt', - 'nested/directory/nested/nestedfile.txt', - 'nested/[special?directory]/directoryfile.txt', - 'nested/[special?directory]/(special-*file).txt', - 'nested/[special?directory]/nested/nestedfile.txt', - 'nested/noextension', + 'newdirectory/.dottedfile', + 'newdirectory/directoryfile.txt', + 'newdirectory/nested/deep-nested/deepnested.txt', + 'newdirectory/nested/nestedfile.txt', ], patterns: [ { - from: '**/*', - to: 'nested', + from: 'directory', + to: 'newdirectory', }, ], }) @@ -201,18 +89,17 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to move multiple files in a different relative context to a non-root directory', (done) => { + it('should copy files to new directory with context', (done) => { runEmit({ expectedAssetKeys: [ - 'nested/directoryfile.txt', - 'nested/nested/deep-nested/deepnested.txt', - 'nested/nested/nestedfile.txt', + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', ], patterns: [ { + from: 'nested', context: 'directory', - from: '**/*', - to: 'nested', + to: 'newdirectory', }, ], }) @@ -220,17 +107,16 @@ describe('apply function', () => { .catch(done); }); - it('can use a direct glob to move multiple files in a different relative context with special characters', (done) => { + it('should copy files using glob', (done) => { runEmit({ expectedAssetKeys: [ - 'directoryfile.txt', - '(special-*file).txt', - 'nested/nestedfile.txt', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', ], patterns: [ { - context: '[special?directory]', - from: { glob: '**/*' }, + from: 'directory/**/*', }, ], }) @@ -238,17 +124,17 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to move multiple files in a different relative context with special characters', (done) => { + it('should copy files using glob to new directory', (done) => { runEmit({ expectedAssetKeys: [ - 'directoryfile.txt', - '(special-*file).txt', - 'nested/nestedfile.txt', + 'newdirectory/directory/directoryfile.txt', + 'newdirectory/directory/nested/deep-nested/deepnested.txt', + 'newdirectory/directory/nested/nestedfile.txt', ], patterns: [ { - context: '[special?directory]', - from: '**/*', + from: 'directory/**/*', + to: 'newdirectory', }, ], }) @@ -256,19 +142,17 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to flatten multiple files in a relative context to a non-root directory', (done) => { + it('should copy files using glob to new directory with context', (done) => { runEmit({ expectedAssetKeys: [ - 'nested/deepnested.txt', - 'nested/directoryfile.txt', - 'nested/nestedfile.txt', + 'newdirectory/nested/deep-nested/deepnested.txt', + 'newdirectory/nested/nestedfile.txt', ], patterns: [ { + from: 'nested/**/*', context: 'directory', - flatten: true, - from: '**/*', - to: 'nested', + to: 'newdirectory', }, ], }) @@ -276,18 +160,13 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob to move multiple files in a different absolute context to a non-root directory', (done) => { + it('should copy a file to a new file', (done) => { runEmit({ - expectedAssetKeys: [ - 'nested/directoryfile.txt', - 'nested/nested/deep-nested/deepnested.txt', - 'nested/nested/nestedfile.txt', - ], + expectedAssetKeys: ['newfile.txt'], patterns: [ { - context: path.join(HELPER_DIR, 'directory'), - from: '**/*', - to: 'nested', + from: 'file.txt', + to: 'newfile.txt', }, ], }) @@ -295,12 +174,14 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob with a full path to move a file to the root directory', (done) => { + it('should copy a file to a new file with context', (done) => { runEmit({ - expectedAssetKeys: ['file.txt'], + expectedAssetKeys: ['newfile.txt'], patterns: [ { - from: path.join(HELPER_DIR, '*.txt'), + from: 'directoryfile.txt', + context: 'directory', + to: 'newfile.txt', }, ], }) @@ -308,53 +189,17 @@ describe('apply function', () => { .catch(done); }); - it('can use a glob with a full path to move multiple files to the root directory', (done) => { + it('should multiple files to a new file', (done) => { runEmit({ - expectedAssetKeys: [ - '[!]/hello.txt', - 'file.txt', - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/nested/nestedfile.txt', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - ], + expectedAssetKeys: ['newfile.txt', 'newbinextension.bin'], patterns: [ { - from: path.join(HELPER_DIR, '**/*.txt'), + from: 'file.txt', + to: 'newfile.txt', }, - ], - }) - .then(done) - .catch(done); - }); - - it('can use a glob to move multiple files to a non-root directory with name, hash and ext', (done) => { - runEmit({ - expectedAssetKeys: [ - 'nested/[!]/hello-d41d8c.txt', - 'nested/binextension-d41d8c.bin', - 'nested/dir (86)/file-d41d8c.txt', - 'nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir-d41d8c.txt', - 'nested/dir (86)/nesteddir/nestedfile-d41d8c.txt', - 'nested/file-22af64.txt', - 'nested/file.txt-5b311c.gz', - 'nested/directory/directoryfile-22af64.txt', - 'nested/directory/nested/deep-nested/deepnested-d41d8c.txt', - 'nested/directory/nested/nestedfile-d41d8c.txt', - 'nested/[special?directory]/(special-*file)-0bd650.txt', - 'nested/[special?directory]/directoryfile-22af64.txt', - 'nested/[special?directory]/nested/nestedfile-d41d8c.txt', - 'nested/noextension-d41d8c', - ], - patterns: [ { - from: '**/*', - to: 'nested/[path][name]-[hash:6].[ext]', + from: 'binextension.bin', + to: 'newbinextension.bin', }, ], }) @@ -362,118 +207,60 @@ describe('apply function', () => { .catch(done); }); - it('can flatten or normalize glob matches', (done) => { + it('should copy multiple files with same "from"', (done) => { runEmit({ - expectedAssetKeys: [ - '[!]-hello.txt', - '[special?directory]-(special-*file).txt', - '[special?directory]-directoryfile.txt', - 'dir (86)-file.txt', - 'directory-directoryfile.txt', - ], + expectedAssetKeys: ['first/file.txt', 'second/file.txt'], patterns: [ { - from: '*/*.*', - test: `([^\\${path.sep}]+)\\${path.sep}([^\\${path.sep}]+)\\.\\w+$`, - to: '[1]-[2].[ext]', + from: 'file.txt', + to: 'first/file.txt', }, - ], - }) - .then(done) - .catch(done); - }); - - it('adds the context directory to the watch list when using glob', (done) => { - run({ - patterns: [ { - from: 'directory/**/*', + from: 'file.txt', + to: 'second/file.txt', }, ], }) - .then((compilation) => { - expect( - Array.from(compilation.contextDependencies) - .map((contextDependency) => contextDependency) - .sort() - ).toEqual([path.join(HELPER_DIR, 'directory')].sort()); - }) .then(done) .catch(done); }); - it('does not add the directory to the watch list when glob is a file', (done) => { - run({ - patterns: [ - { - from: { - glob: 'directory/directoryfile.txt', - }, - }, - ], + it('should works with multiple patterns as String', (done) => { + runEmit({ + expectedAssetKeys: ['binextension.bin', 'file.txt', 'noextension'], + patterns: ['binextension.bin', 'file.txt', 'noextension'], }) - .then((compilation) => { - const absFrom = path.resolve(HELPER_DIR, 'directory'); - expect(compilation.contextDependencies).not.toContain(absFrom); - }) .then(done) .catch(done); }); - it('can use a glob to move a file to the root directory from symbolic link', (done) => { + it('should works with multiple patterns as Object', (done) => { runEmit({ - // Windows doesn't support symbolic link - symlink: true, - expectedAssetKeys: - process.platform === 'win32' - ? [] - : [ - 'symlink/directory-ln/file.txt', - 'symlink/directory-ln/nested-directory/file-in-nested-directory.txt', - 'symlink/directory/file.txt', - 'symlink/directory/nested-directory/file-in-nested-directory.txt', - 'symlink/file-ln.txt', - 'symlink/file.txt', - ], + expectedAssetKeys: ['binextension.bin', 'file.txt', 'noextension'], patterns: [ { - from: 'symlink/**/*.txt', + from: 'binextension.bin', }, - ], - }) - .then(done) - .catch(done); - }); - }); - - describe('with file in from', () => { - it('can move a file to the root directory', (done) => { - runEmit({ - expectedAssetKeys: ['file.txt'], - patterns: [ { from: 'file.txt', }, + { + from: 'noextension', + }, ], }) .then(done) .catch(done); }); + }); - it('can transform a file', (done) => { + describe('difference path segment separation', () => { + it('should work with linux path segment separation path when "from" is glob', (done) => { runEmit({ - expectedAssetKeys: ['file.txt'], - expectedAssetContent: { - 'file.txt': 'newchanged', - }, + expectedAssetKeys: ['directory/nested/nestedfile.txt'], patterns: [ { - from: 'file.txt', - transform(content, absoluteFrom) { - expect(absoluteFrom).toBe(path.join(HELPER_DIR, 'file.txt')); - - return `${content}changed`; - }, + from: 'directory/nested/*', }, ], }) @@ -481,17 +268,14 @@ describe('apply function', () => { .catch(done); }); - it('warns when file not found', (done) => { + it('should work with windows path segment separation path when "from" is glob', (done) => { runEmit({ - expectedAssetKeys: [], - expectedWarnings: [ - new Error( - `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${path.sep}nonexistent.txt'` - ), - ], + expectedAssetKeys: ['directory/nested/nestedfile.txt'], patterns: [ { - from: 'nonexistent.txt', + from: { + glob: 'directory\\nested\\*', + }, }, ], }) @@ -499,20 +283,14 @@ describe('apply function', () => { .catch(done); }); - it('warns when file not found and stats is undefined', (done) => { + it('should work with mixed path segment separation path when "from" is glob', (done) => { runEmit({ - compiler: new MockCompilerNoStat(), - expectedAssetKeys: [], - expectedWarnings: [ - new Error( - `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${path.sep}nonexistent.txt'` - ), - ], + expectedAssetKeys: ['directory/nested/nestedfile.txt'], patterns: [ { - from: 'nonexistent.txt', - to: '.', - toType: 'dir', + from: { + glob: 'directory/nested\\*', + }, }, ], }) @@ -520,17 +298,20 @@ describe('apply function', () => { .catch(done); }); - it('warns when tranform failed', (done) => { + it('should exclude path with linux path segment separators', (done) => { runEmit({ - expectedAssetKeys: [], - expectedErrors: ['a failure happened'], + expectedAssetKeys: [ + '[!]/hello.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/nested/nestedfile.txt', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + ], patterns: [ { - from: 'file.txt', - transform() { - // eslint-disable-next-line no-throw-literal - throw 'a failure happened'; - }, + from: '!(directory)/**/*.txt', }, ], }) @@ -538,855 +319,113 @@ describe('apply function', () => { .catch(done); }); - it('warns when pattern is empty', (done) => { + it('should exclude path with windows path segment separators', (done) => { runEmit({ expectedAssetKeys: [ - '.file.txt', '[!]/hello.txt', '[special?directory]/(special-*file).txt', '[special?directory]/directoryfile.txt', '[special?directory]/nested/nestedfile.txt', - 'binextension.bin', 'dir (86)/file.txt', 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', 'dir (86)/nesteddir/nestedfile.txt', - 'directory/.dottedfile', - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - 'file.txt', - 'file.txt.gz', - 'noextension', ], - expectedErrors: [new Error(`path "from" cannot be empty string`)], patterns: [ { - from: '', + from: '!(directory)\\**\\*.txt', }, ], }) .then(done) .catch(done); }); + }); - it('can use an absolute path to move a file to the root directory', (done) => { - const absolutePath = path.resolve(HELPER_DIR, 'file.txt'); - + describe('errors', () => { + it('should not throw an error if no patterns are passed', (done) => { runEmit({ - expectedAssetKeys: ['file.txt'], - patterns: [ - { - from: absolutePath, - }, - ], + expectedAssetKeys: [], + patterns: undefined, // eslint-disable-line no-undefined }) .then(done) .catch(done); }); - it('can move a file to a new directory without a forward slash', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory/file.txt'], - patterns: [ - { - from: 'file.txt', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to the root directory using an absolute to', (done) => { - runEmit({ - expectedAssetKeys: ['file.txt'], - patterns: [ - { - from: 'file.txt', - to: BUILD_DIR, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('allows absolute to if outpath is defined with webpack-dev-server', (done) => { - runEmit({ - compiler: new MockCompiler({ - outputPath: '/', - devServer: { - outputPath: BUILD_DIR, - }, - }), - expectedAssetKeys: ['file.txt'], - patterns: [ - { - from: 'file.txt', - to: BUILD_DIR, - }, - ], - }) - .then(done) - .catch(done); - }); - - it("throws an error when output path isn't defined with webpack-dev-server", (done) => { - runEmit({ - compiler: new MockCompiler({ - outputPath: '/', - }), - skipAssetsTesting: true, - expectedErrors: [ - new Error( - 'using older versions of webpack-dev-server, devServer.outputPath must be defined to write to absolute paths' - ), - ], - patterns: [ - { - from: 'file.txt', - to: BUILD_DIR, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new directory using an absolute to', (done) => { - runEmit({ - expectedAssetKeys: ['../tempdir/file.txt'], - patterns: [ - { - from: 'file.txt', - to: TEMP_DIR, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new file using an absolute to', (done) => { - const absolutePath = path.resolve(TEMP_DIR, 'newfile.txt'); - - runEmit({ - expectedAssetKeys: ['../tempdir/newfile.txt'], - patterns: [ - { - from: 'file.txt', - to: absolutePath, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new directory with a forward slash', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory/file.txt'], - patterns: [ - { - from: 'file.txt', - to: 'newdirectory/', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file with a context containing special characters', (done) => { - runEmit({ - expectedAssetKeys: ['directoryfile.txt'], - patterns: [ - { - from: 'directoryfile.txt', - context: '[special?directory]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file with special characters with a context containing special characters', (done) => { - runEmit({ - expectedAssetKeys: ['(special-*file).txt'], - patterns: [ - { - from: '(special-*file).txt', - context: '[special?directory]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new directory with an extension', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory.ext/file.txt'], - patterns: [ - { - from: 'file.txt', - to: 'newdirectory.ext', - toType: 'dir', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new directory with an extension and path separator at end', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory.ext/file.txt'], - patterns: [ - { - from: 'file.txt', - to: `newdirectory.ext${path.sep}`, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new file with a different name', (done) => { - runEmit({ - expectedAssetKeys: ['newname.txt'], - patterns: [ - { - from: 'file.txt', - to: 'newname.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file to a new file with no extension', (done) => { - runEmit({ - expectedAssetKeys: ['newname'], - patterns: [ - { - from: 'file.txt', - to: 'newname', - toType: 'file', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file without an extension to a file using a template', (done) => { - runEmit({ - expectedAssetKeys: ['noextension.newext'], - patterns: [ - { - from: 'noextension', - to: '[name][ext].newext', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file with a ".bin" extension using a template', (done) => { - runEmit({ - expectedAssetKeys: ['binextension.bin'], - patterns: [ - { - from: 'binextension.bin', - to: '[name].[ext]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a nested file to the root directory', (done) => { - runEmit({ - expectedAssetKeys: ['directoryfile.txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can use an absolute path to move a nested file to the root directory', (done) => { - const absolutePath = path.resolve( - HELPER_DIR, - 'directory', - 'directoryfile.txt' - ); - - runEmit({ - expectedAssetKeys: ['directoryfile.txt'], - patterns: [ - { - from: absolutePath, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a nested file to a new directory', (done) => { - runEmit({ - expectedAssetKeys: ['newdirectory/directoryfile.txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can use an absolute path to move a nested file to a new directory', (done) => { - const absolutePath = path.resolve( - HELPER_DIR, - 'directory', - 'directoryfile.txt' - ); - - runEmit({ - expectedAssetKeys: ['newdirectory/directoryfile.txt'], - patterns: [ - { - from: absolutePath, - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it("won't overwrite a file already in the compilation", (done) => { - runForce({ - existingAsset: 'file.txt', - expectedAssetContent: { - 'file.txt': 'existing', - }, - patterns: [ - { - from: 'file.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can force overwrite of a file already in the compilation', (done) => { - runForce({ - existingAsset: 'file.txt', - expectedAssetContent: { - 'file.txt': 'new', - }, - patterns: [ - { - force: true, - from: 'file.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('adds the file to the watch list', (done) => { - run({ - patterns: [ - { - from: 'file.txt', - }, - ], - }) - .then((compilation) => { - const absFrom = path.join(HELPER_DIR, 'file.txt'); - - expect(Array.from(compilation.fileDependencies).sort()).toEqual( - [absFrom].sort() - ); - }) - .then(done) - .catch(done); - }); - - it('only include files that have changed', (done) => { - runChange({ - expectedAssetKeys: ['tempfile1.txt'], - newFileLoc1: path.join(HELPER_DIR, 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'tempfile2.txt'), - patterns: [ - { - from: 'tempfile1.txt', - }, - { - from: 'tempfile2.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores files in pattern', (done) => { - runEmit({ - expectedAssetKeys: [ - '[!]/hello.txt', - 'binextension.bin', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/nested/nestedfile.txt', - 'noextension', - ], - patterns: [ - { - from: '**/*', - ignore: ['file.*', 'file-in-nested-directory.*'], - }, - ], - }) - .then(done) - .catch(done); - }); - - it('allows pattern to contain name, hash or ext', (done) => { - runEmit({ - expectedAssetKeys: ['directory/directoryfile-22af64.txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - to: 'directory/[name]-[hash:6].[ext]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('allows pattern to contain contenthash', (done) => { - runEmit({ - expectedAssetKeys: ['directory/22af64.txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - to: 'directory/[contenthash:6].txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('allows pattern to contain custoh `contenthash` digest', (done) => { - runEmit({ - expectedAssetKeys: ['directory/c2a6.txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - to: 'directory/[sha1:contenthash:hex:4].txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('allows pattern to contain `hashType` without `hash` or `contenthash`', (done) => { - runEmit({ - expectedAssetKeys: ['directory/[md5::base64:20].txt'], - patterns: [ - { - from: 'directory/directoryfile.txt', - to: 'directory/[md5::base64:20].txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('transform with promise', (done) => { - runEmit({ - expectedAssetKeys: ['file.txt'], - expectedAssetContent: { - 'file.txt': 'newchanged!', - }, - patterns: [ - { - from: 'file.txt', - transform(content) { - return new Promise((resolve) => { - resolve(`${content}changed!`); - }); - }, - }, - ], - }) - .then(done) - .catch(done); - }); - - it('same file to multiple targets', (done) => { - runEmit({ - expectedAssetKeys: ['first/file.txt', 'second/file.txt'], - patterns: [ - { - from: 'file.txt', - to: 'first/file.txt', - }, - { - from: 'file.txt', - to: 'second/file.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can move a file (symbolic link) to the root directory', (done) => { - // Windows doesn't support symbolic link - runEmit({ - symlink: true, - expectedAssetKeys: process.platform === 'win32' ? [] : ['file-ln.txt'], - patterns: [ - { - from: 'symlink/file-ln.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - }); - - describe('with directory in from', () => { - it("can move a directory's contents to the root directory", (done) => { - runEmit({ - expectedAssetKeys: [ - '.dottedfile', - 'directoryfile.txt', - 'nested/deep-nested/deepnested.txt', - 'nested/nestedfile.txt', - ], - patterns: [ - { - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it("can move a directory's contents to the root directory using from with special characters", (done) => { - runEmit({ - expectedAssetKeys: [ - 'directoryfile.txt', - '(special-*file).txt', - 'nested/nestedfile.txt', - ], - patterns: [ - { - from: - path.sep === '/' ? '[special?directory]' : '[specialdirectory]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it("can move a directory's contents to the root directory using context with special characters", (done) => { - runEmit({ - expectedAssetKeys: [ - 'directoryfile.txt', - '(special-*file).txt', - 'nested/nestedfile.txt', - ], - patterns: [ - { - from: '.', - context: '[special?directory]', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('warns when directory not found', (done) => { - runEmit({ - expectedAssetKeys: [], - expectedWarnings: [ - new Error( - `unable to locate 'nonexistent' at '${HELPER_DIR}${path.sep}nonexistent'` - ), - ], - patterns: [ - { - from: 'nonexistent', - }, - ], - }) - .then(done) - .catch(done); - }); - - it("can use an absolute path to move a directory's contents to the root directory", (done) => { - const absolutePath = path.resolve(HELPER_DIR, 'directory'); - - runEmit({ - expectedAssetKeys: [ - '.dottedfile', - 'directoryfile.txt', - 'nested/deep-nested/deepnested.txt', - 'nested/nestedfile.txt', - ], - patterns: [ - { - from: absolutePath, - }, - ], - }) - .then(done) - .catch(done); - }); - - it("can move a directory's contents to a new directory", (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/.dottedfile', - 'newdirectory/directoryfile.txt', - 'newdirectory/nested/deep-nested/deepnested.txt', - 'newdirectory/nested/nestedfile.txt', - ], - patterns: [ - { - from: 'directory', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); + it('should throw an error if the patterns are an object', () => { + const createPluginWithObject = () => { + // eslint-disable-next-line no-new + new CopyPlugin({}); + }; - it("can move a directory's contents to a new directory using a pattern context", (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - patterns: [ - { - context: 'directory', - from: 'nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); + expect(createPluginWithObject).toThrow(Error); }); - it("can flatten a directory's contents to a new directory", (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/.dottedfile', - 'newdirectory/deepnested.txt', - 'newdirectory/directoryfile.txt', - 'newdirectory/nestedfile.txt', - ], - patterns: [ - { - flatten: true, - from: 'directory', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); + it('should throw an error if the patterns are null', () => { + const createPluginWithNull = () => { + // eslint-disable-next-line no-new + new CopyPlugin(null); + }; - it("can move a directory's contents to a new directory using an absolute to", (done) => { - runEmit({ - expectedAssetKeys: [ - '../tempdir/.dottedfile', - '../tempdir/directoryfile.txt', - '../tempdir/nested/deep-nested/deepnested.txt', - '../tempdir/nested/nestedfile.txt', - ], - patterns: [ - { - from: 'directory', - to: TEMP_DIR, - }, - ], - }) - .then(done) - .catch(done); + expect(createPluginWithNull).toThrow(Error); }); - it("can move a nested directory's contents to the root directory", (done) => { - runEmit({ - expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'], - patterns: [ - { - from: 'directory/nested', - }, - ], - }) - .then(done) - .catch(done); - }); + it('should throw an error if the "from" path is an empty string', () => { + const createPluginWithNull = () => { + // eslint-disable-next-line no-new + new CopyPlugin({ + from: '', + }); + }; - it("can move a nested directory's contents to a new directory", (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - patterns: [ - { - from: 'directory/nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); + expect(createPluginWithNull).toThrow(Error); }); - it("can use an absolute path to move a nested directory's contents to a new directory", (done) => { - const absolutePath = path.resolve(HELPER_DIR, 'directory', 'nested'); - + it('should warn when pattern is empty', (done) => { runEmit({ expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - patterns: [ - { - from: absolutePath, - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it("won't overwrite a file already in the compilation", (done) => { - runForce({ - existingAsset: 'directoryfile.txt', - expectedAssetContent: { - 'directoryfile.txt': 'existing', - }, - patterns: [ - { - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('can force overwrite of a file already in the compilation', (done) => { - runForce({ - existingAsset: 'directoryfile.txt', - expectedAssetContent: { - 'directoryfile.txt': 'new', - }, - patterns: [ - { - force: true, - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('adds the context directory to the watch list', (done) => { - run({ - patterns: [ - { - from: 'directory', - }, + '.file.txt', + '[!]/hello.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/nested/nestedfile.txt', + 'binextension.bin', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + 'directory/.dottedfile', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + 'file.txt', + 'file.txt.gz', + 'noextension', ], - }) - .then((compilation) => { - const absFrom = path.resolve(HELPER_DIR, 'directory'); - expect(Array.from(compilation.contextDependencies).sort()).toEqual( - [absFrom].sort() - ); - }) - .then(done) - .catch(done); - }); - - it('only include files that have changed', (done) => { - runChange({ - expectedAssetKeys: ['tempfile1.txt'], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), + expectedErrors: [new Error(`path "from" cannot be empty string`)], patterns: [ { - from: 'directory', + from: '', }, ], }) .then(done) .catch(done); }); + }); - it('include all files if copyUnmodified is true', (done) => { - runChange({ - expectedAssetKeys: [ - '.dottedfile', - 'directoryfile.txt', - 'nested/deep-nested/deepnested.txt', - 'nested/nestedfile.txt', - 'tempfile1.txt', - 'tempfile2.txt', - ], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), - options: { - copyUnmodified: true, - }, + describe('dev server', () => { + it('should work with absolute to if outpath is defined with webpack-dev-server', (done) => { + runEmit({ + compiler: new MockCompiler({ + outputPath: '/', + devServer: { + outputPath: BUILD_DIR, + }, + }), + expectedAssetKeys: ['file.txt'], patterns: [ { - from: 'directory', + from: 'file.txt', + to: BUILD_DIR, }, ], }) @@ -1394,107 +433,117 @@ describe('apply function', () => { .catch(done); }); - it('can move multiple files to a non-root directory with name, hash and ext', (done) => { + it("should throw an error when output path isn't defined with webpack-dev-server", (done) => { runEmit({ - expectedAssetKeys: [ - 'nested/.dottedfile-79d39f', - 'nested/directoryfile-22af64.txt', - 'nested/nested/deep-nested/deepnested-d41d8c.txt', - 'nested/nested/nestedfile-d41d8c.txt', + compiler: new MockCompiler({ + outputPath: '/', + }), + skipAssetsTesting: true, + expectedErrors: [ + new Error( + 'using older versions of webpack-dev-server, devServer.outputPath must be defined to write to absolute paths' + ), ], patterns: [ { - from: 'directory', - to: 'nested/[path][name]-[hash:6].[ext]', + from: 'file.txt', + to: BUILD_DIR, }, ], }) .then(done) .catch(done); }); + }); - it('can move multiple files to a non-root directory with [1]', (done) => { - runEmit({ - expectedAssetKeys: ['nested/txt'], + describe('watch mode', () => { + it('should add the file to the watch list when "from" is a file', (done) => { + run({ patterns: [ { - from: 'directory/nested/deep-nested', - to: 'nested/[1]', - test: /\.([^.]*)$/, + from: 'file.txt', }, ], }) + .then((compilation) => { + const absFrom = path.join(HELPER_DIR, 'file.txt'); + + expect(Array.from(compilation.fileDependencies).sort()).toEqual( + [absFrom].sort() + ); + }) .then(done) .catch(done); }); - it("can move a directory's contents to the root directory from symbolic link", (done) => { - runEmit({ - // Windows doesn't support symbolic link - symlink: true, - expectedAssetKeys: - process.platform === 'win32' - ? [] - : ['file.txt', 'nested-directory/file-in-nested-directory.txt'], + it('should add a directory to the watch list when "from" is a directory', (done) => { + run({ patterns: [ { - from: 'symlink/directory-ln', + from: 'directory', }, ], }) - .then(done) - .catch(done); - }); - }); + .then((compilation) => { + const absFrom = path.join(HELPER_DIR, 'directory'); - describe('with simple string patterns', () => { - it('can move multiple files', (done) => { - runEmit({ - expectedAssetKeys: ['binextension.bin', 'file.txt', 'noextension'], - patterns: ['binextension.bin', 'file.txt', 'noextension'], - }) + expect(Array.from(compilation.contextDependencies).sort()).toEqual( + [absFrom].sort() + ); + }) .then(done) .catch(done); }); - }); - describe('with difference path segment separation', () => { - it('can normalize backslash path with glob in from', (done) => { - runEmit({ - expectedAssetKeys: ['directory/nested/nestedfile.txt'], + it('should add a directory to the watch list when "from" is a glob', (done) => { + run({ patterns: [ { - from: { - glob: 'directory\\nested\\*', - }, + from: 'directory/**/*', }, ], }) + .then((compilation) => { + expect( + Array.from(compilation.contextDependencies) + .map((contextDependency) => contextDependency) + .sort() + ).toEqual([path.join(HELPER_DIR, 'directory')].sort()); + }) .then(done) .catch(done); }); - it('can normalize backslash path with glob in from (mixed path segment separation)', (done) => { - runEmit({ - expectedAssetKeys: ['directory/nested/nestedfile.txt'], + it('should not add the directory to the watch list when glob is a file', (done) => { + run({ patterns: [ { from: { - glob: 'directory/nested\\*', + glob: 'directory/directoryfile.txt', }, }, ], }) + .then((compilation) => { + const absFrom = path.join(HELPER_DIR, 'directory'); + + expect(compilation.contextDependencies).not.toContain(absFrom); + }) .then(done) .catch(done); }); - it('can normalize backslash path with glob in from (simple)', (done) => { - runEmit({ - expectedAssetKeys: ['directory/nested/nestedfile.txt'], + it('only include files that have changed', (done) => { + runChange({ + expectedAssetKeys: ['tempfile1.txt'], + newFileLoc1: path.join(HELPER_DIR, 'watch', 'tempfile1.txt'), + newFileLoc2: path.join(HELPER_DIR, 'watch', 'tempfile2.txt'), patterns: [ { - from: 'directory\\nested\\*', + from: 'tempfile1.txt', + }, + { + from: 'tempfile2.txt', }, ], }) @@ -1502,20 +551,24 @@ describe('apply function', () => { .catch(done); }); - it('can exclude path', (done) => { - runEmit({ - expectedAssetKeys: [ - '[!]/hello.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/nested/nestedfile.txt', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - ], + it('only include files that have changed', (done) => { + runChange({ + expectedAssetKeys: ['tempfile1.txt'], + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile2.txt' + ), patterns: [ { - from: '!(directory)/**/*.txt', + from: 'directory', }, ], }) @@ -1523,34 +576,49 @@ describe('apply function', () => { .catch(done); }); - it('can exclude path with backslash path', (done) => { - runEmit({ - expectedAssetKeys: [ - '[!]/hello.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/nested/nestedfile.txt', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - ], + it('include all files if copyUnmodified is true', (done) => { + runChange({ + expectedAssetKeys: ['tempfile1.txt', 'tempfile2.txt', '.gitkeep'], + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile2.txt' + ), + options: { + copyUnmodified: true, + }, patterns: [ { - from: '!(directory)\\**\\*.txt', + from: 'directory', }, ], }) .then(done) .catch(done); }); - }); - describe('modified files', () => { it('copy only changed files', (done) => { runChange({ expectedAssetKeys: ['dest1/tempfile1.txt'], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile2.txt' + ), patterns: [ { context: 'directory', @@ -1566,8 +634,18 @@ describe('apply function', () => { it('copy only changed files (multiple patterns)', (done) => { runChange({ expectedAssetKeys: ['dest1/tempfile1.txt', 'dest2/tempfile1.txt'], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile2.txt' + ), patterns: [ { context: 'directory', @@ -1591,8 +669,13 @@ describe('apply function', () => { 'dest1/tempfile1.txt', 'dest2/directory/tempfile1.txt', ], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'tempfile2.txt'), + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join(HELPER_DIR, 'watch', 'tempfile2.txt'), patterns: [ { context: 'directory', @@ -1615,8 +698,13 @@ describe('apply function', () => { 'dest1/directory/tempfile1.txt', 'dest2/tempfile1.txt', ], - newFileLoc1: path.join(HELPER_DIR, 'directory', 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'tempfile2.txt'), + newFileLoc1: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile1.txt' + ), + newFileLoc2: path.join(HELPER_DIR, 'watch', 'tempfile2.txt'), patterns: [ { from: '**/*.txt', @@ -1636,8 +724,13 @@ describe('apply function', () => { it('copy only changed files (multiple patterns with difference context 2)', (done) => { runChange({ expectedAssetKeys: ['dest1/tempfile1.txt'], - newFileLoc1: path.join(HELPER_DIR, 'tempfile1.txt'), - newFileLoc2: path.join(HELPER_DIR, 'directory', 'tempfile2.txt'), + newFileLoc1: path.join(HELPER_DIR, 'watch', 'tempfile1.txt'), + newFileLoc2: path.join( + HELPER_DIR, + 'watch', + 'directory', + 'tempfile2.txt' + ), patterns: [ { from: '**/*.txt', @@ -1654,543 +747,4 @@ describe('apply function', () => { .catch(done); }); }); - - describe('options', () => { - describe('ignore', () => { - it('ignores files when from is a file', (done) => { - runEmit({ - expectedAssetKeys: ['directoryfile.txt'], - options: { - ignore: ['file.*'], - }, - patterns: [ - { - from: 'file.txt', - }, - { - from: 'directory/directoryfile.txt', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores files when from is a directory', (done) => { - runEmit({ - expectedAssetKeys: [ - '.dottedfile', - 'directoryfile.txt', - 'nested/deep-nested/deepnested.txt', - ], - options: { - ignore: ['*/nestedfile.*'], - }, - patterns: [ - { - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores files with a certain extension', (done) => { - runEmit({ - expectedAssetKeys: ['.dottedfile'], - options: { - ignore: ['*.txt'], - }, - patterns: [ - { - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores files that start with a dot', (done) => { - runEmit({ - expectedAssetKeys: [ - '[!]/hello.txt', - 'binextension.bin', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - 'file.txt', - 'file.txt.gz', - 'directory/directoryfile.txt', - 'directory/nested/deep-nested/deepnested.txt', - 'directory/nested/nestedfile.txt', - '[special?directory]/directoryfile.txt', - '[special?directory]/(special-*file).txt', - '[special?directory]/nested/nestedfile.txt', - 'noextension', - ], - options: { - ignore: ['.dottedfile', '.file.txt'], - }, - patterns: [ - { - from: '.', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores all files except those with dots', (done) => { - runEmit({ - expectedAssetKeys: ['.file.txt', 'directory/.dottedfile'], - options: { - ignore: [ - { - dot: false, - glob: '**/*', - }, - ], - }, - patterns: [ - { - from: '.', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores all files even if they start with a dot', (done) => { - runEmit({ - expectedAssetKeys: [], - options: { - ignore: ['**/*'], - }, - patterns: [ - { - from: '.', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('ignores nested directory', (done) => { - runEmit({ - expectedAssetKeys: [ - '.file.txt', - '[!]/hello.txt', - 'binextension.bin', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - 'file.txt', - 'file.txt.gz', - 'noextension', - ], - options: { - ignore: [ - 'directory/**/*', - `[[]special${ - process.platform === 'win32' ? '' : '[?]' - }directory]/**/*`, - ], - }, - patterns: [ - { - from: '.', - }, - ], - }) - .then(done) - .catch(done); - }); - - if (path.sep === '/') { - it('ignores nested directory(can use "\\" to escape if path.sep is "/")', (done) => { - runEmit({ - expectedAssetKeys: [ - '.file.txt', - '[!]/hello.txt', - 'binextension.bin', - 'dir (86)/file.txt', - 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', - 'dir (86)/nesteddir/nestedfile.txt', - 'file.txt', - 'file.txt.gz', - 'noextension', - ], - options: { - ignore: ['directory/**/*', '\\[special\\?directory\\]/**/*'], - }, - patterns: [ - { - from: '.', - }, - ], - }) - .then(done) - .catch(done); - }); - } - - it('ignores nested directory (glob)', (done) => { - runEmit({ - expectedAssetKeys: ['.dottedfile', 'directoryfile.txt'], - options: { - ignore: ['nested/**/*'], - }, - patterns: [ - { - from: 'directory', - }, - ], - }) - .then(done) - .catch(done); - }); - }); - - describe('context', () => { - it('overrides webpack config context with absolute path', (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - options: { - context: path.resolve(HELPER_DIR, 'directory'), - }, - patterns: [ - { - from: 'nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('overrides webpack config context with relative path', (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - options: { - context: 'directory', - }, - patterns: [ - { - from: 'nested', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('is overridden by pattern context', (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/deep-nested/deepnested.txt', - 'newdirectory/nestedfile.txt', - ], - options: { - context: 'directory', - }, - patterns: [ - { - context: 'nested', - from: '.', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - - it('overrides webpack config context with absolute path', (done) => { - runEmit({ - expectedAssetKeys: [ - 'newdirectory/file.txt', - 'newdirectory/nesteddir/deepnesteddir/deepnesteddir.txt', - 'newdirectory/nesteddir/nestedfile.txt', - ], - options: { - context: path.resolve(HELPER_DIR, 'dir (86)'), - }, - patterns: [ - { - from: '**/*', - to: 'newdirectory', - }, - ], - }) - .then(done) - .catch(done); - }); - }); - - describe('cache', () => { - const cacheDir = findCacheDir({ name: 'copy-webpack-plugin' }); - - beforeEach(() => cacache.rm.all(cacheDir)); - - it('file should be cached', (done) => { - const newContent = 'newchanged!'; - const from = 'file.txt'; - - runEmit({ - expectedAssetKeys: ['file.txt'], - expectedAssetContent: { - 'file.txt': newContent, - }, - patterns: [ - { - from, - cache: true, - transform: function transform(content) { - return new Promise((resolve) => { - resolve(`${content}changed!`); - }); - }, - }, - ], - }) - .then(() => - cacache.ls(cacheDir).then((cacheEntries) => { - const cacheKeys = Object.keys(cacheEntries); - - expect(cacheKeys).toHaveLength(1); - - cacheKeys.forEach((cacheKey) => { - // eslint-disable-next-line no-new-func - const cacheEntry = new Function( - `'use strict'\nreturn ${cacheKey}` - )(); - - expect(cacheEntry.pattern.from).toBe(from); - }); - }) - ) - .then(done) - .catch(done); - }); - - it('files in directory should be cached', (done) => { - const from = 'directory'; - - runEmit({ - expectedAssetKeys: [ - '.dottedfile', - 'directoryfile.txt', - 'nested/deep-nested/deepnested.txt', - 'nested/nestedfile.txt', - ], - expectedAssetContent: { - '.dottedfile': 'dottedfile contents\nchanged!', - 'directoryfile.txt': 'newchanged!', - 'nested/nestedfile.txt': 'changed!', - }, - patterns: [ - { - from, - cache: true, - transform: function transform(content) { - return new Promise((resolve) => { - resolve(`${content}changed!`); - }); - }, - }, - ], - }) - .then(() => - cacache.ls(cacheDir).then((cacheEntries) => { - const cacheKeys = Object.keys(cacheEntries); - - expect(cacheKeys).toHaveLength(3); - - cacheKeys.forEach((cacheKey) => { - // eslint-disable-next-line no-new-func - const cacheEntry = new Function( - `'use strict'\nreturn ${cacheKey}` - )(); - - expect(cacheEntry.pattern.from).toBe(from); - }); - }) - ) - .then(done) - .catch(done); - }); - - it('glob should be cached', (done) => { - const from = '*.txt'; - - runEmit({ - expectedAssetKeys: ['file.txt'], - expectedAssetContent: { - 'file.txt': 'newchanged!', - }, - patterns: [ - { - from, - cache: true, - transform: function transform(content) { - return new Promise((resolve) => { - resolve(`${content}changed!`); - }); - }, - }, - ], - }) - .then(() => - cacache.ls(cacheDir).then((cacheEntries) => { - const cacheKeys = Object.keys(cacheEntries); - - expect(cacheKeys).toHaveLength(1); - - cacheKeys.forEach((cacheKey) => { - // eslint-disable-next-line no-new-func - const cacheEntry = new Function( - `'use strict'\nreturn ${cacheKey}` - )(); - - expect(cacheEntry.pattern.from).toBe(from); - }); - }) - ) - .then(done) - .catch(done); - }); - - it('file should be cached with custom cache key', (done) => { - const newContent = 'newchanged!'; - const from = 'file.txt'; - - runEmit({ - expectedAssetKeys: ['file.txt'], - expectedAssetContent: { - 'file.txt': newContent, - }, - patterns: [ - { - from, - cache: { - key: 'foobar', - }, - transform(content) { - return new Promise((resolve) => { - resolve(`${content}changed!`); - }); - }, - }, - ], - }) - .then(() => - cacache.ls(cacheDir).then((cacheEntries) => { - const cacheKeys = Object.keys(cacheEntries); - - expect(cacheKeys).toHaveLength(1); - - cacheKeys.forEach((cacheKey) => { - expect(cacheKey).toBe('foobar'); - }); - }) - ) - .then(done) - .catch(done); - }); - - it('binary file should be cached', (done) => { - const from = 'file.txt.gz'; - const content = fs.readFileSync(path.join(HELPER_DIR, from)); - const expectedNewContent = zlib.gzipSync('newchanged!'); - - expect(isGzip(content)).toBe(true); - expect(isGzip(expectedNewContent)).toBe(true); - - runEmit({ - expectedAssetKeys: ['file.txt.gz'], - expectedAssetContent: { - 'file.txt.gz': expectedNewContent, - }, - patterns: [ - { - from, - cache: true, - // eslint-disable-next-line no-shadow - transform: function transform(content) { - expect(isGzip(content)).toBe(true); - - return new Promise((resolve) => { - // eslint-disable-next-line no-shadow - zlib.unzip(content, (error, content) => { - if (error) { - throw error; - } - - const newContent = Buffer.from(`${content}changed!`); - - // eslint-disable-next-line no-shadow - zlib.gzip(newContent, (error, compressedData) => { - if (error) { - throw error; - } - - expect(isGzip(compressedData)).toBe(true); - - return resolve(compressedData); - }); - }); - }); - }, - }, - ], - }) - .then(() => - cacache.ls(cacheDir).then((cacheEntries) => { - const cacheKeys = Object.keys(cacheEntries); - - expect(cacheKeys).toHaveLength(1); - - cacheKeys.forEach((cacheKey) => { - // eslint-disable-next-line no-new-func - const cacheEntry = new Function( - `'use strict'\nreturn ${cacheKey}` - )(); - - expect(cacheEntry.pattern.from).toBe(from); - }); - }) - ) - .then(done) - .catch(done); - }); - }); - }); - - it('should move a file and use posix separator for emitting assets', (done) => { - runEmit({ - expectedAssetKeys: ['dir/nestedfile.txt'], - patterns: [ - { - context: HELPER_DIR, - from: 'directory/nested/nestedfile.txt', - to: 'dir', - }, - ], - }) - .then(done) - .catch(done); - }); }); diff --git a/test/cache-option.test.js b/test/cache-option.test.js new file mode 100644 index 00000000..f175961a --- /dev/null +++ b/test/cache-option.test.js @@ -0,0 +1,250 @@ +import fs from 'fs'; +import path from 'path'; +import zlib from 'zlib'; + +import cacache from 'cacache'; +import findCacheDir from 'find-cache-dir'; +import isGzip from 'is-gzip'; + +import { runEmit } from './utils/run'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('cache option', () => { + const cacheDir = findCacheDir({ name: 'copy-webpack-plugin' }); + + beforeEach(() => cacache.rm.all(cacheDir)); + + it('should cache when "from" is a file', (done) => { + const newContent = 'newchanged!'; + const from = 'file.txt'; + + runEmit({ + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': newContent, + }, + patterns: [ + { + from, + cache: true, + transform: function transform(content) { + return new Promise((resolve) => { + resolve(`${content}changed!`); + }); + }, + }, + ], + }) + .then(() => + cacache.ls(cacheDir).then((cacheEntries) => { + const cacheKeys = Object.keys(cacheEntries); + + expect(cacheKeys).toHaveLength(1); + + cacheKeys.forEach((cacheKey) => { + // eslint-disable-next-line no-new-func + const cacheEntry = new Function( + `'use strict'\nreturn ${cacheKey}` + )(); + + expect(cacheEntry.pattern.from).toBe(from); + }); + }) + ) + .then(done) + .catch(done); + }); + + it('should cache files when "from" is a directory', (done) => { + const from = 'directory'; + + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetContent: { + '.dottedfile': 'dottedfile contents\nchanged!', + 'directoryfile.txt': 'newchanged!', + 'nested/nestedfile.txt': 'changed!', + }, + patterns: [ + { + from, + cache: true, + transform: function transform(content) { + return new Promise((resolve) => { + resolve(`${content}changed!`); + }); + }, + }, + ], + }) + .then(() => + cacache.ls(cacheDir).then((cacheEntries) => { + const cacheKeys = Object.keys(cacheEntries); + + expect(cacheKeys).toHaveLength(3); + + cacheKeys.forEach((cacheKey) => { + // eslint-disable-next-line no-new-func + const cacheEntry = new Function( + `'use strict'\nreturn ${cacheKey}` + )(); + + expect(cacheEntry.pattern.from).toBe(from); + }); + }) + ) + .then(done) + .catch(done); + }); + + it('should cache when "from" is a glob', (done) => { + const from = 'directory/*.txt'; + + runEmit({ + expectedAssetKeys: ['directory/directoryfile.txt'], + expectedAssetContent: { + 'directory/directoryfile.txt': 'newchanged!', + }, + patterns: [ + { + from, + cache: true, + transform: function transform(content) { + return new Promise((resolve) => { + resolve(`${content}changed!`); + }); + }, + }, + ], + }) + .then(() => + cacache.ls(cacheDir).then((cacheEntries) => { + const cacheKeys = Object.keys(cacheEntries); + + expect(cacheKeys).toHaveLength(1); + + cacheKeys.forEach((cacheKey) => { + // eslint-disable-next-line no-new-func + const cacheEntry = new Function( + `'use strict'\nreturn ${cacheKey}` + )(); + + // Todo need investigate + expect(cacheEntry.pattern.from.replace(/\\/, '/')).toBe(from); + }); + }) + ) + .then(done) + .catch(done); + }); + + it('should cache file with custom cache key', (done) => { + const newContent = 'newchanged!'; + const from = 'file.txt'; + + runEmit({ + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': newContent, + }, + patterns: [ + { + from, + cache: { + key: 'foobar', + }, + transform(content) { + return new Promise((resolve) => { + resolve(`${content}changed!`); + }); + }, + }, + ], + }) + .then(() => + cacache.ls(cacheDir).then((cacheEntries) => { + const cacheKeys = Object.keys(cacheEntries); + + expect(cacheKeys).toHaveLength(1); + + cacheKeys.forEach((cacheKey) => { + expect(cacheKey).toBe('foobar'); + }); + }) + ) + .then(done) + .catch(done); + }); + + it('should cache binary file', (done) => { + const from = 'file.txt.gz'; + const content = fs.readFileSync(path.join(HELPER_DIR, from)); + const expectedNewContent = zlib.gzipSync('newchanged!'); + + expect(isGzip(content)).toBe(true); + expect(isGzip(expectedNewContent)).toBe(true); + + runEmit({ + expectedAssetKeys: ['file.txt.gz'], + expectedAssetContent: { + 'file.txt.gz': expectedNewContent, + }, + patterns: [ + { + from, + cache: true, + // eslint-disable-next-line no-shadow + transform: function transform(content) { + expect(isGzip(content)).toBe(true); + + return new Promise((resolve) => { + // eslint-disable-next-line no-shadow + zlib.unzip(content, (error, content) => { + if (error) { + throw error; + } + + const newContent = Buffer.from(`${content}changed!`); + + // eslint-disable-next-line no-shadow + zlib.gzip(newContent, (error, compressedData) => { + if (error) { + throw error; + } + + expect(isGzip(compressedData)).toBe(true); + + return resolve(compressedData); + }); + }); + }); + }, + }, + ], + }) + .then(() => + cacache.ls(cacheDir).then((cacheEntries) => { + const cacheKeys = Object.keys(cacheEntries); + + expect(cacheKeys).toHaveLength(1); + + cacheKeys.forEach((cacheKey) => { + // eslint-disable-next-line no-new-func + const cacheEntry = new Function( + `'use strict'\nreturn ${cacheKey}` + )(); + + expect(cacheEntry.pattern.from).toBe(from); + }); + }) + ) + .then(done) + .catch(done); + }); +}); diff --git a/test/context-option.test.js b/test/context-option.test.js new file mode 100644 index 00000000..a42eaf92 --- /dev/null +++ b/test/context-option.test.js @@ -0,0 +1,269 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('context option', () => { + it('should work when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + from: 'directoryfile.txt', + context: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a file and "context" with special characters', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + from: 'directoryfile.txt', + context: '[special?directory]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'], + patterns: [ + { + from: 'nested', + context: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a directory and "to" is a new directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + patterns: [ + { + context: 'directory', + from: 'nested', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a directory and "context" with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + // Todo strange behavour when you use `HELPER_DIR`, need investigate for next major release + from: '.', + context: '[special?directory]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: 'nested/**/*', + context: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a glob and "to" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/directoryfile.txt', + 'nested/nested/deep-nested/deepnested.txt', + 'nested/nested/nestedfile.txt', + ], + patterns: [ + { + context: 'directory', + from: '**/*', + to: 'nested', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a glob and "to" is a directory and "content" is an absolute path', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/directoryfile.txt', + 'nested/nested/deep-nested/deepnested.txt', + 'nested/nested/nestedfile.txt', + ], + patterns: [ + { + context: path.join(HELPER_DIR, 'directory'), + from: '**/*', + to: 'nested', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a glob and "context" with special characters', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: '**/*', + context: '[special?directory]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a glob and "context" with special characters #2', (done) => { + runEmit({ + expectedAssetKeys: ['(special-*file).txt'], + patterns: [ + { + from: '(special-*file).txt', + context: '[special?directory]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should work when "from" is a file and "context" is an absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + from: 'directoryfile.txt', + context: path.join(HELPER_DIR, 'directory'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should override webpack config context with an absolute path', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + options: { + context: path.join(HELPER_DIR, 'directory'), + }, + patterns: [ + { + from: 'nested', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should override webpack config context with a relative path', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + options: { + context: 'directory', + }, + patterns: [ + { + from: 'nested', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should override global context on pattern context with a relative path', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + options: { + context: 'directory', + }, + patterns: [ + { + context: 'nested', + from: '.', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('overrides webpack config context with an absolute path', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/file.txt', + 'newdirectory/nesteddir/deepnesteddir/deepnesteddir.txt', + 'newdirectory/nesteddir/nestedfile.txt', + ], + options: { + context: path.join(HELPER_DIR, 'dir (86)'), + }, + patterns: [ + { + from: '**/*', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/flatten-option.test.js b/test/flatten-option.test.js new file mode 100644 index 00000000..80766edb --- /dev/null +++ b/test/flatten-option.test.js @@ -0,0 +1,147 @@ +import { runEmit } from './utils/run'; + +describe('flatten option', () => { + it('should flatten a directory\'s files to a root directory when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + flatten: true, + from: 'directory/directoryfile.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten a directory\'s files to a new directory when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: ['nested/directoryfile.txt'], + patterns: [ + { + flatten: true, + from: 'directory/directoryfile.txt', + to: 'nested', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten a directory\'s files to a root directory when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'deepnested.txt', + 'directoryfile.txt', + 'nestedfile.txt', + ], + patterns: [ + { + flatten: true, + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten a directory\'s files to new directory when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/.dottedfile', + 'newdirectory/deepnested.txt', + 'newdirectory/directoryfile.txt', + 'newdirectory/nestedfile.txt', + ], + patterns: [ + { + flatten: true, + from: 'directory', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten a directory\'s files to a root directory when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'deepnested.txt', + 'directoryfile.txt', + 'nestedfile.txt', + ], + patterns: [ + { + flatten: true, + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten a directory\'s files to a new directory when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/deepnested.txt', + 'nested/directoryfile.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + flatten: true, + from: 'directory/**/*', + to: 'nested', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten files in a relative context to a root directory when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'deepnested.txt', + 'directoryfile.txt', + 'nestedfile.txt', + ], + patterns: [ + { + context: 'directory', + flatten: true, + from: '**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should flatten files in a relative context to a non-root directory when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/deepnested.txt', + 'nested/directoryfile.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + context: 'directory', + flatten: true, + from: '**/*', + to: 'nested', + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/force-option.test.js b/test/force-option.test.js new file mode 100644 index 00000000..1567007d --- /dev/null +++ b/test/force-option.test.js @@ -0,0 +1,237 @@ +import { runForce } from './utils/run'; + +describe('force option', () => { + describe('is not specified', () => { + it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => { + runForce({ + existingAssets: ['file.txt'], + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'existing', + }, + patterns: [ + { + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should not overwrite files already in the compilation when "from" is a directory', (done) => { + runForce({ + existingAssets: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetContent: { + '.dottedfile': 'existing', + 'nested/deep-nested/deepnested.txt': 'existing', + 'nested/nestedfile.txt': 'existing', + 'directoryfile.txt': 'existing', + }, + patterns: [ + { + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should not overwrite files already in the compilation when "from" is a glob', (done) => { + runForce({ + existingAssets: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetContent: { + 'directory/nested/deep-nested/deepnested.txt': 'existing', + 'directory/nested/nestedfile.txt': 'existing', + 'directory/directoryfile.txt': 'existing', + }, + patterns: [ + { + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is "false" (Boolean)', () => { + it('should not overwrite a file already in the compilation by default when "from" is a file', (done) => { + runForce({ + existingAssets: ['file.txt'], + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'existing', + }, + patterns: [ + { + force: false, + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should not overwrite files already in the compilation when "from" is a directory', (done) => { + runForce({ + existingAssets: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetContent: { + '.dottedfile': 'existing', + 'nested/deep-nested/deepnested.txt': 'existing', + 'nested/nestedfile.txt': 'existing', + 'directoryfile.txt': 'existing', + }, + patterns: [ + { + force: false, + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should not overwrite files already in the compilation when "from" is a glob', (done) => { + runForce({ + existingAssets: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetContent: { + 'directory/nested/deep-nested/deepnested.txt': 'existing', + 'directory/nested/nestedfile.txt': 'existing', + 'directory/directoryfile.txt': 'existing', + }, + patterns: [ + { + force: false, + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is "true" (Boolean)', () => { + it('should force overwrite a file already in the compilation when "from" is a file', (done) => { + runForce({ + existingAssets: ['file.txt'], + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'new', + }, + patterns: [ + { + force: true, + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should force overwrite files already in the compilation when "from" is a directory', (done) => { + runForce({ + existingAssets: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetContent: { + '.dottedfile': 'dottedfile contents\n', + 'nested/deep-nested/deepnested.txt': '', + 'nested/nestedfile.txt': '', + 'directoryfile.txt': 'new', + }, + patterns: [ + { + force: true, + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should force overwrite files already in the compilation when "from" is a glob', (done) => { + runForce({ + existingAssets: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetContent: { + 'directory/nested/deep-nested/deepnested.txt': '', + 'directory/nested/nestedfile.txt': '', + 'directory/directoryfile.txt': 'new', + }, + patterns: [ + { + force: true, + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + }); +}); diff --git a/test/from-option.test.js b/test/from-option.test.js new file mode 100644 index 00000000..8a9662dd --- /dev/null +++ b/test/from-option.test.js @@ -0,0 +1,522 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('from option', () => { + describe('is a file', () => { + it('should move a file', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file when "from" an absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, 'file.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file from nesting directory', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file from nesting directory when "from" an absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory/directoryfile.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file (symbolic link)', (done) => { + runEmit({ + symlink: true, + expectedAssetKeys: process.platform === 'win32' ? [] : ['file-ln.txt'], + patterns: [ + { + from: 'symlink/file-ln.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when file not found', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedWarnings: [ + new Error( + `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${path.sep}nonexistent.txt'` + ), + ], + patterns: [ + { + from: 'nonexistent.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is a directory', () => { + it('should move files', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files when "from" is current directory', (done) => { + runEmit({ + expectedAssetKeys: [ + '.file.txt', + '[!]/hello.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/nested/nestedfile.txt', + 'binextension.bin', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + 'directory/.dottedfile', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + 'file.txt', + 'file.txt.gz', + 'noextension', + ], + patterns: [ + { + from: '.', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files when "from" is relative path to context', (done) => { + runEmit({ + expectedAssetKeys: [ + '.file.txt', + '[!]/hello.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/nested/nestedfile.txt', + 'binextension.bin', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + 'directory/.dottedfile', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + 'file.txt', + 'file.txt.gz', + 'noextension', + ], + patterns: [ + { + from: '../helpers', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files with a forward slash', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory/', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files from symbolic link', (done) => { + runEmit({ + // Windows doesn't support symbolic link + symlink: true, + expectedAssetKeys: + process.platform === 'win32' + ? [] + : ['file.txt', 'nested-directory/file-in-nested-directory.txt'], + patterns: [ + { + from: 'symlink/directory-ln', + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should move files when 'from' is a absolute path", (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it("should move files when 'from' with special characters", (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + '(special-*file).txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: + path.sep === '/' ? '[special?directory]' : '[specialdirectory]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files from nested directory', (done) => { + runEmit({ + expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'], + patterns: [ + { + from: 'directory/nested', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files from nested directory with an absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['deep-nested/deepnested.txt', 'nestedfile.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory/nested'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when directory not found', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedWarnings: [ + new Error( + `unable to locate 'nonexistent' at '${HELPER_DIR}${path.sep}nonexistent'` + ), + ], + patterns: [ + { + from: 'nonexistent', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is a glob', () => { + it('should move files', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: '*.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files when a glob contains absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, '*.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files using globstar', (done) => { + runEmit({ + expectedAssetKeys: [ + '[!]/hello.txt', + 'binextension.bin', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + 'file.txt', + 'file.txt.gz', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt', + 'noextension', + ], + patterns: [ + { + from: '**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files using globstar and contains an absolute path', (done) => { + runEmit({ + expectedAssetKeys: [ + '[!]/hello.txt', + 'file.txt', + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + '[special?directory]/directoryfile.txt', + '[special?directory]/(special-*file).txt', + '[special?directory]/nested/nestedfile.txt', + 'dir (86)/file.txt', + 'dir (86)/nesteddir/deepnesteddir/deepnesteddir.txt', + 'dir (86)/nesteddir/nestedfile.txt', + ], + patterns: [ + { + from: path.join(HELPER_DIR, '**/*.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files in nested directory using globstar', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/[!]/hello-d41d8c.txt', + 'nested/binextension-d41d8c.bin', + 'nested/dir (86)/file-d41d8c.txt', + 'nested/dir (86)/nesteddir/deepnesteddir/deepnesteddir-d41d8c.txt', + 'nested/dir (86)/nesteddir/nestedfile-d41d8c.txt', + 'nested/file-22af64.txt', + 'nested/file.txt-5b311c.gz', + 'nested/directory/directoryfile-22af64.txt', + 'nested/directory/nested/deep-nested/deepnested-d41d8c.txt', + 'nested/directory/nested/nestedfile-d41d8c.txt', + 'nested/[special?directory]/(special-*file)-0bd650.txt', + 'nested/[special?directory]/directoryfile-22af64.txt', + 'nested/[special?directory]/nested/nestedfile-d41d8c.txt', + 'nested/noextension-d41d8c', + ], + patterns: [ + { + from: '**/*', + to: 'nested/[path][name]-[hash:6].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files from nested directory', (done) => { + runEmit({ + expectedAssetKeys: ['directory/directoryfile.txt'], + patterns: [ + { + from: 'directory/directory*.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files from nested directory #2', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory/**/*.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files using bracketed glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + 'file.txt', + 'noextension', + ], + patterns: [ + { + from: '{file.txt,noextension,directory/**/*}', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files (symbolic link)', (done) => { + runEmit({ + // Windows doesn't support symbolic link + symlink: true, + expectedAssetKeys: + process.platform === 'win32' + ? [] + : [ + 'symlink/directory-ln/file.txt', + 'symlink/directory-ln/nested-directory/file-in-nested-directory.txt', + 'symlink/directory/file.txt', + 'symlink/directory/nested-directory/file-in-nested-directory.txt', + 'symlink/file-ln.txt', + 'symlink/file.txt', + ], + patterns: [ + { + from: 'symlink/**/*.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is a object with glob and glob options', () => { + it('should move files', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: { + glob: '*.txt', + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files exclude dot files', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: { + glob: '*.txt', + dot: false, + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files with dot files', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt', '.file.txt'], + patterns: [ + { + from: { + glob: '*.txt', + dot: true, + }, + }, + ], + }) + .then(done) + .catch(done); + }); + }); +}); diff --git a/test/globOptions-option.test.js b/test/globOptions-option.test.js new file mode 100644 index 00000000..a6dc0a38 --- /dev/null +++ b/test/globOptions-option.test.js @@ -0,0 +1,35 @@ +import { runEmit } from './utils/run'; + +describe('from option', () => { + it('should move files exclude dot files', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: '*.txt', + globOptions: { + dot: false, + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files include dot files', (done) => { + runEmit({ + expectedAssetKeys: ['.file.txt', 'file.txt'], + patterns: [ + { + from: '*.txt', + globOptions: { + dot: true, + }, + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/helpers/watch/.gitkeep b/test/helpers/watch/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/helpers/watch/directory/.gitkeep b/test/helpers/watch/directory/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/test/ignore-option.test.js b/test/ignore-option.test.js new file mode 100644 index 00000000..50db76c8 --- /dev/null +++ b/test/ignore-option.test.js @@ -0,0 +1,186 @@ +import { runEmit } from './utils/run'; + +describe('ignore option', () => { + it('should ignore files when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: [], + patterns: [ + { + ignore: ['file.*'], + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should files when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + ], + patterns: [ + { + ignore: ['*/nestedfile.*'], + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should files in nested directory when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: ['.dottedfile', 'directoryfile.txt'], + patterns: [ + { + ignore: ['**/nested/**'], + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should files when from is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + ], + patterns: [ + { + ignore: ['*nestedfile.*'], + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should files in nested directory when from is a glob', (done) => { + runEmit({ + expectedAssetKeys: ['directory/directoryfile.txt'], + patterns: [ + { + ignore: ['*/nested/**'], + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignore files with a certain extension', (done) => { + runEmit({ + expectedAssetKeys: ['.dottedfile'], + patterns: [ + { + ignore: ['*.txt'], + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignore files with multiple ignore patterns', (done) => { + runEmit({ + expectedAssetKeys: ['directory/nested/nestedfile.txt'], + patterns: [ + { + ignore: ['directoryfile.*', '**/deep-nested/**'], + from: 'directory/**/*', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignore files except those with dots', (done) => { + runEmit({ + expectedAssetKeys: ['.dottedfile'], + options: { + ignore: [ + { + dot: false, + glob: '**/*', + }, + ], + }, + patterns: [ + { + from: 'directory', + ignore: [ + { + dot: false, + glob: '**/*', + }, + ], + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignore files that start with a dot', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + ignore: ['.dottedfile'], + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignores all files even if they start with a dot', (done) => { + runEmit({ + expectedAssetKeys: [], + options: { + ignore: ['**/*'], + }, + patterns: [ + { + from: 'directory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should ignore files when "from" is a file (global ignore)', (done) => { + runEmit({ + expectedAssetKeys: [], + options: { + ignore: ['file.*'], + }, + patterns: [ + { + ignore: ['file.*'], + from: 'file.txt', + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/test-option.test.js b/test/test-option.test.js new file mode 100644 index 00000000..c0503161 --- /dev/null +++ b/test/test-option.test.js @@ -0,0 +1,73 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +describe('test option', () => { + it('should move files to a root directory with [1]', (done) => { + runEmit({ + expectedAssetKeys: ['txt'], + patterns: [ + { + from: 'directory/nested/deep-nested', + to: '[1]', + test: /\.([^.]*)$/, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a non-root directory with [1]', (done) => { + runEmit({ + expectedAssetKeys: ['nested/txt'], + patterns: [ + { + from: 'directory/nested/deep-nested', + to: 'nested/[1]', + test: /\.([^.]*)$/, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files and flatten them to a non-root directory with [1]-[2].[ext]', (done) => { + runEmit({ + expectedAssetKeys: [ + 'nested/deep-nested-deepnested.txt', + 'nested/directory-directoryfile.txt', + 'nested/nested-nestedfile.txt', + ], + patterns: [ + { + from: 'directory/**/*', + test: `([^\\${path.sep}]+)\\${path.sep}([^\\${path.sep}]+)\\.\\w+$`, + to: 'nested/[1]-[2].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files and flatten them to a root directory with [1]-[2].[ext]', (done) => { + runEmit({ + expectedAssetKeys: [ + 'deep-nested-deepnested.txt', + 'directory-directoryfile.txt', + 'nested-nestedfile.txt', + ], + patterns: [ + { + from: 'directory/**/*', + test: `([^\\${path.sep}]+)\\${path.sep}([^\\${path.sep}]+)\\.\\w+$`, + to: '[1]-[2].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/to-option.test.js b/test/to-option.test.js new file mode 100644 index 00000000..6245c5ef --- /dev/null +++ b/test/to-option.test.js @@ -0,0 +1,452 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +const BUILD_DIR = path.join(__dirname, 'build'); +const TEMP_DIR = path.join(__dirname, 'tempdir'); +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('to option', () => { + describe('is a file', () => { + it('should move a file to a new file', (done) => { + runEmit({ + expectedAssetKeys: ['newfile.txt'], + patterns: [ + { + from: 'file.txt', + to: 'newfile.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new file when "to" is absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['../tempdir/newfile.txt'], + patterns: [ + { + from: 'file.txt', + to: path.join(TEMP_DIR, 'newfile.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new file inside nested directory', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/newfile.txt'], + patterns: [ + { + from: 'file.txt', + to: 'newdirectory/newfile.txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new file inside nested directory when "to" an absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/newfile.txt'], + patterns: [ + { + from: 'file.txt', + to: path.join(BUILD_DIR, 'newdirectory/newfile.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new file inside other directory what out of context', (done) => { + runEmit({ + expectedAssetKeys: ['../tempdir/newdirectory/newfile.txt'], + patterns: [ + { + from: 'file.txt', + to: path.join(TEMP_DIR, 'newdirectory/newfile.txt'), + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file using invalid template syntax', (done) => { + runEmit({ + expectedAssetKeys: ['directory/[md5::base64:20].txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: 'directory/[md5::base64:20].txt', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is a directory', () => { + it('should move a file to a new directory', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/file.txt'], + patterns: [ + { + from: 'file.txt', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory out of context', (done) => { + runEmit({ + expectedAssetKeys: ['../tempdir/file.txt'], + patterns: [ + { + from: 'file.txt', + to: TEMP_DIR, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory with a forward slash', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/file.txt'], + patterns: [ + { + from: 'file.txt', + to: 'newdirectory/', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory with an extension and path separator at end', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory.ext/file.txt'], + patterns: [ + { + from: 'file.txt', + to: `newdirectory.ext${path.sep}`, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory when "to" is absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: 'file.txt', + to: BUILD_DIR, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory when "to" is absolute path with a forward slash', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + patterns: [ + { + from: 'file.txt', + to: `${BUILD_DIR}/`, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory from nested directory', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/directoryfile.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory from nested directory when "from" is absolute path', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/directoryfile.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory', 'directoryfile.txt'), + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory from nested directory when "from" is absolute path with a forward slash', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/directoryfile.txt'], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory', 'directoryfile.txt'), + to: 'newdirectory/', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/.dottedfile', + 'newdirectory/directoryfile.txt', + 'newdirectory/nested/deep-nested/deepnested.txt', + 'newdirectory/nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new nested directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + patterns: [ + { + from: path.join(HELPER_DIR, 'directory', 'nested'), + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new directory out of context', (done) => { + runEmit({ + expectedAssetKeys: [ + '../tempdir/.dottedfile', + '../tempdir/directoryfile.txt', + '../tempdir/nested/deep-nested/deepnested.txt', + '../tempdir/nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + to: TEMP_DIR, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new directory when "to" is absolute path', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + to: BUILD_DIR, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new directory when "to" is absolute path with a forward slash', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + patterns: [ + { + from: 'directory', + to: `${BUILD_DIR}/`, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files to a new directory from nested directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/deep-nested/deepnested.txt', + 'newdirectory/nestedfile.txt', + ], + patterns: [ + { + from: 'directory/nested', + to: 'newdirectory', + }, + ], + }) + .then(done) + .catch(done); + }); + }); + + describe('is a template', () => { + it('should move a file using "contenthash"', (done) => { + runEmit({ + expectedAssetKeys: ['directory/22af64.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: 'directory/[contenthash:6].txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file using custom `contenthash` digest', (done) => { + runEmit({ + expectedAssetKeys: ['directory/c2a6.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: 'directory/[sha1:contenthash:hex:4].txt', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file using "name" and "ext"', (done) => { + runEmit({ + expectedAssetKeys: ['binextension.bin'], + patterns: [ + { + from: 'binextension.bin', + to: '[name].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file using "name", "contenthash" and "ext"', (done) => { + runEmit({ + expectedAssetKeys: ['file-22af64.txt'], + patterns: [ + { + from: 'file.txt', + to: '[name]-[contenthash:6].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file from nested directory', (done) => { + runEmit({ + expectedAssetKeys: ['directoryfile-22af64.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: '[name]-[hash:6].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file from nested directory to new directory', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory/directoryfile-22af64.txt'], + patterns: [ + { + from: 'directory/directoryfile.txt', + to: 'newdirectory/[name]-[hash:6].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file without an extension using "name", "ext"', (done) => { + runEmit({ + expectedAssetKeys: ['noextension.d41d8c.newext'], + patterns: [ + { + from: 'noextension', + to: '[name][ext].[contenthash:6].newext', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move files using "path", "name", "contenthash" and "ext"', (done) => { + runEmit({ + expectedAssetKeys: [ + 'newdirectory/.dottedfile-79d39f', + 'newdirectory/directoryfile-22af64.txt', + 'newdirectory/nested/deep-nested/deepnested-d41d8c.txt', + 'newdirectory/nested/nestedfile-d41d8c.txt', + ], + patterns: [ + { + from: 'directory', + to: 'newdirectory/[path][name]-[contenthash:6].[ext]', + }, + ], + }) + .then(done) + .catch(done); + }); + }); +}); diff --git a/test/toType-option.test.js b/test/toType-option.test.js new file mode 100644 index 00000000..d6490f54 --- /dev/null +++ b/test/toType-option.test.js @@ -0,0 +1,107 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; +import { MockCompilerNoStat } from './utils/mocks'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('toType option', () => { + it('should move a file to a new file', (done) => { + runEmit({ + expectedAssetKeys: ['new-file.txt'], + patterns: [ + { + from: 'file.txt', + to: 'new-file.txt', + toType: 'file', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory', (done) => { + runEmit({ + expectedAssetKeys: ['new-file.txt/file.txt'], + patterns: [ + { + from: 'file.txt', + to: 'new-file.txt', + toType: 'dir', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directory/directorynew-directoryfile.txt.22af645d1859cb5ca6da0c484f1f37ea.22af645d1859cb5ca6da0c484f1f37ea.22af645d.22af645d.txt', + ], + patterns: [ + { + from: 'directory/directoryfile.*', + to: + '[path][folder]new-[name].[ext].[hash].[contenthash].[md5:contenthash:hex:8].[md5:hash:hex:8].txt', + toType: 'template', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new file with no extension', (done) => { + runEmit({ + expectedAssetKeys: ['newname'], + patterns: [ + { + from: 'file.txt', + to: 'newname', + toType: 'file', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should move a file to a new directory with an extension', (done) => { + runEmit({ + expectedAssetKeys: ['newdirectory.ext/file.txt'], + patterns: [ + { + from: 'file.txt', + to: 'newdirectory.ext', + toType: 'dir', + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when file not found and stats is undefined', (done) => { + runEmit({ + compiler: new MockCompilerNoStat(), + expectedAssetKeys: [], + expectedWarnings: [ + new Error( + `unable to locate 'nonexistent.txt' at '${HELPER_DIR}${path.sep}nonexistent.txt'` + ), + ], + patterns: [ + { + from: 'nonexistent.txt', + to: '.', + toType: 'dir', + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/transform-option.test.js b/test/transform-option.test.js new file mode 100644 index 00000000..a118d9cd --- /dev/null +++ b/test/transform-option.test.js @@ -0,0 +1,184 @@ +import path from 'path'; + +import { runEmit } from './utils/run'; + +const HELPER_DIR = path.join(__dirname, 'helpers'); + +describe('transform option', () => { + it('should transform file when "from" is a file', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'newchanged', + }, + patterns: [ + { + from: 'file.txt', + transform(content, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return `${content}changed`; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path of every when "from" is a directory', (done) => { + runEmit({ + expectedAssetKeys: [ + '.dottedfile', + 'directoryfile.txt', + 'nested/deep-nested/deepnested.txt', + 'nested/nestedfile.txt', + ], + expectedAssetContent: { + '.dottedfile': 'dottedfile contents\nchanged', + 'directoryfile.txt': 'newchanged', + 'nested/deep-nested/deepnested.txt': 'changed', + 'nested/nestedfile.txt': 'changed', + }, + patterns: [ + { + from: 'directory', + transform(content, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return `${content}changed`; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path of every file when "from" is a glob', (done) => { + runEmit({ + expectedAssetKeys: [ + 'directory/directoryfile.txt', + 'directory/nested/deep-nested/deepnested.txt', + 'directory/nested/nestedfile.txt', + ], + expectedAssetContent: { + 'directory/directoryfile.txt': 'newchanged', + 'directory/nested/deep-nested/deepnested.txt': 'changed', + 'directory/nested/nestedfile.txt': 'changed', + }, + patterns: [ + { + from: 'directory/**/*', + transform(content, absoluteFrom) { + expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); + + return `${content}changed`; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform file when function return Promise', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'newchanged!', + }, + patterns: [ + { + from: 'file.txt', + transform(content) { + return new Promise((resolve) => { + resolve(`${content}changed!`); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should transform target path when async function used', (done) => { + runEmit({ + expectedAssetKeys: ['file.txt'], + expectedAssetContent: { + 'file.txt': 'newchanged!', + }, + patterns: [ + { + from: 'file.txt', + async transform(content) { + const newPath = await new Promise((resolve) => { + resolve(`${content}changed!`); + }); + + return newPath; + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when function throw error', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + transform() { + // eslint-disable-next-line no-throw-literal + throw new Error('a failure happened'); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when Promise was rejected', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + transform() { + return new Promise((resolve, reject) => { + return reject(new Error('a failure happened')); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); + + it('should warn when async function throw error', (done) => { + runEmit({ + expectedAssetKeys: [], + expectedErrors: [new Error('a failure happened')], + patterns: [ + { + from: 'file.txt', + async transform() { + await new Promise((resolve, reject) => { + reject(new Error('a failure happened')); + }); + }, + }, + ], + }) + .then(done) + .catch(done); + }); +}); diff --git a/test/transformPath-option.test.js b/test/transformPath-option.test.js index 3ab1b161..d48c6f22 100644 --- a/test/transformPath-option.test.js +++ b/test/transformPath-option.test.js @@ -51,20 +51,13 @@ describe('transformPath option', () => { 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: '**/*', + from: 'directory/**/*', transformPath(targetPath, absoluteFrom) { expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); @@ -119,7 +112,7 @@ describe('transformPath option', () => { .catch(done); }); - it('should warns when function throw error', (done) => { + it('should warn when function throw error', (done) => { runEmit({ expectedAssetKeys: [], expectedErrors: [new Error('a failure happened')], @@ -136,7 +129,7 @@ describe('transformPath option', () => { .catch(done); }); - it('should warns when Promise was rejected', (done) => { + it('should warn when Promise was rejected', (done) => { runEmit({ expectedAssetKeys: [], expectedErrors: [new Error('a failure happened')], @@ -155,7 +148,7 @@ describe('transformPath option', () => { .catch(done); }); - it('should warns when async function throw error', (done) => { + it('should warn when async function throw error', (done) => { runEmit({ expectedAssetKeys: [], expectedErrors: [new Error('a failure happened')], @@ -177,24 +170,13 @@ describe('transformPath option', () => { 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: '**/*', + from: 'directory/**/*', to: 'nested/[path][name]-[hash:6].[ext]', transformPath(targetPath, absoluteFrom) { expect(absoluteFrom.includes(HELPER_DIR)).toBe(true); diff --git a/test/utils/run.js b/test/utils/run.js index 17cf14f1..26efbbfa 100644 --- a/test/utils/run.js +++ b/test/utils/run.js @@ -34,7 +34,12 @@ function run(opts) { opts.options.ignore = []; } - opts.options.ignore.push('symlink/**/*', 'file-ln.txt', 'directory-ln'); + opts.options.ignore.push( + 'symlink/**/*', + 'file-ln.txt', + 'directory-ln', + 'watch/**/*' + ); } new CopyPlugin(opts.patterns, opts.options).apply(compiler); @@ -136,14 +141,17 @@ function runForce(opts) { opts.compilation = { assets: {}, }; - // eslint-disable-next-line no-param-reassign - opts.compilation.assets[opts.existingAsset] = { - source() { - return 'existing'; - }, - }; - return run(opts).then(() => {}); + opts.existingAssets.forEach((assetName) => { + // eslint-disable-next-line no-param-reassign + opts.compilation.assets[assetName] = { + source() { + return 'existing'; + }, + }; + }); + + return runEmit(opts).then(() => {}); } function runChange(opts) { @@ -163,7 +171,7 @@ function runChange(opts) { return run({ compiler, - options: opts.options, + options: Object.assign({}, opts.options, { context: 'watch' }), patterns: opts.patterns, }) .then(() => {