turbowatch
Version:
Extremely fast file change detector and task orchestrator for Node.js.
209 lines (144 loc) • 4.79 kB
text/typescript
import { isFSWatcherAvailable } from '../isFSWatcherAvailable';
import { ChokidarWatcher } from './ChokidarWatcher';
import { type FileWatchingBackend } from './FileWatchingBackend';
import { FSWatcher } from './FSWatcher';
import { TurboWatcher } from './TurboWatcher';
import fs from 'node:fs/promises';
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
import * as sinon from 'sinon';
import { beforeEach, expect, it } from 'vitest';
const fixturesPath = path.resolve(__dirname, '.fixtures');
beforeEach(async () => {
await fs.rm(fixturesPath, {
force: true,
recursive: true,
});
await fs.mkdir(fixturesPath);
});
const waitForReady = (watcher: FileWatchingBackend) => {
return new Promise((resolve) => {
watcher.on('ready', () => {
resolve(null);
});
});
};
const backends = [
{
name: 'Chokidar',
Watcher: ChokidarWatcher,
},
{
name: 'FS',
Watcher: FSWatcher,
},
{
name: 'Turbo',
Watcher: TurboWatcher,
},
];
for (const { Watcher, name } of backends) {
if (isFSWatcherAvailable() === false && Watcher === FSWatcher) {
continue;
}
it('[' + name + '] detects file change', async () => {
const watcher = new Watcher(fixturesPath);
await waitForReady(watcher);
const onChange = sinon.stub();
watcher.on('change', onChange);
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'foo'), '');
await setTimeout(100);
expect(
onChange.calledWith({
filename: path.join(fixturesPath, 'foo'),
}),
).toBe(true);
await watcher.close();
});
it('[' + name + '] detects changes to a file that is replaced', async () => {
const watcher = new Watcher(fixturesPath);
await waitForReady(watcher);
const onChange = sinon.stub();
watcher.on('change', onChange);
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'foo'), '');
await setTimeout(100);
await fs.unlink(path.join(fixturesPath, 'foo'));
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'foo'), '');
await setTimeout(100);
expect(onChange.callCount).toBeGreaterThanOrEqual(3);
await watcher.close();
});
it('[' + name + '] detects hard link change (linked file)', async () => {
await fs.mkdir(path.resolve(fixturesPath, 'foo'));
await fs.writeFile(path.join(fixturesPath, 'bar'), '');
await fs.link(
path.join(fixturesPath, 'bar'),
path.join(fixturesPath, 'foo', 'bar'),
);
const watcher = new Watcher(path.resolve(fixturesPath, 'foo'));
await waitForReady(watcher);
const onChange = sinon.stub();
watcher.on('change', onChange);
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'bar'), '');
await setTimeout(100);
expect(
onChange.calledWith({
filename: path.join(fixturesPath, 'foo', 'bar'),
}),
).toBe(true);
await watcher.close();
});
it('[' + name + '] detects symlink change (linked file)', async () => {
await fs.mkdir(path.resolve(fixturesPath, 'foo'));
await fs.writeFile(path.join(fixturesPath, 'bar'), '');
await fs.symlink(
path.join(fixturesPath, 'bar'),
path.join(fixturesPath, 'foo', 'bar'),
);
const watcher = new Watcher(path.resolve(fixturesPath, 'foo'));
await waitForReady(watcher);
const onChange = sinon.stub();
watcher.on('change', onChange);
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'bar'), '');
await setTimeout(100);
expect(
onChange.calledWith({
filename: path.join(fixturesPath, 'foo', 'bar'),
}),
).toBe(true);
await watcher.close();
});
it('[' + name + '] detects symlink change (linked path)', async () => {
await fs.mkdir(path.resolve(fixturesPath, 'foo'));
await fs.mkdir(path.resolve(fixturesPath, 'bar'));
await fs.writeFile(path.join(fixturesPath, 'bar', 'baz'), '');
await fs.symlink(
path.join(fixturesPath, 'bar'),
path.join(fixturesPath, 'foo', 'bar'),
);
const watcher = new Watcher(path.resolve(fixturesPath, 'foo'));
await waitForReady(watcher);
const onChange = sinon.stub();
watcher.on('change', onChange);
await setTimeout(100);
await fs.writeFile(path.join(fixturesPath, 'bar', 'baz'), '');
await setTimeout(100);
expect(
onChange.calledWith({
filename: path.join(fixturesPath, 'foo', 'bar', 'baz'),
}),
).toBe(true);
// TODO investigate why this is failing in GitHub CI
// expect(
// onChange.calledWith({
// filename: path.join(fixturesPath, 'foo', 'bar'),
// }),
// ).toBe(true);
await watcher.close();
});
}