Files
smartwatch/test/test.basic.ts

160 lines
4.7 KiB
TypeScript
Raw Permalink Normal View History

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as smartwatch from '../ts/index.js';
import * as smartfile from '@push.rocks/smartfile';
import * as smartrx from '@push.rocks/smartrx';
import * as fs from 'fs';
import * as path from 'path';
// Skip in CI
if (process.env.CI) {
process.exit(0);
}
const TEST_DIR = './test/assets';
// Helper to delay
const delay = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
// Helper to wait for an event with timeout
async function waitForEvent<T>(
observable: smartrx.rxjs.Observable<T>,
timeoutMs: number = 5000
): Promise<T> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
subscription.unsubscribe();
reject(new Error(`Timeout waiting for event after ${timeoutMs}ms`));
}, timeoutMs);
const subscription = observable.subscribe((value) => {
clearTimeout(timeout);
subscription.unsubscribe();
resolve(value);
});
});
}
// Helper to wait for a specific file's event (filters by filename)
async function waitForFileEvent<T extends [string, ...any[]]>(
observable: smartrx.rxjs.Observable<T>,
expectedFile: string,
timeoutMs: number = 5000
): Promise<T> {
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
subscription.unsubscribe();
reject(new Error(`Timeout waiting for event on ${expectedFile} after ${timeoutMs}ms`));
}, timeoutMs);
const subscription = observable.subscribe((value) => {
const [filePath] = value;
if (filePath.includes(expectedFile)) {
clearTimeout(timeout);
subscription.unsubscribe();
resolve(value);
}
// Otherwise keep waiting for the right file
});
});
}
let testSmartwatch: smartwatch.Smartwatch;
// ===========================================
// BASIC TESTS
// ===========================================
tap.test('should create a new instance', async () => {
testSmartwatch = new smartwatch.Smartwatch([]);
expect(testSmartwatch).toBeInstanceOf(smartwatch.Smartwatch);
});
tap.test('should add paths and start watching', async () => {
testSmartwatch.add([`${TEST_DIR}/**/*.txt`]);
await testSmartwatch.start();
expect(testSmartwatch.status).toEqual('watching');
// Wait for chokidar to be ready
await delay(500);
});
tap.test('should detect ADD event for new files', async () => {
const addObservable = await testSmartwatch.getObservableFor('add');
// Subscribe FIRST, then create file
const eventPromise = waitForFileEvent(addObservable, 'add-test.txt');
// Create a new file
const testFile = path.join(TEST_DIR, 'add-test.txt');
await smartfile.memory.toFs('test content', testFile);
const [filePath] = await eventPromise;
expect(filePath).toInclude('add-test.txt');
// Cleanup
await fs.promises.unlink(testFile);
await delay(200);
});
tap.test('should detect CHANGE event for modified files', async () => {
// First create the file
const testFile = path.join(TEST_DIR, 'change-test.txt');
await smartfile.memory.toFs('initial content', testFile);
// Wait for add event to complete
await delay(300);
const changeObservable = await testSmartwatch.getObservableFor('change');
const eventPromise = waitForFileEvent(changeObservable, 'change-test.txt');
// Modify the file
await smartfile.memory.toFs('modified content', testFile);
const [filePath] = await eventPromise;
expect(filePath).toInclude('change-test.txt');
// Cleanup
await fs.promises.unlink(testFile);
await delay(200);
});
tap.test('should detect UNLINK event for deleted files', async () => {
// First create the file
const testFile = path.join(TEST_DIR, 'unlink-test.txt');
await smartfile.memory.toFs('to be deleted', testFile);
// Wait for add event to complete
await delay(300);
const unlinkObservable = await testSmartwatch.getObservableFor('unlink');
// Use file-specific wait to handle any pending unlinks from other tests
const eventPromise = waitForFileEvent(unlinkObservable, 'unlink-test.txt');
// Delete the file
await fs.promises.unlink(testFile);
const [filePath] = await eventPromise;
expect(filePath).toInclude('unlink-test.txt');
});
tap.test('should stop the watch process', async () => {
await testSmartwatch.stop();
expect(testSmartwatch.status).toEqual('idle');
});
tap.test('cleanup: remove any remaining test files', async () => {
const files = await fs.promises.readdir(TEST_DIR);
for (const file of files) {
if (file.startsWith('add-') || file.startsWith('change-') || file.startsWith('unlink-')) {
try {
await fs.promises.unlink(path.join(TEST_DIR, file));
} catch {
// Ignore
}
}
}
});
export default tap.start();