/
file-list.spec.coffee
480 lines (360 loc) · 18.1 KB
/
file-list.spec.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
#==============================================================================
# lib/file-list.js module
#==============================================================================
describe 'file-list', ->
mocks = require 'mocks'
events = require 'events'
path = require 'path'
config = require '../../lib/config'
onFileListModifiedSpy = mocks_ = null
m = list = emitter = fileListModifiedPromise = preprocessMock = null
mockGlob = mocks.glob.create
'/some/*.js': ['/some/a.js', '/some/b.js']
'*.txt': ['/c.txt', '/a.txt', '/b.txt']
'*.js': ['/folder', '/folder/x.js']
'/a.*': ['/a.txt']
# we need at least 11 elements to trigger V8's quick sort
'**': ['/a.txt', '/b.txt', '/c.txt', '/a.txt', '/c.txt', '/b.txt', '/a.txt', '/c.txt',
'/a.txt', '/a.txt', '/c.txt']
mockFs = mocks.fs.create
some:
'0.js': mocks.fs.file '2012-04-04'
'a.js': mocks.fs.file '2012-04-04'
'b.js': mocks.fs.file '2012-05-05'
folder:
'x.js': mocks.fs.file 0
'a.txt': mocks.fs.file 0
'b.txt': mocks.fs.file 0
'c.txt': mocks.fs.file 0
'a.js': mocks.fs.file '2012-01-01'
pathsFrom = (files) ->
files.map (file) -> file.path
findFile = (path, files) ->
found = null
files.forEach (file) ->
found = file if file.path is path and found is null
throw new Error "Files do not contain '#{path}'" if found is null
found
# refresh the list and
# - ignore the file_list_modified event fired during refresh()
# - rethrow exceptions
refreshListAndThen = (resume) ->
list.refresh().then((files) ->
onFileListModifiedSpy.reset()
resume files
).done()
# create an array of pattern objects from given strings
patterns = (strings...) ->
new config.Pattern(str) for str in strings
beforeEach ->
mocks_ =
glob: mockGlob
fs: mockFs
minimatch: require('minimatch')
m = mocks.loadFile __dirname + '/../../lib/file-list.js', mocks_
onFileListModifiedSpy = sinon.spy()
emitter = new events.EventEmitter
emitter.on 'file_list_modified', onFileListModifiedSpy
preprocessMock = sinon.spy((file, done) -> process.nextTick done)
#============================================================================
# List.refresh()
#============================================================================
describe 'refresh', ->
it 'should resolve patterns, keeping the order', (done) ->
mocks.predictableNextTick.pattern = [1, 0]
list = new m.List patterns('/some/*.js', '*.txt'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(list.buckets.length).to.equal 2
# first bucket == first pattern (even though callbacks were reversed)
expect(pathsFrom list.buckets[0]).to.contain '/some/a.js', '/some/b.js'
expect(pathsFrom list.buckets[1]).to.contain '/a.txt', '/b.txt', '/c.txt'
done()
it 'should ignore directories', (done) ->
list = new m.List patterns('*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom list.buckets[0]).to.contain '/folder/x.js'
expect(pathsFrom list.buckets[0]).not.to.contain '/folder/'
done()
it 'should set mtime for every file', (done) ->
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(findFile('/some/a.js', list.buckets[0]).mtime).to.deep.equal new Date '2012-04-04'
expect(findFile('/some/b.js', list.buckets[0]).mtime).to.deep.equal new Date '2012-05-05'
done()
it 'should ignore files matching excludes', (done) ->
list = new m.List patterns('*.txt'), ['/a.*', '**/b.txt'], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom list.buckets[0]).to.contain '/c.txt'
expect(pathsFrom list.buckets[0]).not.to.contain '/a.txt'
expect(pathsFrom list.buckets[0]).not.to.contain '/b.txt'
done()
it 'should not glob urls and set isUrl flag', (done) ->
list = new m.List patterns('http://some.com'), [], emitter
refreshListAndThen (files) ->
expect(findFile('http://some.com', list.buckets[0]).isUrl).to.equal true
done()
it 'should preprocess all files', (done) ->
# MATCH /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(preprocessMock).to.have.been.called
expect(preprocessMock.callCount).to.equal 2
done()
it 'should return a promise with list of files', (done) ->
# MATCH /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(files.included).to.exist
expect(files.served).to.exist
done()
it 'should handle fs.stat errors', (done) ->
sinon.stub(mockFs, 'stat').yields([new Error(), null])
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
mockFs.stat.restore()
done()
#============================================================================
# List.reload()
#============================================================================
describe 'reload', ->
it 'should reload the patterns and return promise', (done) ->
# MATCH /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
# MATCH /c.txt, /a.txt, /b.txt
list.reload(patterns('*.txt'), []).then (files) ->
expect(files.included).to.exist
expect(files.served).to.exist
expect(pathsFrom files.served).to.deep.equal ['/a.txt', '/b.txt', '/c.txt']
done()
#============================================================================
# List.getServedFiles()
#============================================================================
describe 'getServedFiles', ->
# this method does not exist anymore, results are returned as a promise
it 'should return flat array of resolved files', (done) ->
list = new m.List patterns('*.txt'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(files.served.length).to.equal 3
expect(pathsFrom files.served).to.contain '/a.txt', '/b.txt', '/c.txt'
done()
it 'should return unique set', (done) ->
list = new m.List patterns('/a.*', '*.txt'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(files.served.length).to.equal 3
expect(pathsFrom files.served).to.contain '/a.txt', '/b.txt', '/c.txt'
done()
it 'should sort files within buckets and keep order of patterns (buckets)', (done) ->
# /a.* => /a.txt [MATCH in *.txt as well]
# /some/*.js => /some/a.js, /some/b.js [/some/b.js EXCLUDED]
# *.txt => /c.txt, a.txt, b.txt [UNSORTED]
list = new m.List patterns('/a.*', '/some/*.js', '*.txt'), ['**/b.js'], emitter,
preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom files.served).to.deep.equal ['/a.txt', '/some/a.js', '/b.txt', '/c.txt']
done()
it 'should sort files within buckets (if more than 11 elements)', (done) ->
# regression for sorting many items
list = new m.List patterns('**'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom files.served).to.deep.equal ['/a.txt', '/b.txt', '/c.txt']
done()
it 'should return only served files', (done) ->
# /a.* => /a.txt [served TRUE]
# /some/*.js => /some/a.js, /some/b.js [served FALSE]
files = [new config.Pattern('/a.*', true), new config.Pattern('/some/*.js', false)]
list = new m.List files, [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom files.served).to.deep.equal ['/a.txt']
done()
#============================================================================
# List.getIncludedFiles()
#============================================================================
describe 'getIncludedFiles', ->
# this method does not exist anymore, results are returned as a promise
it 'should return flat array of included files', (done) ->
# /a.* => /a.txt [included FALSE]
# /some/*.js => /some/a.js, /some/b.js [included TRUE]
files = [new config.Pattern('/a.*', true, false), new config.Pattern('/some/*.js')]
list = new m.List files, [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom files.included).not.to.contain '/a.txt'
expect(pathsFrom files.included).to.deep.equal ['/some/a.js', '/some/b.js']
done()
#============================================================================
# List.addFile()
#============================================================================
describe 'addFile', ->
waitForAddingFile = (done, path, resume) ->
emitter.once 'file_list_modified', (promise) ->
expect(promise).to.be.fulfilled.then(resume).should.notify(done)
list.addFile path
it 'should add the file to correct position (bucket)', (done) ->
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
expect(pathsFrom files.served).to.deep.equal ['/some/a.js', '/some/b.js', '/a.txt']
waitForAddingFile done, '/a.js', (files) ->
expect(pathsFrom files.served).to.deep.equal [
'/some/a.js', '/some/b.js', '/a.js', '/a.txt'
]
it 'should fire "file_list_modified" and pass a promise', (done) ->
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
waitForAddingFile done, '/a.js', (files) ->
expect(onFileListModifiedSpy).to.have.been.called
expect(files).to.exist
it 'should not add excluded file (and not fire event)', (done) ->
list = new m.List patterns('/some/*.js', '/a.*'), ['/*.js'], emitter, preprocessMock
refreshListAndThen (files) ->
list.addFile '/a.js', ->
expect(onFileListModifiedSpy).not.to.have.been.called
done()
it 'should not add file (neither fire event) if file already in the list', (done) ->
# chokidar on fucking windows might fire the event multiple times
# and we want to ignore initial adding as well
# MATCH: /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js'), [], emitter, preprocessMock
refreshListAndThen (files) ->
list.addFile '/some/a.js', ->
expect(onFileListModifiedSpy).not.to.have.been.called
done()
it 'should set proper mtime of new file', (done) ->
list = new m.List patterns('/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
waitForAddingFile done, '/a.js', (files) ->
expect(findFile('/a.js', files.served).mtime).to.deep.equal new Date '2012-01-01'
it 'should preprocess added file', (done) ->
# MATCH: /a.txt
list = new m.List patterns('/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
preprocessMock.reset()
waitForAddingFile done, '/a.js', ->
expect(preprocessMock).to.have.been.calledOnce
expect(preprocessMock.args[0][0].originalPath).to.equal '/a.js'
#============================================================================
# List.changeFile()
#============================================================================
describe 'changeFile', ->
waitForChangingFile = (done, path, resume) ->
emitter.once 'file_list_modified', (promise) ->
expect(promise).to.be.fulfilled.then(resume).should.notify(done)
list.changeFile path
it 'should update mtime and fire "file_list_modified"', (done) ->
# MATCH: /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
mockFs._touchFile '/some/b.js', '2020-01-01'
waitForChangingFile done, '/some/b.js', (files) ->
expect(onFileListModifiedSpy).to.have.been.called
expect(findFile('/some/b.js', files.served).mtime).to.deep.equal new Date '2020-01-01'
it 'should not fire "file_list_modified" if no matching file', (done) ->
# MATCH: /some/a.js
list = new m.List patterns('/some/*.js', '/a.*'), ['/some/b.js'], emitter, preprocessMock
refreshListAndThen (files) ->
mockFs._touchFile '/some/b.js', '2020-01-01'
list.changeFile '/some/b.js', ->
expect(onFileListModifiedSpy).not.to.have.been.called
done()
it 'should not fire "file_list_modified" if mtime has not changed', (done) ->
# chokidar on fucking windows sometimes fires event multiple times
# MATCH: /some/a.js, /some/b.js, /a.txt
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
# not touching the file, stat will return still the same
list.changeFile '/some/b.js', ->
expect(onFileListModifiedSpy).not.to.have.been.called
done()
# WIN
# it 'should remove file if ENOENT stat', (done) ->
# # chokidar fires "change" instead of remove, on windows
# # MATCH: /a.txt
# list = new m.List patterns('/a.*'), [], emitter, preprocessMock
# list.refresh().then (files) ->
# list.getServedFiles()[0].originalPath = '/non-existing-file'
# waitForChangingFile done, '/non-existing-file', (files) ->
# expect(files.served).to.deep.equal []
# expect(onFileListModifiedSpy).to.have.been.called
it 'should preprocess changed file', (done) ->
# MATCH: /some/a.js, /some/b.js
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
preprocessMock.reset()
mockFs._touchFile '/some/a.js', '2020-01-01'
list.changeFile '/some/a.js', ->
expect(preprocessMock).to.have.been.called
expect(preprocessMock.lastCall.args[0]).to.have.property 'path', '/some/a.js'
done()
#============================================================================
# List.removeFile()
#============================================================================
describe 'removeFile', ->
waitForRemovingFile = (done, path, resume) ->
emitter.once 'file_list_modified', (promise) ->
expect(promise).to.be.fulfilled.then(resume).should.notify(done)
list.removeFile path
it 'should remove file from list and fire "file_list_modified"', ->
# MATCH: /some/a.js, /some/b.js, /a.txt
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
waitForRemovingFile '/some/a.js', (files) ->
expect(pathsFrom files.served).to.deep.equal ['/some/b.js', '/a.txt']
expect(onFileListModifiedSpy).to.have.been.called
it 'should not fire "file_list_modified" if file is not in the list', ->
# MATCH: /some/a.js, /some/b.js, /a.txt
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock
refreshListAndThen (files) ->
waitForRemovingFile '/a.js', ->
expect(onFileListModifiedSpy).not.to.have.been.called
#============================================================================
# Batch Interval processing
#============================================================================
describe 'batch interval', ->
it 'should batch multiple changes within an interval', (done) ->
timeoutSpy = sinon.stub().returns true
globals_ = setTimeout: timeoutSpy
m = mocks.loadFile __dirname + '/../../lib/file-list.js', mocks_, globals_
# MATCH: /some/a.js, /some/b.js, /a.txt
list = new m.List patterns('/some/*.js', '/a.*'), [], emitter, preprocessMock, 1000
refreshListAndThen (files) ->
timeoutSpy.reset()
emitter.once 'file_list_modified', (promise) ->
expect(promise).to.be.fulfilled.then (files) ->
expect(pathsFrom files.served).to.deep.equal ['/some/0.js', '/some/b.js', '/a.txt']
expect(onFileListModifiedSpy).to.have.been.calledOnce
# another batch, should fire new "file_list_modified" event
onFileListModifiedSpy.reset()
timeoutSpy.reset()
emitter.once 'file_list_modified', (promise) ->
expect(promise).to.be.fulfilled.then((files) ->
expect(pathsFrom files.served).to.deep.equal ['/some/b.js', '/a.txt']
expect(onFileListModifiedSpy).to.have.been.calledOnce
).should.notify(done)
list.removeFile '/some/0.js' # /some/b.js, /a.txt
process.nextTick -> process.nextTick ->
expect(timeoutSpy).to.have.been.called
timeoutSpy.lastCall.args[0]()
mockFs._touchFile '/some/b.js', '2020-01-01'
list.changeFile '/some/b.js'
list.removeFile '/some/a.js' # /some/b.js, /a.txt
list.removeFile '/a.txt' # /some/b.js
list.addFile '/a.txt' # /some/b.js, /a.txt
list.addFile '/some/0.js' # /some/0.js, /some/b.js, /a.txt
expect(onFileListModifiedSpy).to.have.been.called
process.nextTick -> process.nextTick ->
expect(timeoutSpy).to.have.been.called
timeoutSpy.lastCall.args[0]()
#============================================================================
# Win Globbing
#============================================================================
describe 'createWinGlob', ->
it 'should path separator is slash', ->
mockGlob = (pattern, opts, done) ->
expect(pattern).to.equal 'x:/Users/vojta/*.js'
# for Travis test
results = [
path.join('x:', 'Users', 'vojta', 'file.js'),
path.join('x:', 'Users', 'vojta', 'more.js')
]
done null, results
winGlob = m.createWinGlob mockGlob
winGlob 'x:/Users/vojta/*.js', {}, (err, results) ->
expect(results).to.deep.equal ['x:/Users/vojta/file.js', 'x:/Users/vojta/more.js']