diff --git a/lib/folder-monitor.js b/lib/folder-monitor.js index 45843d7..c27da7e 100644 --- a/lib/folder-monitor.js +++ b/lib/folder-monitor.js @@ -188,6 +188,9 @@ class FolderMonitor extends EventEmitter { _invalidateLifecycle(paused) { this._generation++; + if (paused) { + for (const filePath of this._batchBuffer) this._seenFiles.delete(this._normalizePath(filePath)); + } this._paused = paused; this._scanning = false; this._followUpRequested = false; diff --git a/tests/folder-monitor.test.js b/tests/folder-monitor.test.js index 47d3a51..9d77f7f 100644 --- a/tests/folder-monitor.test.js +++ b/tests/folder-monitor.test.js @@ -345,6 +345,35 @@ test('resume preserves session duplicate history', async () => { assert.equal(monitor.status().seenCount, 1); }); +test('watcher add paused before batch timeout is emitted exactly once by resume scan', async () => { + const timers = createManualTimers(); + const watchers = []; + const newFiles = []; + const filePath = 'C:\\watch\\pending.mkv'; + const monitor = new FolderMonitor({ + watch: () => { + const watcher = new EventEmitter(); + watcher.close = async () => {}; + watchers.push(watcher); + return watcher; + }, + access: async () => {}, + walkFolder: async () => [{ path: filePath, name: 'pending.mkv', size: 1 }], + stat: async () => ({ mtimeMs: 1 }), + ...timers + }); + monitor.on('new-files', (files) => newFiles.push(files)); + const settings = { folderPath: 'C:\\watch', extensions: 'mkv', skipDuplicates: true, reconcileIntervalMinutes: 5 }; + monitor.start(settings); + watchers[0].emit('add', filePath); + assert.deepEqual(newFiles, []); + await monitor.pause(); + assert.deepEqual(newFiles, []); + await monitor.resume(settings); + assert.deepEqual(newFiles, [[filePath]]); + assert.equal(monitor.status().seenCount, 1); +}); + test('dry scan leaves the public status byte-identical and does not consume reconnect state', async () => { let reachable = false; const timers = createManualTimers();