• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3
4if (common.isIBMi)
5  common.skip('IBMi does not support `fs.watch()`');
6
7const { watch } = require('fs/promises');
8const fs = require('fs');
9const assert = require('assert');
10const { join } = require('path');
11const tmpdir = require('../common/tmpdir');
12
13class WatchTestCase {
14  constructor(shouldInclude, dirName, fileName, field) {
15    this.dirName = dirName;
16    this.fileName = fileName;
17    this.field = field;
18    this.shouldSkip = !shouldInclude;
19  }
20  get dirPath() { return join(tmpdir.path, this.dirName); }
21  get filePath() { return join(this.dirPath, this.fileName); }
22}
23
24const kCases = [
25  // Watch on a directory should callback with a filename on supported systems
26  new WatchTestCase(
27    common.isLinux || common.isOSX || common.isWindows || common.isAIX,
28    'watch1',
29    'foo',
30    'filePath'
31  ),
32  // Watch on a file should callback with a filename on supported systems
33  new WatchTestCase(
34    common.isLinux || common.isOSX || common.isWindows,
35    'watch2',
36    'bar',
37    'dirPath'
38  ),
39];
40
41tmpdir.refresh();
42
43for (const testCase of kCases) {
44  if (testCase.shouldSkip) continue;
45  fs.mkdirSync(testCase.dirPath);
46  // Long content so it's actually flushed.
47  const content1 = Date.now() + testCase.fileName.toLowerCase().repeat(1e4);
48  fs.writeFileSync(testCase.filePath, content1);
49
50  let interval;
51  async function test() {
52    const watcher = watch(testCase[testCase.field]);
53    for await (const { eventType, filename } of watcher) {
54      clearInterval(interval);
55      assert.strictEqual(['rename', 'change'].includes(eventType), true);
56      assert.strictEqual(filename, testCase.fileName);
57      break;
58    }
59
60    // Waiting on it again is a non-op
61    // eslint-disable-next-line no-unused-vars
62    for await (const p of watcher) {
63      assert.fail('should not run');
64    }
65  }
66
67  // Long content so it's actually flushed. toUpperCase so there's real change.
68  const content2 = Date.now() + testCase.fileName.toUpperCase().repeat(1e4);
69  interval = setInterval(() => {
70    fs.writeFileSync(testCase.filePath, '');
71    fs.writeFileSync(testCase.filePath, content2);
72  }, 100);
73
74  test().then(common.mustCall());
75}
76
77assert.rejects(
78  async () => {
79    // eslint-disable-next-line no-unused-vars, no-empty
80    for await (const _ of watch(1)) { }
81  },
82  { code: 'ERR_INVALID_ARG_TYPE' });
83
84assert.rejects(
85  async () => {
86    // eslint-disable-next-line no-unused-vars, no-empty
87    for await (const _ of watch(__filename, 1)) { }
88  },
89  { code: 'ERR_INVALID_ARG_TYPE' });
90
91assert.rejects(
92  async () => {
93    // eslint-disable-next-line no-unused-vars, no-empty
94    for await (const _ of watch('', { persistent: 1 })) { }
95  },
96  { code: 'ERR_INVALID_ARG_TYPE' });
97
98assert.rejects(
99  async () => {
100    // eslint-disable-next-line no-unused-vars, no-empty
101    for await (const _ of watch('', { recursive: 1 })) { }
102  },
103  { code: 'ERR_INVALID_ARG_TYPE' });
104
105assert.rejects(
106  async () => {
107    // eslint-disable-next-line no-unused-vars, no-empty
108    for await (const _ of watch('', { encoding: 1 })) { }
109  },
110  { code: 'ERR_INVALID_ARG_VALUE' });
111
112assert.rejects(
113  async () => {
114    // eslint-disable-next-line no-unused-vars, no-empty
115    for await (const _ of watch('', { signal: 1 })) { }
116  },
117  { code: 'ERR_INVALID_ARG_TYPE' });
118
119(async () => {
120  const ac = new AbortController();
121  const { signal } = ac;
122  setImmediate(() => ac.abort());
123  try {
124    // eslint-disable-next-line no-unused-vars, no-empty
125    for await (const _ of watch(__filename, { signal })) { }
126  } catch (err) {
127    assert.strictEqual(err.name, 'AbortError');
128  }
129})().then(common.mustCall());
130