UNPKG

19.7 kBPlain TextView Raw
1import * as fse from "fs-extra";
2import { expect } from "chai";
3import path from "./assert_path";
4import helper from "./helper";
5import * as jetpack from "..";
6import { InspectResult } from "../types";
7
8describe("copy", () => {
9 beforeEach(helper.setCleanTestCwd);
10 afterEach(helper.switchBackToCorrectCwd);
11
12 describe("copies a file", () => {
13 const preparations = () => {
14 fse.outputFileSync("file.txt", "abc");
15 };
16
17 const expectations = () => {
18 path("file.txt").shouldBeFileWithContent("abc");
19 path("file_copied.txt").shouldBeFileWithContent("abc");
20 };
21
22 it("sync", () => {
23 preparations();
24 jetpack.copy("file.txt", "file_copied.txt");
25 expectations();
26 });
27
28 it("async", done => {
29 preparations();
30 jetpack.copyAsync("file.txt", "file_copied.txt").then(() => {
31 expectations();
32 done();
33 });
34 });
35 });
36
37 describe("can copy file to nonexistent directory (will create directory)", () => {
38 const preparations = () => {
39 fse.outputFileSync("file.txt", "abc");
40 };
41
42 const expectations = () => {
43 path("file.txt").shouldBeFileWithContent("abc");
44 path("dir/dir/file.txt").shouldBeFileWithContent("abc");
45 };
46
47 it("sync", () => {
48 preparations();
49 jetpack.copy("file.txt", "dir/dir/file.txt");
50 expectations();
51 });
52
53 it("async", done => {
54 preparations();
55 jetpack.copyAsync("file.txt", "dir/dir/file.txt").then(() => {
56 expectations();
57 done();
58 });
59 });
60 });
61
62 describe("copies empty directory", () => {
63 const preparations = () => {
64 fse.mkdirsSync("dir");
65 };
66
67 const expectations = () => {
68 path("copied/dir").shouldBeDirectory();
69 };
70
71 it("sync", () => {
72 preparations();
73 jetpack.copy("dir", "copied/dir");
74 expectations();
75 });
76
77 it("async", done => {
78 preparations();
79 jetpack.copyAsync("dir", "copied/dir").then(() => {
80 expectations();
81 done();
82 });
83 });
84 });
85
86 describe("copies a tree of files", () => {
87 const preparations = () => {
88 fse.outputFileSync("a/f1.txt", "abc");
89 fse.outputFileSync("a/b/f2.txt", "123");
90 fse.mkdirsSync("a/b/c");
91 };
92
93 const expectations = () => {
94 path("copied/a/f1.txt").shouldBeFileWithContent("abc");
95 path("copied/a/b/c").shouldBeDirectory();
96 path("copied/a/b/f2.txt").shouldBeFileWithContent("123");
97 };
98
99 it("sync", () => {
100 preparations();
101 jetpack.copy("a", "copied/a");
102 expectations();
103 });
104
105 it("async", done => {
106 preparations();
107 jetpack.copyAsync("a", "copied/a").then(() => {
108 expectations();
109 done();
110 });
111 });
112 });
113
114 describe("generates nice error if source path doesn't exist", () => {
115 const expectations = (err: any) => {
116 expect(err.code).to.equal("ENOENT");
117 expect(err.message).to.have.string("Path to copy doesn't exist");
118 };
119
120 it("sync", () => {
121 try {
122 jetpack.copy("a", "b");
123 throw new Error("Expected error to be thrown");
124 } catch (err) {
125 expectations(err);
126 }
127 });
128
129 it("async", done => {
130 jetpack.copyAsync("a", "b").catch(err => {
131 expectations(err);
132 done();
133 });
134 });
135 });
136
137 describe("respects internal CWD of jetpack instance", () => {
138 const preparations = () => {
139 fse.outputFileSync("a/b.txt", "abc");
140 };
141
142 const expectations = () => {
143 path("a/b.txt").shouldBeFileWithContent("abc");
144 path("a/x.txt").shouldBeFileWithContent("abc");
145 };
146
147 it("sync", () => {
148 const jetContext = jetpack.cwd("a");
149 preparations();
150 jetContext.copy("b.txt", "x.txt");
151 expectations();
152 });
153
154 it("async", done => {
155 const jetContext = jetpack.cwd("a");
156 preparations();
157 jetContext.copyAsync("b.txt", "x.txt").then(() => {
158 expectations();
159 done();
160 });
161 });
162 });
163
164 describe("overwriting behaviour", () => {
165 describe("does not overwrite by default", () => {
166 const preparations = () => {
167 fse.outputFileSync("a/file.txt", "abc");
168 fse.mkdirsSync("b");
169 };
170
171 const expectations = (err: any) => {
172 expect(err.code).to.equal("EEXIST");
173 expect(err.message).to.have.string("Destination path already exists");
174 };
175
176 it("sync", () => {
177 preparations();
178 try {
179 jetpack.copy("a", "b");
180 throw new Error("Expected error to be thrown");
181 } catch (err) {
182 expectations(err);
183 }
184 });
185
186 it("async", done => {
187 preparations();
188 jetpack.copyAsync("a", "b").catch(err => {
189 expectations(err);
190 done();
191 });
192 });
193 });
194
195 describe("overwrites if it was specified", () => {
196 const preparations = () => {
197 fse.outputFileSync("a/file.txt", "abc");
198 fse.outputFileSync("b/file.txt", "xyz");
199 };
200
201 const expectations = () => {
202 path("a/file.txt").shouldBeFileWithContent("abc");
203 path("b/file.txt").shouldBeFileWithContent("abc");
204 };
205
206 it("sync", () => {
207 preparations();
208 jetpack.copy("a", "b", { overwrite: true });
209 expectations();
210 });
211
212 it("async", done => {
213 preparations();
214 jetpack.copyAsync("a", "b", { overwrite: true }).then(() => {
215 expectations();
216 done();
217 });
218 });
219 });
220
221 describe("overwrites according to what function returns", () => {
222 const preparations = () => {
223 fse.outputFileSync("from-here/foo/canada.txt", "abc");
224 fse.outputFileSync("to-here/foo/canada.txt", "xyz");
225 fse.outputFileSync("from-here/foo/eh.txt", "abc");
226 fse.outputFileSync("to-here/foo/eh.txt", "xyz");
227 };
228
229 const expectations = () => {
230 // canada is copied
231 path("from-here/foo/canada.txt").shouldBeFileWithContent("abc");
232 path("to-here/foo/canada.txt").shouldBeFileWithContent("abc");
233
234 // eh is not copied
235 path("from-here/foo/eh.txt").shouldBeFileWithContent("abc");
236 path("to-here/foo/eh.txt").shouldBeFileWithContent("xyz");
237 };
238
239 const overwrite = (
240 srcInspectData: InspectResult,
241 destInspectData: InspectResult
242 ) => {
243 expect(srcInspectData).to.have.property("name");
244 expect(srcInspectData).to.have.property("type");
245 expect(srcInspectData).to.have.property("mode");
246 expect(srcInspectData).to.have.property("accessTime");
247 expect(srcInspectData).to.have.property("modifyTime");
248 expect(srcInspectData).to.have.property("changeTime");
249 expect(srcInspectData).to.have.property("absolutePath");
250
251 expect(destInspectData).to.have.property("name");
252 expect(destInspectData).to.have.property("type");
253 expect(destInspectData).to.have.property("mode");
254 expect(destInspectData).to.have.property("accessTime");
255 expect(destInspectData).to.have.property("modifyTime");
256 expect(destInspectData).to.have.property("changeTime");
257 expect(destInspectData).to.have.property("absolutePath");
258
259 return srcInspectData.name.includes("canada");
260 };
261
262 it("sync", () => {
263 preparations();
264 jetpack.copy("from-here", "to-here", { overwrite });
265 expectations();
266 });
267
268 it("async", done => {
269 preparations();
270 jetpack.copyAsync("from-here", "to-here", { overwrite }).then(() => {
271 expectations();
272 done();
273 });
274 });
275 });
276 });
277
278 describe("async overwrite function can return promise", () => {
279 const preparations = () => {
280 fse.outputFileSync("from-here/foo/canada.txt", "abc");
281 fse.outputFileSync("to-here/foo/canada.txt", "xyz");
282 fse.outputFileSync("from-here/foo/eh.txt", "123");
283 fse.outputFileSync("to-here/foo/eh.txt", "456");
284 };
285
286 const expectations = () => {
287 // canada is copied
288 path("from-here/foo/canada.txt").shouldBeFileWithContent("abc");
289 path("to-here/foo/canada.txt").shouldBeFileWithContent("abc");
290
291 // eh is not copied
292 path("from-here/foo/eh.txt").shouldBeFileWithContent("123");
293 path("to-here/foo/eh.txt").shouldBeFileWithContent("456");
294 };
295
296 const overwrite = (
297 srcInspectData: InspectResult,
298 destInspectData: InspectResult
299 ): Promise<boolean> => {
300 return new Promise((resolve, reject) => {
301 jetpack.readAsync(srcInspectData.absolutePath).then(data => {
302 resolve(data === "abc");
303 });
304 });
305 };
306
307 it("async", done => {
308 preparations();
309 jetpack
310 .copyAsync("from-here", "to-here", { overwrite })
311 .then(() => {
312 expectations();
313 done();
314 })
315 .catch(done);
316 });
317 });
318
319 describe("filter what to copy", () => {
320 describe("by simple pattern", () => {
321 const preparations = () => {
322 fse.outputFileSync("dir/file.txt", "1");
323 fse.outputFileSync("dir/file.md", "m1");
324 fse.outputFileSync("dir/a/file.txt", "2");
325 fse.outputFileSync("dir/a/file.md", "m2");
326 };
327
328 const expectations = () => {
329 path("copy/file.txt").shouldBeFileWithContent("1");
330 path("copy/file.md").shouldNotExist();
331 path("copy/a/file.txt").shouldBeFileWithContent("2");
332 path("copy/a/file.md").shouldNotExist();
333 };
334
335 it("sync", () => {
336 preparations();
337 jetpack.copy("dir", "copy", { matching: "*.txt" });
338 expectations();
339 });
340
341 it("async", done => {
342 preparations();
343 jetpack.copyAsync("dir", "copy", { matching: "*.txt" }).then(() => {
344 expectations();
345 done();
346 });
347 });
348 });
349
350 describe("by pattern anchored to copied directory", () => {
351 const preparations = () => {
352 fse.outputFileSync("x/y/dir/file.txt", "1");
353 fse.outputFileSync("x/y/dir/a/file.txt", "2");
354 fse.outputFileSync("x/y/dir/a/b/file.txt", "3");
355 };
356
357 const expectations = () => {
358 path("copy/file.txt").shouldNotExist();
359 path("copy/a/file.txt").shouldBeFileWithContent("2");
360 path("copy/a/b/file.txt").shouldNotExist();
361 };
362
363 it("sync", () => {
364 preparations();
365 jetpack.copy("x/y/dir", "copy", { matching: "a/*.txt" });
366 expectations();
367 });
368
369 it("async", done => {
370 preparations();
371 jetpack
372 .copyAsync("x/y/dir", "copy", { matching: "a/*.txt" })
373 .then(() => {
374 expectations();
375 done();
376 });
377 });
378 });
379
380 describe("can use ./ as indication of anchor directory", () => {
381 const preparations = () => {
382 fse.outputFileSync("x/y/a.txt", "123");
383 fse.outputFileSync("x/y/b/a.txt", "456");
384 };
385
386 const expectations = () => {
387 path("copy/a.txt").shouldBeFileWithContent("123");
388 path("copy/b/a.txt").shouldNotExist();
389 };
390
391 it("sync", () => {
392 preparations();
393 jetpack.copy("x/y", "copy", { matching: "./a.txt" });
394 expectations();
395 });
396
397 it("async", done => {
398 preparations();
399 jetpack.copyAsync("x/y", "copy", { matching: "./a.txt" }).then(() => {
400 expectations();
401 done();
402 });
403 });
404 });
405
406 describe("matching works also if copying single file", () => {
407 const preparations = () => {
408 fse.outputFileSync("a", "123");
409 fse.outputFileSync("x", "456");
410 };
411
412 const expectations = () => {
413 path("a-copy").shouldNotExist();
414 path("x-copy").shouldBeFileWithContent("456");
415 };
416
417 it("sync", () => {
418 preparations();
419 jetpack.copy("a", "a-copy", { matching: "x" });
420 jetpack.copy("x", "x-copy", { matching: "x" });
421 expectations();
422 });
423
424 it("async", done => {
425 preparations();
426 jetpack
427 .copyAsync("a", "a-copy", { matching: "x" })
428 .then(() => {
429 return jetpack.copyAsync("x", "x-copy", { matching: "x" });
430 })
431 .then(() => {
432 expectations();
433 done();
434 });
435 });
436 });
437
438 describe("can use negation in patterns", () => {
439 const preparations = () => {
440 fse.mkdirsSync("x/y/dir/a/b");
441 fse.mkdirsSync("x/y/dir/a/x");
442 fse.mkdirsSync("x/y/dir/a/y");
443 fse.mkdirsSync("x/y/dir/a/z");
444 };
445
446 const expectations = () => {
447 path("copy/dir/a/b").shouldBeDirectory();
448 path("copy/dir/a/x").shouldNotExist();
449 path("copy/dir/a/y").shouldNotExist();
450 path("copy/dir/a/z").shouldNotExist();
451 };
452
453 it("sync", () => {
454 preparations();
455 jetpack.copy("x/y", "copy", {
456 matching: [
457 "**",
458 // Three different pattern types to test:
459 "!x",
460 "!dir/a/y",
461 "!./dir/a/z"
462 ]
463 });
464 expectations();
465 });
466
467 it("async", done => {
468 preparations();
469 jetpack
470 .copyAsync("x/y", "copy", {
471 matching: [
472 "**",
473 // Three different pattern types to test:
474 "!x",
475 "!dir/a/y",
476 "!./dir/a/z"
477 ]
478 })
479 .then(() => {
480 expectations();
481 done();
482 });
483 });
484 });
485
486 describe("wildcard copies everything", () => {
487 const preparations = () => {
488 // Just a file
489 fse.outputFileSync("x/file.txt", "123");
490 // Dot file
491 fse.outputFileSync("x/y/.dot", "dot");
492 // Empty directory
493 fse.mkdirsSync("x/y/z");
494 };
495
496 const expectations = () => {
497 path("copy/file.txt").shouldBeFileWithContent("123");
498 path("copy/y/.dot").shouldBeFileWithContent("dot");
499 path("copy/y/z").shouldBeDirectory();
500 };
501
502 it("sync", () => {
503 preparations();
504 jetpack.copy("x", "copy", { matching: "**" });
505 expectations();
506 });
507
508 it("async", done => {
509 preparations();
510 jetpack.copyAsync("x", "copy", { matching: "**" }).then(() => {
511 expectations();
512 done();
513 });
514 });
515 });
516 });
517
518 describe("can copy symlink", () => {
519 const preparations = () => {
520 fse.mkdirsSync("to_copy");
521 fse.symlinkSync("some/file", "to_copy/symlink");
522 };
523 const expectations = () => {
524 expect(fse.lstatSync("copied/symlink").isSymbolicLink()).to.equal(true);
525 expect(fse.readlinkSync("copied/symlink")).to.equal(
526 helper.osSep("some/file")
527 );
528 };
529
530 it("sync", () => {
531 preparations();
532 jetpack.copy("to_copy", "copied");
533 expectations();
534 });
535
536 it("async", done => {
537 preparations();
538 jetpack.copyAsync("to_copy", "copied").then(() => {
539 expectations();
540 done();
541 });
542 });
543 });
544
545 describe("can overwrite symlink", () => {
546 const preparations = () => {
547 fse.mkdirsSync("to_copy");
548 fse.symlinkSync("some/file", "to_copy/symlink");
549 fse.mkdirsSync("copied");
550 fse.symlinkSync("some/other_file", "copied/symlink");
551 };
552
553 const expectations = () => {
554 expect(fse.lstatSync("copied/symlink").isSymbolicLink()).to.equal(true);
555 expect(fse.readlinkSync("copied/symlink")).to.equal(
556 helper.osSep("some/file")
557 );
558 };
559
560 it("sync", () => {
561 preparations();
562 jetpack.copy("to_copy", "copied", { overwrite: true });
563 expectations();
564 });
565
566 it("async", done => {
567 preparations();
568 jetpack.copyAsync("to_copy", "copied", { overwrite: true }).then(() => {
569 expectations();
570 done();
571 });
572 });
573 });
574
575 describe("if ignoreCase=true it ignores case in patterns", () => {
576 // This test actually tests nothing if performed on case-insensitive file system.
577
578 const preparations = () => {
579 fse.mkdirsSync("orig/FoO/BaR/x");
580 };
581
582 const expectations = () => {
583 path("copy/FoO/BaR/x").shouldBeDirectory();
584 };
585
586 it("sync", () => {
587 preparations();
588 jetpack.copy("orig", "copy", {
589 matching: ["foo/bar/x"],
590 ignoreCase: true
591 });
592 expectations();
593 });
594
595 it("async", done => {
596 preparations();
597 jetpack
598 .copyAsync("orig", "copy", {
599 matching: ["foo/bar/x"],
600 ignoreCase: true
601 })
602 .then(() => {
603 expectations();
604 done();
605 });
606 });
607 });
608
609 if (process.platform !== "win32") {
610 describe("copies also file permissions (unix only)", () => {
611 const preparations = () => {
612 fse.outputFileSync("a/b/c.txt", "abc");
613 fse.chmodSync("a/b", "700");
614 fse.chmodSync("a/b/c.txt", "711");
615 };
616
617 const expectations = () => {
618 path("x/b").shouldHaveMode("700");
619 path("x/b/c.txt").shouldHaveMode("711");
620 };
621
622 it("sync", () => {
623 preparations();
624 jetpack.copy("a", "x");
625 expectations();
626 });
627
628 it("async", done => {
629 preparations();
630 jetpack.copyAsync("a", "x").then(() => {
631 expectations();
632 done();
633 });
634 });
635 });
636 }
637
638 describe("input validation", () => {
639 const tests = [
640 { type: "sync", method: jetpack.copy as any, methodName: "copy" },
641 {
642 type: "async",
643 method: jetpack.copyAsync as any,
644 methodName: "copyAsync"
645 }
646 ];
647
648 describe('"from" argument', () => {
649 tests.forEach(test => {
650 it(test.type, () => {
651 expect(() => {
652 test.method(undefined, "xyz");
653 }).to.throw(
654 `Argument "from" passed to ${
655 test.methodName
656 }(from, to, [options]) must be a string. Received undefined`
657 );
658 });
659 });
660 });
661
662 describe('"to" argument', () => {
663 tests.forEach(test => {
664 it(test.type, () => {
665 expect(() => {
666 test.method("abc");
667 }).to.throw(
668 `Argument "to" passed to ${
669 test.methodName
670 }(from, to, [options]) must be a string. Received undefined`
671 );
672 });
673 });
674 });
675
676 describe('"options" object', () => {
677 describe('"overwrite" argument', () => {
678 tests.forEach(test => {
679 it(test.type, () => {
680 expect(() => {
681 test.method("abc", "xyz", { overwrite: 1 });
682 }).to.throw(
683 `Argument "options.overwrite" passed to ${
684 test.methodName
685 }(from, to, [options]) must be a boolean or a function. Received number`
686 );
687 });
688 });
689 });
690 describe('"matching" argument', () => {
691 tests.forEach(test => {
692 it(test.type, () => {
693 expect(() => {
694 test.method("abc", "xyz", { matching: 1 });
695 }).to.throw(
696 `Argument "options.matching" passed to ${
697 test.methodName
698 }(from, to, [options]) must be a string or an array of string. Received number`
699 );
700 });
701 });
702 });
703 describe('"ignoreCase" argument', () => {
704 tests.forEach(test => {
705 it(test.type, () => {
706 expect(() => {
707 test.method("abc", "xyz", { ignoreCase: 1 });
708 }).to.throw(
709 `Argument "options.ignoreCase" passed to ${
710 test.methodName
711 }(from, to, [options]) must be a boolean. Received number`
712 );
713 });
714 });
715 });
716 });
717 });
718});