Skip to content

Commit

Permalink
Added watch tests
Browse files Browse the repository at this point in the history
Fixed rename event filename
Fixed watch rename event filename
Resolved #6
  • Loading branch information
james-pre committed Sep 17, 2024
1 parent 60094a6 commit 700401c
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 13 deletions.
10 changes: 3 additions & 7 deletions src/emulation/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,13 +755,9 @@ export function unwatchFile(path: fs.PathLike, listener: (curr: Stats, prev: Sta
}
unwatchFile satisfies Omit<typeof fs.unwatchFile, '__promisify__'>;

export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): fs.FSWatcher;
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): fs.FSWatcher;
export function watch(
path: fs.PathLike,
options?: fs.WatchOptions | ((event: string, filename: string) => any),
listener?: (event: string, filename: string) => any
): fs.FSWatcher {
export function watch(path: fs.PathLike, listener?: (event: string, filename: string) => any): FSWatcher;
export function watch(path: fs.PathLike, options: { persistent?: boolean }, listener?: (event: string, filename: string) => any): FSWatcher;
export function watch(path: fs.PathLike, options?: fs.WatchOptions | ((event: string, filename: string) => any), listener?: (event: string, filename: string) => any): FSWatcher {
const watcher = new FSWatcher<string>(typeof options == 'object' ? options : {}, normalizePath(path));
listener = typeof options == 'function' ? options : listener;
watcher.on('change', listener || nop);
Expand Down
3 changes: 1 addition & 2 deletions src/emulation/promises.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,12 +395,11 @@ export async function rename(oldPath: fs.PathLike, newPath: fs.PathLike): Promis
try {
if (src.mountPoint == dst.mountPoint) {
await src.fs.rename(src.path, dst.path, cred);
emitChange('rename', newPath.toString());
emitChange('rename', oldPath.toString());
return;
}
await writeFile(newPath, await readFile(oldPath));
await unlink(oldPath);
emitChange('rename', newPath.toString());
emitChange('rename', oldPath.toString());
} catch (e) {
throw fixError(e as Error, { [src.path]: oldPath, [dst.path]: newPath });
Expand Down
5 changes: 3 additions & 2 deletions src/emulation/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ export function renameSync(oldPath: fs.PathLike, newPath: fs.PathLike): void {
const paths = { [_old.path]: oldPath, [_new.path]: newPath };
try {
if (_old === _new) {
return _old.fs.renameSync(_old.path, _new.path, cred);
_old.fs.renameSync(_old.path, _new.path, cred);
emitChange('rename', oldPath.toString());
return;
}

writeFileSync(newPath, readFileSync(oldPath));
unlinkSync(oldPath);
emitChange('rename', newPath.toString());
emitChange('rename', oldPath.toString());
} catch (e) {
throw fixError(e as Error, paths);
Expand Down
10 changes: 8 additions & 2 deletions src/emulation/watchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isStatsEqual, type Stats } from '../stats.js';
import { normalizePath } from '../utils.js';
import { dirname } from './path.js';
import { statSync } from './sync.js';
import { basename } from 'node:path';

/**
* Base class for file system watchers.
Expand Down Expand Up @@ -73,10 +74,15 @@ export class FSWatcher<T extends string | Buffer = string | Buffer>
super();
addWatcher(path.toString(), this);
}

public close(): void {
super.emit('close');
removeWatcher(this.path.toString(), this);
}

public [Symbol.dispose](): void {
this.close();
}
}

/**
Expand Down Expand Up @@ -168,7 +174,7 @@ export function emitChange(eventType: fs.WatchEventType, filename: string) {
// Notify watchers on the specific file
if (watchers.has(normalizedFilename)) {
for (const watcher of watchers.get(normalizedFilename)!) {
watcher.emit('change', eventType, filename);
watcher.emit('change', eventType, basename(filename));
}
}

Expand All @@ -177,7 +183,7 @@ export function emitChange(eventType: fs.WatchEventType, filename: string) {
while (parent !== normalizedFilename && parent !== '/') {
if (watchers.has(parent)) {
for (const watcher of watchers.get(parent)!) {
watcher.emit('change', eventType, filename);
watcher.emit('change', eventType, basename(filename));
}
}
normalizedFilename = parent;
Expand Down
116 changes: 116 additions & 0 deletions tests/fs/watch.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { fs } from '../common.js';

const testDir = '/test-watch-dir';
const testFile = `${testDir}/test.txt`;

describe('Watch Features', () => {
beforeAll(async () => {
// Set up test directory and file
try {
await fs.promises.mkdir(testDir);
} catch (err) {
// Directory might already exist
}
await fs.promises.writeFile(testFile, 'Initial content');
});

afterAll(async () => {
// Clean up test directory and file
try {
await fs.promises.unlink(testFile);
} catch (err) {
// File might already be deleted
}
try {
await fs.promises.rmdir(testDir);
} catch (err) {
// Directory might already be deleted
}
});

test('fs.watch should emit events on file change', async () => {
using watcher = fs.watch(testFile, (eventType, filename) => {
expect(eventType).toBe('change');
expect(filename).toBe('test.txt');
});

// Modify the file to trigger the event
await fs.promises.writeFile(testFile, 'Updated content');
});

test('fs.watch should emit events on file rename (delete)', async () => {
using watcher = fs.watch(testFile, (eventType, filename) => {
expect(eventType).toBe('rename');
expect(filename).toBe('test.txt');
});

// Delete the file to trigger the event
await fs.promises.unlink(testFile);
});

test('fs.watchFile should detect changes to a file', async () => {
const listener = (curr: fs.Stats, prev: fs.Stats) => {
expect(curr.mtimeMs).not.toBe(prev.mtimeMs);
fs.unwatchFile(testFile, listener);
};

fs.watchFile(testFile, listener);

// Modify the file to trigger the event
await fs.promises.writeFile(testFile, 'Changed content');
});

test('fs.unwatchFile should stop watching the file', async () => {
let changeDetected = false;

const listener = (curr: fs.Stats, prev: fs.Stats) => {
changeDetected = true;
};

fs.watchFile(testFile, listener);
fs.unwatchFile(testFile, listener);

// Modify the file to see if the listener is called
await fs.promises.writeFile(testFile, 'Another change');

// Wait to see if any change is detected
expect(changeDetected).toBe(false);
});

test('fs.watch should work with directories', async () => {
using watcher = fs.watch(testDir, (eventType, filename) => {
expect(eventType).toBe('change');
expect(filename).toBe('newFile.txt');
});

await fs.promises.writeFile(`${testDir}/newFile.txt`, 'Content');
});

test('fs.watch should detect file renames', async () => {
const oldFile = `${testDir}/oldFile.txt`;
const newFile = `${testDir}/newFile.txt`;

await fs.promises.writeFile(oldFile, 'Some content');

using watcher = fs.watch(testDir, (eventType, filename) => {
expect(eventType).toBe('rename');
expect(filename).toBe('oldFile.txt');
});

// Rename the file to trigger the event
await fs.promises.rename(oldFile, newFile);
});

test('fs.watch should detect file deletions', async () => {
const tempFile = `${testDir}/tempFile.txt`;

await fs.promises.writeFile(tempFile, 'Temporary content');

using watcher = fs.watch(tempFile, (eventType, filename) => {
expect(eventType).toBe('rename');
expect(filename).toBe('tempFile.txt');
});

await fs.promises.unlink(tempFile);
});
});

0 comments on commit 700401c

Please sign in to comment.