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: any) => { 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: any) => { 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 = () => { // canada is copied path("from-here/foo/canada.txt").shouldBeFileWithContent("abc"); path("to-here/foo/canada.txt").shouldBeFileWithContent("abc"); // eh is not copied path("from-here/foo/eh.txt").shouldBeFileWithContent("abc"); path("to-here/foo/eh.txt").shouldBeFileWithContent("xyz"); }; const overwrite = ( srcInspectData: InspectResult, destInspectData: InspectResult ) => { 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 = () => { // canada is copied path("from-here/foo/canada.txt").shouldBeFileWithContent("abc"); path("to-here/foo/canada.txt").shouldBeFileWithContent("abc"); // eh is not copied path("from-here/foo/eh.txt").shouldBeFileWithContent("123"); path("to-here/foo/eh.txt").shouldBeFileWithContent("456"); }; const overwrite = ( srcInspectData: InspectResult, destInspectData: InspectResult ): Promise => { 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: [ "**", // Three different pattern types to test: "!x", "!dir/a/y", "!./dir/a/z" ] }); expectations(); }); it("async", done => { preparations(); jetpack .copyAsync("x/y", "copy", { matching: [ "**", // Three different pattern types to test: "!x", "!dir/a/y", "!./dir/a/z" ] }) .then(() => { expectations(); done(); }); }); }); describe("wildcard copies everything", () => { const preparations = () => { // Just a file fse.outputFileSync("x/file.txt", "123"); // Dot file fse.outputFileSync("x/y/.dot", "dot"); // Empty directory 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", () => { // This test actually tests nothing if performed on case-insensitive file system. 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` ); }); }); }); }); }); });