1 | import {dir} from 'tmp';
|
2 | import {mkdirSync, rmdirSync, unlinkSync, writeFileSync} from 'fs';
|
3 | import {join} from 'path';
|
4 | import {expect} from 'chai';
|
5 | import {assertFileSystemContract, content, dirName, fileName, ignoredDir, ignoredFile} from './implementation-suite'
|
6 | import {EventsMatcher} from '../test-kit/drivers/events-matcher';
|
7 | import {LocalFileSystem, FileSystem, fileSystemEventNames} from '../src/nodejs';
|
8 |
|
9 | describe(`the local filesystem implementation`, () => {
|
10 | let dirCleanup: () => void, rootPath: string, testPath: string;
|
11 | let counter = 0;
|
12 | let disposableFileSystem: LocalFileSystem;
|
13 |
|
14 | before(done => {
|
15 | dir({unsafeCleanup: true}, (_err, path, cleanupCallback) => {
|
16 | dirCleanup = cleanupCallback;
|
17 | rootPath = path;
|
18 | done();
|
19 | })
|
20 | });
|
21 | after(() => {
|
22 | try {
|
23 | dirCleanup();
|
24 |
|
25 | } catch (e) {
|
26 | console.log('cleanup error', e);
|
27 | }
|
28 | });
|
29 | afterEach(() => {
|
30 |
|
31 | disposableFileSystem && disposableFileSystem.dispose();
|
32 | });
|
33 |
|
34 | function getFS() {
|
35 | testPath = join(rootPath, 'fs_' + (counter++));
|
36 | mkdirSync(testPath);
|
37 | disposableFileSystem = new LocalFileSystem(
|
38 | testPath,
|
39 | [ignoredDir, ignoredFile]
|
40 | );
|
41 | return disposableFileSystem.init();
|
42 | }
|
43 |
|
44 | const eventMatcherOptions: EventsMatcher.Options = {
|
45 | retries: 40,
|
46 | interval: 50,
|
47 | timeout: 2500,
|
48 | noExtraEventsGrace: 150
|
49 | };
|
50 | assertFileSystemContract(getFS, eventMatcherOptions);
|
51 | describe(`external changes`, () => {
|
52 | let fs: FileSystem;
|
53 | let matcher: EventsMatcher;
|
54 | beforeEach(() => {
|
55 | matcher = new EventsMatcher(eventMatcherOptions);
|
56 | return getFS().then(newFs => {
|
57 | fs = newFs;
|
58 | matcher.track(fs.events, ...fileSystemEventNames);
|
59 | });
|
60 | });
|
61 |
|
62 | it(`handles dir creation`, () => {
|
63 | const path = join(testPath, dirName);
|
64 | mkdirSync(path);
|
65 | return expect(fs.loadDirectoryTree())
|
66 | .to.eventually.have.property('children').eql([
|
67 | {children: [], fullPath: dirName, name: dirName, type: 'dir'}
|
68 | ]);
|
69 | });
|
70 |
|
71 | it(`handles dir deletion`, () => {
|
72 | const path = join(testPath, dirName);
|
73 | mkdirSync(path);
|
74 | rmdirSync(path);
|
75 | return expect(fs.loadDirectoryTree()).to.eventually.have.property('children').eql([]);
|
76 | });
|
77 |
|
78 | it(`handles file creation`, () => {
|
79 | const path = join(testPath, fileName);
|
80 | writeFileSync(path, content);
|
81 | return expect(fs.loadTextFile(fileName)).to.eventually.equals(content);
|
82 | });
|
83 |
|
84 | it(`handles file deletion`, () => {
|
85 | const path = join(testPath, fileName);
|
86 | writeFileSync(path, content);
|
87 | unlinkSync(path);
|
88 | return expect(fs.loadTextFile(fileName)).to.eventually.be.rejected;
|
89 | });
|
90 |
|
91 | it(`handles file change`, () => {
|
92 | const path = join(testPath, fileName);
|
93 | const newContent = `_${content}`;
|
94 | writeFileSync(path, content);
|
95 | return matcher.expect([{type: 'fileCreated', fullPath: fileName, newContent: content}])
|
96 | .then(() => {
|
97 | writeFileSync(path, newContent);
|
98 | return Promise.resolve();
|
99 | })
|
100 | .then(() => expect(fs.loadTextFile(fileName)).to.eventually.equals(newContent));
|
101 | });
|
102 |
|
103 | it(`ignores events from ignored dir`, () => {
|
104 | mkdirSync(join(testPath, ignoredDir))
|
105 | return matcher.expect([])
|
106 | });
|
107 |
|
108 | it(`ignores events from ignored file`, () => {
|
109 | mkdirSync(join(testPath, dirName))
|
110 | return matcher.expect([{type: 'directoryCreated', fullPath: dirName}])
|
111 | .then(() => writeFileSync(join(testPath, ignoredFile), content))
|
112 | .then(() => matcher.expect([]))
|
113 | });
|
114 |
|
115 | it(`loadDirectoryTree() ignores ignored folder and file`, () => {
|
116 | const expectedStructure = {
|
117 | name: '',
|
118 | type: 'dir',
|
119 | fullPath: '',
|
120 | children: [{name: dirName, type: 'dir', fullPath: dirName, children: []}]
|
121 | };
|
122 | mkdirSync(join(testPath, ignoredDir))
|
123 | mkdirSync(join(testPath, dirName))
|
124 | writeFileSync(join(testPath, ignoredFile), content)
|
125 | return expect(fs.loadDirectoryTree()).to.eventually.deep.equal(expectedStructure)
|
126 | });
|
127 |
|
128 | it(`loadDirectoryTree() ignores ignored folder with special characters`, () => {
|
129 | const expectedStructure = {
|
130 | name: '',
|
131 | type: 'dir',
|
132 | fullPath: '',
|
133 | children: [{name: dirName, type: 'dir', fullPath: dirName, children: []}]
|
134 | };
|
135 | mkdirSync(join(testPath, ignoredDir))
|
136 | mkdirSync(join(testPath, ignoredDir, 'name-with-dashes'))
|
137 | mkdirSync(join(testPath, ignoredDir, 'name-with-dashes', '.name_starts_with_dot'))
|
138 | mkdirSync(join(testPath, ignoredDir, 'name-with-dashes', '.name_starts_with_dot', '.name_starts_with_dot'))
|
139 | mkdirSync(join(testPath, dirName))
|
140 | return expect(fs.loadDirectoryTree()).to.eventually.deep.equal(expectedStructure)
|
141 | });
|
142 |
|
143 | it(`ignores events in dot-folders and files`, () => {
|
144 | mkdirSync(join(testPath, ignoredDir));
|
145 | mkdirSync(join(testPath, ignoredDir, `.${dirName}`));
|
146 | writeFileSync(join(testPath, ignoredDir, `.${dirName}`, `.${fileName}`), content);
|
147 |
|
148 | return matcher.expect([]);
|
149 | });
|
150 |
|
151 | it(`loading existed ignored file - fails`, function () {
|
152 | mkdirSync(join(testPath, dirName))
|
153 | writeFileSync(join(testPath, ignoredFile), content)
|
154 |
|
155 | return expect(fs.loadTextFile(ignoredFile)).to.be.rejectedWith(Error)
|
156 | });
|
157 |
|
158 | it(`emits 'unexpectedError' if 'loadTextFile' rejected in watcher 'add' callback`, () => {
|
159 | fs.loadTextFile = () => Promise.reject('go away!');
|
160 | const path = join(testPath, fileName);
|
161 | writeFileSync(path, content);
|
162 | return matcher.expect([{type: 'unexpectedError'}]);
|
163 | });
|
164 |
|
165 | it(`emits 'unexpectedError' if 'loadTextFile' rejected in watcher 'change' callback`, () => {
|
166 | return fs.saveFile(fileName, content)
|
167 | .then(() => fs.loadTextFile = () => Promise.reject('go away!'))
|
168 | .then(() => fs.saveFile(fileName, `_${content}`))
|
169 | .then(() => matcher.expect([{type: 'unexpectedError'}]))
|
170 | });
|
171 | });
|
172 | });
|