import * as fse from "fs-extra" ; import { expect } from "chai" ; import path from "./assert_path" ; import helper from "./helper" ; import * as jetpack from ".." ; import { InspectResult } from "../types" ; describe ( "copy" , ( ) => { beforeEach ( helper . setCleanTestCwd ) ; afterEach ( helper . switchBackToCorrectCwd ) ; describe ( "copies a file" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "file.txt" , "abc" ) ; } ; const expectations = ( ) => { path ( "file.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "file_copied.txt" ) . shouldBeFileWithContent ( "abc" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "file.txt" , "file_copied.txt" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "file.txt" , "file_copied.txt" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "can copy file to nonexistent directory (will create directory)" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "file.txt" , "abc" ) ; } ; const expectations = ( ) => { path ( "file.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "dir/dir/file.txt" ) . shouldBeFileWithContent ( "abc" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "file.txt" , "dir/dir/file.txt" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "file.txt" , "dir/dir/file.txt" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "copies empty directory" , ( ) => { const preparations = ( ) => { fse . mkdirsSync ( "dir" ) ; } ; const expectations = ( ) => { path ( "copied/dir" ) . shouldBeDirectory ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "dir" , "copied/dir" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "dir" , "copied/dir" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "copies a tree of files" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a/f1.txt" , "abc" ) ; fse . outputFileSync ( "a/b/f2.txt" , "123" ) ; fse . mkdirsSync ( "a/b/c" ) ; } ; const expectations = ( ) => { path ( "copied/a/f1.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "copied/a/b/c" ) . shouldBeDirectory ( ) ; path ( "copied/a/b/f2.txt" ) . shouldBeFileWithContent ( "123" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "a" , "copied/a" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "a" , "copied/a" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "generates nice error if source path doesn't exist" , ( ) => { const expectations = ( err ) => { expect ( err . code ) . to . equal ( "ENOENT" ) ; expect ( err . message ) . to . have . string ( "Path to copy doesn't exist" ) ; } ; it ( "sync" , ( ) => { try { jetpack . copy ( "a" , "b" ) ; throw new Error ( "Expected error to be thrown" ) ; } catch ( err ) { expectations ( err ) ; } } ) ; it ( "async" , done => { jetpack . copyAsync ( "a" , "b" ) . catch ( err => { expectations ( err ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "respects internal CWD of jetpack instance" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a/b.txt" , "abc" ) ; } ; const expectations = ( ) => { path ( "a/b.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "a/x.txt" ) . shouldBeFileWithContent ( "abc" ) ; } ; it ( "sync" , ( ) => { const jetContext = jetpack . cwd ( "a" ) ; preparations ( ) ; jetContext . copy ( "b.txt" , "x.txt" ) ; expectations ( ) ; } ) ; it ( "async" , done => { const jetContext = jetpack . cwd ( "a" ) ; preparations ( ) ; jetContext . copyAsync ( "b.txt" , "x.txt" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "overwriting behaviour" , ( ) => { describe ( "does not overwrite by default" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a/file.txt" , "abc" ) ; fse . mkdirsSync ( "b" ) ; } ; const expectations = ( err ) => { expect ( err . code ) . to . equal ( "EEXIST" ) ; expect ( err . message ) . to . have . string ( "Destination path already exists" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; try { jetpack . copy ( "a" , "b" ) ; throw new Error ( "Expected error to be thrown" ) ; } catch ( err ) { expectations ( err ) ; } } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "a" , "b" ) . catch ( err => { expectations ( err ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "overwrites if it was specified" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a/file.txt" , "abc" ) ; fse . outputFileSync ( "b/file.txt" , "xyz" ) ; } ; const expectations = ( ) => { path ( "a/file.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "b/file.txt" ) . shouldBeFileWithContent ( "abc" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "a" , "b" , { overwrite : true } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "a" , "b" , { overwrite : true } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "overwrites according to what function returns" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "from-here/foo/canada.txt" , "abc" ) ; fse . outputFileSync ( "to-here/foo/canada.txt" , "xyz" ) ; fse . outputFileSync ( "from-here/foo/eh.txt" , "abc" ) ; fse . outputFileSync ( "to-here/foo/eh.txt" , "xyz" ) ; } ; const expectations = ( ) => { path ( "from-here/foo/canada.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "to-here/foo/canada.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "from-here/foo/eh.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "to-here/foo/eh.txt" ) . shouldBeFileWithContent ( "xyz" ) ; } ; const overwrite = ( srcInspectData , destInspectData ) => { expect ( srcInspectData ) . to . have . property ( "name" ) ; expect ( srcInspectData ) . to . have . property ( "type" ) ; expect ( srcInspectData ) . to . have . property ( "mode" ) ; expect ( srcInspectData ) . to . have . property ( "accessTime" ) ; expect ( srcInspectData ) . to . have . property ( "modifyTime" ) ; expect ( srcInspectData ) . to . have . property ( "changeTime" ) ; expect ( srcInspectData ) . to . have . property ( "absolutePath" ) ; expect ( destInspectData ) . to . have . property ( "name" ) ; expect ( destInspectData ) . to . have . property ( "type" ) ; expect ( destInspectData ) . to . have . property ( "mode" ) ; expect ( destInspectData ) . to . have . property ( "accessTime" ) ; expect ( destInspectData ) . to . have . property ( "modifyTime" ) ; expect ( destInspectData ) . to . have . property ( "changeTime" ) ; expect ( destInspectData ) . to . have . property ( "absolutePath" ) ; return srcInspectData . name . includes ( "canada" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "from-here" , "to-here" , { overwrite } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "from-here" , "to-here" , { overwrite } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; } ) ; describe ( "async overwrite function can return promise" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "from-here/foo/canada.txt" , "abc" ) ; fse . outputFileSync ( "to-here/foo/canada.txt" , "xyz" ) ; fse . outputFileSync ( "from-here/foo/eh.txt" , "123" ) ; fse . outputFileSync ( "to-here/foo/eh.txt" , "456" ) ; } ; const expectations = ( ) => { path ( "from-here/foo/canada.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "to-here/foo/canada.txt" ) . shouldBeFileWithContent ( "abc" ) ; path ( "from-here/foo/eh.txt" ) . shouldBeFileWithContent ( "123" ) ; path ( "to-here/foo/eh.txt" ) . shouldBeFileWithContent ( "456" ) ; } ; const overwrite = ( srcInspectData , destInspectData ) : Promise < boolean > => { return new Promise ( ( resolve , reject ) => { jetpack . readAsync ( srcInspectData . absolutePath ) . then ( data => { resolve ( data === "abc" ) ; } ) ; } ) ; } ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "from-here" , "to-here" , { overwrite } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) . catch ( done ) ; } ) ; } ) ; describe ( "filter what to copy" , ( ) => { describe ( "by simple pattern" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "dir/file.txt" , "1" ) ; fse . outputFileSync ( "dir/file.md" , "m1" ) ; fse . outputFileSync ( "dir/a/file.txt" , "2" ) ; fse . outputFileSync ( "dir/a/file.md" , "m2" ) ; } ; const expectations = ( ) => { path ( "copy/file.txt" ) . shouldBeFileWithContent ( "1" ) ; path ( "copy/file.md" ) . shouldNotExist ( ) ; path ( "copy/a/file.txt" ) . shouldBeFileWithContent ( "2" ) ; path ( "copy/a/file.md" ) . shouldNotExist ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "dir" , "copy" , { matching : "*.txt" } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "dir" , "copy" , { matching : "*.txt" } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "by pattern anchored to copied directory" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "x/y/dir/file.txt" , "1" ) ; fse . outputFileSync ( "x/y/dir/a/file.txt" , "2" ) ; fse . outputFileSync ( "x/y/dir/a/b/file.txt" , "3" ) ; } ; const expectations = ( ) => { path ( "copy/file.txt" ) . shouldNotExist ( ) ; path ( "copy/a/file.txt" ) . shouldBeFileWithContent ( "2" ) ; path ( "copy/a/b/file.txt" ) . shouldNotExist ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "x/y/dir" , "copy" , { matching : "a/*.txt" } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "x/y/dir" , "copy" , { matching : "a/*.txt" } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "can use ./ as indication of anchor directory" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "x/y/a.txt" , "123" ) ; fse . outputFileSync ( "x/y/b/a.txt" , "456" ) ; } ; const expectations = ( ) => { path ( "copy/a.txt" ) . shouldBeFileWithContent ( "123" ) ; path ( "copy/b/a.txt" ) . shouldNotExist ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "x/y" , "copy" , { matching : "./a.txt" } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "x/y" , "copy" , { matching : "./a.txt" } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "matching works also if copying single file" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a" , "123" ) ; fse . outputFileSync ( "x" , "456" ) ; } ; const expectations = ( ) => { path ( "a-copy" ) . shouldNotExist ( ) ; path ( "x-copy" ) . shouldBeFileWithContent ( "456" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "a" , "a-copy" , { matching : "x" } ) ; jetpack . copy ( "x" , "x-copy" , { matching : "x" } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "a" , "a-copy" , { matching : "x" } ) . then ( ( ) => { return jetpack . copyAsync ( "x" , "x-copy" , { matching : "x" } ) ; } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "can use negation in patterns" , ( ) => { const preparations = ( ) => { fse . mkdirsSync ( "x/y/dir/a/b" ) ; fse . mkdirsSync ( "x/y/dir/a/x" ) ; fse . mkdirsSync ( "x/y/dir/a/y" ) ; fse . mkdirsSync ( "x/y/dir/a/z" ) ; } ; const expectations = ( ) => { path ( "copy/dir/a/b" ) . shouldBeDirectory ( ) ; path ( "copy/dir/a/x" ) . shouldNotExist ( ) ; path ( "copy/dir/a/y" ) . shouldNotExist ( ) ; path ( "copy/dir/a/z" ) . shouldNotExist ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "x/y" , "copy" , { matching : [ "**" , "!x" , "!dir/a/y" , "!./dir/a/z" ] } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "x/y" , "copy" , { matching : [ "**" , "!x" , "!dir/a/y" , "!./dir/a/z" ] } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "wildcard copies everything" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "x/file.txt" , "123" ) ; fse . outputFileSync ( "x/y/.dot" , "dot" ) ; fse . mkdirsSync ( "x/y/z" ) ; } ; const expectations = ( ) => { path ( "copy/file.txt" ) . shouldBeFileWithContent ( "123" ) ; path ( "copy/y/.dot" ) . shouldBeFileWithContent ( "dot" ) ; path ( "copy/y/z" ) . shouldBeDirectory ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "x" , "copy" , { matching : "**" } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "x" , "copy" , { matching : "**" } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; } ) ; describe ( "can copy symlink" , ( ) => { const preparations = ( ) => { fse . mkdirsSync ( "to_copy" ) ; fse . symlinkSync ( "some/file" , "to_copy/symlink" ) ; } ; const expectations = ( ) => { expect ( fse . lstatSync ( "copied/symlink" ) . isSymbolicLink ( ) ) . to . equal ( true ) ; expect ( fse . readlinkSync ( "copied/symlink" ) ) . to . equal ( helper . osSep ( "some/file" ) ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "to_copy" , "copied" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "to_copy" , "copied" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "can overwrite symlink" , ( ) => { const preparations = ( ) => { fse . mkdirsSync ( "to_copy" ) ; fse . symlinkSync ( "some/file" , "to_copy/symlink" ) ; fse . mkdirsSync ( "copied" ) ; fse . symlinkSync ( "some/other_file" , "copied/symlink" ) ; } ; const expectations = ( ) => { expect ( fse . lstatSync ( "copied/symlink" ) . isSymbolicLink ( ) ) . to . equal ( true ) ; expect ( fse . readlinkSync ( "copied/symlink" ) ) . to . equal ( helper . osSep ( "some/file" ) ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "to_copy" , "copied" , { overwrite : true } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "to_copy" , "copied" , { overwrite : true } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; describe ( "if ignoreCase=true it ignores case in patterns" , ( ) => { const preparations = ( ) => { fse . mkdirsSync ( "orig/FoO/BaR/x" ) ; } ; const expectations = ( ) => { path ( "copy/FoO/BaR/x" ) . shouldBeDirectory ( ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "orig" , "copy" , { matching : [ "foo/bar/x" ] , ignoreCase : true } ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "orig" , "copy" , { matching : [ "foo/bar/x" ] , ignoreCase : true } ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; if ( process . platform !== "win32" ) { describe ( "copies also file permissions (unix only)" , ( ) => { const preparations = ( ) => { fse . outputFileSync ( "a/b/c.txt" , "abc" ) ; fse . chmodSync ( "a/b" , "700" ) ; fse . chmodSync ( "a/b/c.txt" , "711" ) ; } ; const expectations = ( ) => { path ( "x/b" ) . shouldHaveMode ( "700" ) ; path ( "x/b/c.txt" ) . shouldHaveMode ( "711" ) ; } ; it ( "sync" , ( ) => { preparations ( ) ; jetpack . copy ( "a" , "x" ) ; expectations ( ) ; } ) ; it ( "async" , done => { preparations ( ) ; jetpack . copyAsync ( "a" , "x" ) . then ( ( ) => { expectations ( ) ; done ( ) ; } ) ; } ) ; } ) ; } describe ( "input validation" , ( ) => { const tests = [ { type : "sync" , method : jetpack . copy as any , methodName : "copy" } , { type : "async" , method : jetpack . copyAsync as any , methodName : "copyAsync" } ] ; describe ( '"from" argument' , ( ) => { tests . forEach ( test => { it ( test . type , ( ) => { expect ( ( ) => { test . method ( undefined , "xyz" ) ; } ) . to . throw ( `Argument "from" passed to ${ test . methodName }(from, to, [options]) must be a string. Received undefined` ) ; } ) ; } ) ; } ) ; describe ( '"to" argument' , ( ) => { tests . forEach ( test => { it ( test . type , ( ) => { expect ( ( ) => { test . method ( "abc" ) ; } ) . to . throw ( `Argument "to" passed to ${ test . methodName }(from, to, [options]) must be a string. Received undefined` ) ; } ) ; } ) ; } ) ; describe ( '"options" object' , ( ) => { describe ( '"overwrite" argument' , ( ) => { tests . forEach ( test => { it ( test . type , ( ) => { expect ( ( ) => { test . method ( "abc" , "xyz" , { overwrite : 1 } ) ; } ) . to . throw ( `Argument "options.overwrite" passed to ${ test . methodName }(from, to, [options]) must be a boolean or a function. Received number` ) ; } ) ; } ) ; } ) ; describe ( '"matching" argument' , ( ) => { tests . forEach ( test => { it ( test . type , ( ) => { expect ( ( ) => { test . method ( "abc" , "xyz" , { matching : 1 } ) ; } ) . to . throw ( `Argument "options.matching" passed to ${ test . methodName }(from, to, [options]) must be a string or an array of string. Received number` ) ; } ) ; } ) ; } ) ; describe ( '"ignoreCase" argument' , ( ) => { tests . forEach ( test => { it ( test . type , ( ) => { expect ( ( ) => { test . method ( "abc" , "xyz" , { ignoreCase : 1 } ) ; } ) . to . throw ( `Argument "options.ignoreCase" passed to ${ test . methodName }(from, to, [options]) must be a boolean. Received number` ) ; } ) ; } ) ; } ) ; } ) ; } ) ; } ) ;