• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Flags: --expose-internals
2import * as common from '../common/index.mjs';
3import * as fixtures from '../common/fixtures.mjs';
4import tmpdir from '../common/tmpdir.js';
5import path from 'node:path';
6import assert from 'node:assert';
7import process from 'node:process';
8import { describe, it, beforeEach, afterEach } from 'node:test';
9import { writeFileSync, mkdirSync } from 'node:fs';
10import { setTimeout } from 'node:timers/promises';
11import { once } from 'node:events';
12import { spawn } from 'node:child_process';
13import watcher from 'internal/watch_mode/files_watcher';
14
15if (common.isIBMi)
16  common.skip('IBMi does not support `fs.watch()`');
17
18const supportsRecursiveWatching = common.isOSX || common.isWindows;
19
20const { FilesWatcher } = watcher;
21tmpdir.refresh();
22
23describe('watch mode file watcher', () => {
24  let watcher;
25  let changesCount;
26
27  beforeEach(() => {
28    changesCount = 0;
29    watcher = new FilesWatcher({ throttle: 100 });
30    watcher.on('changed', () => changesCount++);
31  });
32
33  afterEach(() => watcher.clear());
34
35  let counter = 0;
36  function writeAndWaitForChanges(watcher, file) {
37    return new Promise((resolve) => {
38      const interval = setInterval(() => writeFileSync(file, `write ${counter++}`), 100);
39      watcher.once('changed', () => {
40        clearInterval(interval);
41        resolve();
42      });
43    });
44  }
45
46  it('should watch changed files', async () => {
47    const file = path.join(tmpdir.path, 'file1');
48    writeFileSync(file, 'written');
49    watcher.filterFile(file);
50    await writeAndWaitForChanges(watcher, file);
51    assert.strictEqual(changesCount, 1);
52  });
53
54  it('should throttle changes', async () => {
55    const file = path.join(tmpdir.path, 'file2');
56    writeFileSync(file, 'written');
57    watcher.filterFile(file);
58    await writeAndWaitForChanges(watcher, file);
59
60    writeFileSync(file, '1');
61    writeFileSync(file, '2');
62    writeFileSync(file, '3');
63    writeFileSync(file, '4');
64    await setTimeout(200); // throttle * 2
65    writeFileSync(file, '5');
66    const changed = once(watcher, 'changed');
67    writeFileSync(file, 'after');
68    await changed;
69    // Unfortunately testing that changesCount === 2 is flaky
70    assert.ok(changesCount < 5);
71  });
72
73  it('should ignore files in watched directory if they are not filtered',
74     { skip: !supportsRecursiveWatching }, async () => {
75       watcher.on('changed', common.mustNotCall());
76       watcher.watchPath(tmpdir.path);
77       writeFileSync(path.join(tmpdir.path, 'file3'), '1');
78       // Wait for this long to make sure changes are not triggered
79       await setTimeout(1000);
80     });
81
82  it('should allow clearing filters', async () => {
83    const file = path.join(tmpdir.path, 'file4');
84    writeFileSync(file, 'written');
85    watcher.filterFile(file);
86    await writeAndWaitForChanges(watcher, file);
87
88    writeFileSync(file, '1');
89
90    await setTimeout(200); // avoid throttling
91    watcher.clearFileFilters();
92    writeFileSync(file, '2');
93    // Wait for this long to make sure changes are triggered only once
94    await setTimeout(1000);
95    assert.strictEqual(changesCount, 1);
96  });
97
98  it('should watch all files in watched path when in "all" mode',
99     { skip: !supportsRecursiveWatching }, async () => {
100       watcher = new FilesWatcher({ throttle: 100, mode: 'all' });
101       watcher.on('changed', () => changesCount++);
102
103       const file = path.join(tmpdir.path, 'file5');
104       watcher.watchPath(tmpdir.path);
105
106       const changed = once(watcher, 'changed');
107       await setTimeout(common.platformTimeout(100)); // avoid throttling
108       writeFileSync(file, 'changed');
109       await changed;
110       assert.strictEqual(changesCount, 1);
111     });
112
113  it('should ruse existing watcher if it exists',
114     { skip: !supportsRecursiveWatching }, () => {
115       assert.deepStrictEqual(watcher.watchedPaths, []);
116       watcher.watchPath(tmpdir.path);
117       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
118       watcher.watchPath(tmpdir.path);
119       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
120     });
121
122  it('should ruse existing watcher of a parent directory',
123     { skip: !supportsRecursiveWatching }, () => {
124       assert.deepStrictEqual(watcher.watchedPaths, []);
125       watcher.watchPath(tmpdir.path);
126       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
127       watcher.watchPath(path.join(tmpdir.path, 'subdirectory'));
128       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
129     });
130
131  it('should remove existing watcher if adding a parent directory watcher',
132     { skip: !supportsRecursiveWatching }, () => {
133       assert.deepStrictEqual(watcher.watchedPaths, []);
134       const subdirectory = path.join(tmpdir.path, 'subdirectory');
135       mkdirSync(subdirectory);
136       watcher.watchPath(subdirectory);
137       assert.deepStrictEqual(watcher.watchedPaths, [subdirectory]);
138       watcher.watchPath(tmpdir.path);
139       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
140     });
141
142  it('should clear all watchers when calling clear',
143     { skip: !supportsRecursiveWatching }, () => {
144       assert.deepStrictEqual(watcher.watchedPaths, []);
145       watcher.watchPath(tmpdir.path);
146       assert.deepStrictEqual(watcher.watchedPaths, [tmpdir.path]);
147       watcher.clear();
148       assert.deepStrictEqual(watcher.watchedPaths, []);
149     });
150
151  it('should watch files from subprocess IPC events', async () => {
152    const file = fixtures.path('watch-mode/ipc.js');
153    const child = spawn(process.execPath, [file], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'], encoding: 'utf8' });
154    watcher.watchChildProcessModules(child);
155    await once(child, 'exit');
156    let expected = [file, path.join(tmpdir.path, 'file')];
157    if (supportsRecursiveWatching) {
158      expected = expected.map((file) => path.dirname(file));
159    }
160    assert.deepStrictEqual(watcher.watchedPaths, expected);
161  });
162});
163