diff --git a/Source/Testably.Abstractions.Testing/FileSystem/ChangeDescription.cs b/Source/Testably.Abstractions.Testing/FileSystem/ChangeDescription.cs
index df654a10..675eb0e4 100644
--- a/Source/Testably.Abstractions.Testing/FileSystem/ChangeDescription.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystem/ChangeDescription.cs
@@ -58,6 +58,17 @@ internal ChangeDescription(WatcherChangeTypes changeType,
NotifyFilters = notifyFilters;
}
+ internal ChangeDescription(ChangeDescription source)
+ {
+ Path = source.Path;
+ Name = source.Name;
+ OldPath = source.OldPath;
+ OldName = source.OldName;
+ ChangeType = source.ChangeType;
+ FileSystemType = source.FileSystemType;
+ NotifyFilters = source.NotifyFilters;
+ }
+
///
public override string ToString()
=> $"{ChangeType} ({FileSystemType}) {Path} [{NotifyFilters}]";
diff --git a/Source/Testably.Abstractions.Testing/FileSystem/ChangeHandler.cs b/Source/Testably.Abstractions.Testing/FileSystem/ChangeHandler.cs
index cc737908..db120c4a 100644
--- a/Source/Testably.Abstractions.Testing/FileSystem/ChangeHandler.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystem/ChangeHandler.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.IO;
using Testably.Abstractions.Testing.Storage;
@@ -23,13 +23,24 @@ private readonly Notification.INotificationFactory
private readonly MockFileSystem _mockFileSystem;
- private readonly Notification.INotificationFactory
- _watcherNotificationTriggeredCallbacks = Notification.CreateFactory();
+ private readonly List? _watcherHistory;
+#if NET9_0_OR_GREATER
+ private readonly System.Threading.Lock _watcherHistoryLock = new();
+#else
+ private readonly object _watcherHistoryLock = new();
+#endif
+
+ private readonly Notification.INotificationFactory
+ _watcherNotificationTriggeredCallbacks =
+ Notification.CreateFactory();
public ChangeHandler(MockFileSystem mockFileSystem, bool recordNotificationHistory)
{
_mockFileSystem = mockFileSystem;
_history = recordNotificationHistory ? new List() : null;
+ _watcherHistory = recordNotificationHistory
+ ? new List()
+ : null;
}
#region IInterceptionHandler Members
@@ -97,11 +108,50 @@ public IAwaitableCallback OnEventOrReplay(
#region IWatcherTriggeredHandler Members
///
- public IAwaitableCallback OnTriggered(
- Action? triggerCallback = null,
- Func? predicate = null)
+ public IAwaitableCallback OnTriggered(
+ Action? triggerCallback = null,
+ Func? predicate = null)
=> _watcherNotificationTriggeredCallbacks.RegisterCallback(triggerCallback, predicate);
+ ///
+ public IAwaitableCallback OnTriggeredOrReplay(
+ Action? triggerCallback = null,
+ Func? predicate = null)
+ {
+ if (_watcherHistory is null)
+ {
+ throw new InvalidOperationException(
+ $"{nameof(OnTriggeredOrReplay)} requires notification history, but it was disabled via " +
+ $"{nameof(MockFileSystem.MockFileSystemOptions)}." +
+ $"{nameof(MockFileSystem.MockFileSystemOptions.WithoutNotificationHistory)}. " +
+ $"Use {nameof(OnTriggered)} instead, or remove the opt-out.");
+ }
+
+ IAwaitableCallback waiter;
+ WatcherChangeDescription[] snapshot;
+ lock (_watcherHistoryLock)
+ {
+ waiter =
+ _watcherNotificationTriggeredCallbacks.RegisterCallback(triggerCallback, predicate);
+ snapshot = _watcherHistory.ToArray();
+ }
+
+ try
+ {
+ foreach (WatcherChangeDescription past in snapshot)
+ {
+ _watcherNotificationTriggeredCallbacks.Replay(waiter, past);
+ }
+ }
+ catch
+ {
+ waiter.Dispose();
+ throw;
+ }
+
+ return waiter;
+ }
+
#endregion
internal void NotifyCompletedChange(ChangeDescription? fileSystemChange)
@@ -140,6 +190,24 @@ internal ChangeDescription NotifyPendingChange(WatcherChangeTypes changeType,
return fileSystemChange;
}
- internal void NotifyWatcherTriggeredChange(ChangeDescription fileSystemChange)
- => _watcherNotificationTriggeredCallbacks.InvokeCallbacks(fileSystemChange);
+ internal void NotifyWatcherTriggeredChange(ChangeDescription fileSystemChange,
+ IFileSystemWatcher watcher)
+ {
+ WatcherChangeDescription watcherChange = new(fileSystemChange, watcher);
+
+ if (_watcherHistory is null)
+ {
+ _watcherNotificationTriggeredCallbacks.InvokeCallbacks(watcherChange);
+ return;
+ }
+
+ Action invoke;
+ lock (_watcherHistoryLock)
+ {
+ _watcherHistory.Add(watcherChange);
+ invoke = _watcherNotificationTriggeredCallbacks.SnapshotInvocations();
+ }
+
+ invoke(watcherChange);
+ }
}
diff --git a/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs b/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
index 33068dec..1775afd2 100644
--- a/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
@@ -528,7 +528,7 @@ private void NotifyChange(ChangeDescription item)
TriggerRenameNotification(item);
}
- _fileSystem.ChangeHandler.NotifyWatcherTriggeredChange(item);
+ _fileSystem.ChangeHandler.NotifyWatcherTriggeredChange(item, this);
}
}
diff --git a/Source/Testably.Abstractions.Testing/FileSystem/IWatcherTriggeredHandler.cs b/Source/Testably.Abstractions.Testing/FileSystem/IWatcherTriggeredHandler.cs
index a155f99b..ba66b815 100644
--- a/Source/Testably.Abstractions.Testing/FileSystem/IWatcherTriggeredHandler.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystem/IWatcherTriggeredHandler.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
namespace Testably.Abstractions.Testing.FileSystem;
@@ -16,8 +16,31 @@ public interface IWatcherTriggeredHandler : IFileSystemEntity
/// (optional) A predicate used to filter which callbacks should be notified.
/// If set to (default value) all callbacks are notified.
///
- /// An to un-register the callback on dispose.
- IAwaitableCallback OnTriggered(
- Action? triggerCallback = null,
- Func? predicate = null);
+ /// An to un-register the callback on dispose.
+ IAwaitableCallback OnTriggered(
+ Action? triggerCallback = null,
+ Func? predicate = null);
+
+ ///
+ /// Like , but the returned callback also replays any matching
+ /// watcher-triggered notifications that occurred before this call, in their original order.
+ ///
+ /// Notification history is enabled by default. If it has been disabled via
+ /// , calling
+ /// this method throws ; use
+ /// instead in that case.
+ ///
+ /// (optional) The callback to execute for each replayed and future notification.
+ ///
+ /// (optional) A predicate used to filter which notifications are replayed and which future notifications notify the callback.
+ /// If set to (default value) all notifications are considered.
+ ///
+ /// An to un-register the callback on dispose.
+ ///
+ /// Notification history was disabled via
+ /// .
+ ///
+ IAwaitableCallback OnTriggeredOrReplay(
+ Action? triggerCallback = null,
+ Func? predicate = null);
}
diff --git a/Source/Testably.Abstractions.Testing/FileSystem/WatcherChangeDescription.cs b/Source/Testably.Abstractions.Testing/FileSystem/WatcherChangeDescription.cs
new file mode 100644
index 00000000..3f216a9d
--- /dev/null
+++ b/Source/Testably.Abstractions.Testing/FileSystem/WatcherChangeDescription.cs
@@ -0,0 +1,20 @@
+namespace Testably.Abstractions.Testing.FileSystem;
+
+///
+/// Describes a change in the that was emitted by a specific
+/// . The property allows
+/// consumers to filter watcher-triggered notifications by the emitting watcher instance.
+///
+public sealed class WatcherChangeDescription : ChangeDescription
+{
+ ///
+ /// The that emitted this change notification.
+ ///
+ public IFileSystemWatcher FileSystemWatcher { get; }
+
+ internal WatcherChangeDescription(ChangeDescription source, IFileSystemWatcher fileSystemWatcher)
+ : base(source)
+ {
+ FileSystemWatcher = fileSystemWatcher;
+ }
+}
diff --git a/Source/Testably.Abstractions.Testing/MockFileSystem.cs b/Source/Testably.Abstractions.Testing/MockFileSystem.cs
index 0e4a91e6..36bb8dee 100644
--- a/Source/Testably.Abstractions.Testing/MockFileSystem.cs
+++ b/Source/Testably.Abstractions.Testing/MockFileSystem.cs
@@ -316,7 +316,8 @@ public class MockFileSystemOptions
///
/// Whether to record a history of completed change notifications so that
- /// can replay past events.
+ /// and
+ /// can replay past events.
///
internal bool RecordNotificationHistory { get; private set; } = true;
@@ -378,10 +379,12 @@ public MockFileSystemOptions UseTimeSystem(ITimeSystem timeSystem)
}
///
- /// Disables recording the change notification history. With history disabled,
- /// throws
+ /// Disables recording the change notification history for both the notify and
+ /// watcher-triggered streams. With history disabled,
+ /// and
+ /// throw
/// ; use
- /// instead.
+ /// or instead.
///
public MockFileSystemOptions WithoutNotificationHistory()
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt
index eddfa063..e2af3e6b 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net10.0.txt
@@ -231,7 +231,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -257,6 +258,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
index 07ce3eb9..fa6ea6d7 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net6.0.txt
@@ -224,7 +224,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -244,6 +245,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
index d68c6252..fc9b2984 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net8.0.txt
@@ -231,7 +231,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -257,6 +258,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt
index 147f89e5..c724e81b 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_net9.0.txt
@@ -231,7 +231,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -257,6 +258,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
index 16b86b69..21b4bd4b 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.0.txt
@@ -218,7 +218,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -237,6 +238,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
index 46c01a2f..e1953db2 100644
--- a/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
+++ b/Tests/Api/Testably.Abstractions.Api.Tests/Expected/Testably.Abstractions.Testing_netstandard2.1.txt
@@ -218,7 +218,8 @@ namespace Testably.Abstractions.Testing.FileSystem
}
public interface IWatcherTriggeredHandler : System.IO.Abstractions.IFileSystemEntity
{
- Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggered(System.Action? triggerCallback = null, System.Func? predicate = null);
+ Testably.Abstractions.Testing.IAwaitableCallback OnTriggeredOrReplay(System.Action? triggerCallback = null, System.Func? predicate = null);
}
public class NullAccessControlStrategy : Testably.Abstractions.Testing.FileSystem.IAccessControlStrategy
{
@@ -237,6 +238,10 @@ namespace Testably.Abstractions.Testing.FileSystem
public string Path { get; }
public System.IO.FileShare Share { get; }
}
+ public sealed class WatcherChangeDescription : Testably.Abstractions.Testing.FileSystem.ChangeDescription
+ {
+ public System.IO.Abstractions.IFileSystemWatcher FileSystemWatcher { get; }
+ }
}
namespace Testably.Abstractions.Testing.Initializer
{
diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystem/ChangeHandlerTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/ChangeHandlerTests.cs
index 5d33757a..4c38168d 100644
--- a/Tests/Testably.Abstractions.Testing.Tests/FileSystem/ChangeHandlerTests.cs
+++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/ChangeHandlerTests.cs
@@ -94,7 +94,8 @@ public async Task ExecuteCallback_ShouldTriggerNotification(
}
public static
- IEnumerable<(Action?, Action, WatcherChangeTypes, FileSystemTypes, string)> NotificationTriggeringMethods()
+ IEnumerable<(Action?, Action, WatcherChangeTypes,
+ FileSystemTypes, string)> NotificationTriggeringMethods()
{
yield return (null, (f, p) => f.Directory.CreateDirectory(p), WatcherChangeTypes.Created,
FileSystemTypes.Directory, $"path_{Guid.NewGuid()}");
@@ -106,6 +107,21 @@ public static
WatcherChangeTypes.Deleted, FileSystemTypes.File, $"path_{Guid.NewGuid()}");
}
+ [Test]
+ [AutoArguments]
+ public async Task OnEventOrReplay_CallbackThrowsDuringReplay_ShouldNotLeakWaiter(string path)
+ {
+ FileSystem.File.WriteAllText(path, null);
+
+ void Subscribe()
+ => FileSystem.Notify.OnEventOrReplay(_ => throw new InvalidOperationException("boom"));
+
+ await That(Subscribe).Throws().WithMessage("boom");
+
+ void TriggerAnotherChange() => FileSystem.File.WriteAllText(path, "more");
+ await That(TriggerAnotherChange).DoesNotThrow();
+ }
+
[Test]
[AutoArguments]
public async Task OnEventOrReplay_ShouldAlsoReceiveFutureEvents(string path1, string path2)
@@ -168,18 +184,107 @@ await That(Act).Throws()
}
[Test]
- [AutoArguments]
- public async Task OnEventOrReplay_CallbackThrowsDuringReplay_ShouldNotLeakWaiter(string path)
+ public async Task OnTriggeredOrReplay_ShouldAlsoReceiveFutureEmissions()
{
- FileSystem.File.WriteAllText(path, null);
+ FileSystem.InitializeIn(".");
+ IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New(".");
+ watcher.EnableRaisingEvents = true;
- void Subscribe() => FileSystem.Notify.OnEventOrReplay(
- _ => throw new InvalidOperationException("boom"));
+ using IAwaitableCallback warmup =
+ FileSystem.Watcher.OnTriggered();
+ FileSystem.File.WriteAllText("foo.txt", "some-text");
+ warmup.Wait(timeout: 30000);
- await That(Subscribe).Throws().WithMessage("boom");
+ using IAwaitableCallback onEvent = FileSystem.Watcher
+ .OnTriggeredOrReplay(predicate: c => c.ChangeType == WatcherChangeTypes.Created);
- void TriggerAnotherChange() => FileSystem.File.WriteAllText(path, "more");
- await That(TriggerAnotherChange).DoesNotThrow();
+ FileSystem.File.WriteAllText("bar.txt", "more-text");
+
+ WatcherChangeDescription[] received = await onEvent.WaitAsync(
+ count: 2,
+ timeout: TimeSpan.FromSeconds(5));
+
+ await That(received.Length).IsEqualTo(2);
+ }
+
+ [Test]
+ public async Task OnTriggeredOrReplay_ShouldFilterByEmittingWatcher()
+ {
+ FileSystem.InitializeIn(".");
+ IFileSystemWatcher watcher1 = FileSystem.FileSystemWatcher.New(".");
+ IFileSystemWatcher watcher2 = FileSystem.FileSystemWatcher.New(".");
+ watcher1.EnableRaisingEvents = true;
+ watcher2.EnableRaisingEvents = true;
+
+ using IAwaitableCallback warmup =
+ FileSystem.Watcher.OnTriggered();
+ FileSystem.File.WriteAllText("foo.txt", "some-text");
+ warmup.Wait(count: 2, timeout: TimeSpan.FromSeconds(30));
+
+ using IAwaitableCallback onEvent = FileSystem.Watcher
+ .OnTriggeredOrReplay(predicate: c =>
+ ReferenceEquals(c.FileSystemWatcher, watcher1));
+
+ WatcherChangeDescription[] replayed =
+ await onEvent.WaitAsync(timeout: TimeSpan.FromSeconds(5));
+
+ await That(replayed.Length).IsEqualTo(1);
+ await That(replayed[0].FileSystemWatcher).IsSameAs(watcher1);
+ }
+
+ [Test]
+ public async Task OnTriggeredOrReplay_ShouldNotReplayEmissionsFilteredOutByPredicate()
+ {
+ FileSystem.InitializeIn(".");
+ IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New(".");
+ watcher.EnableRaisingEvents = true;
+
+ using IAwaitableCallback warmup =
+ FileSystem.Watcher.OnTriggered();
+ FileSystem.File.WriteAllText("foo.txt", "some-text");
+ warmup.Wait(timeout: 30000);
+
+ using IAwaitableCallback onEvent = FileSystem.Watcher
+ .OnTriggeredOrReplay(predicate: c => c.ChangeType == WatcherChangeTypes.Deleted);
+
+ void Act() =>
+ // ReSharper disable once AccessToDisposedClosure
+ onEvent.Wait(timeout: 50);
+
+ await That(Act).Throws();
+ }
+
+ [Test]
+ public async Task OnTriggeredOrReplay_ShouldReplayPriorMatchingEmissions()
+ {
+ FileSystem.InitializeIn(".");
+ IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New(".");
+ watcher.EnableRaisingEvents = true;
+
+ using IAwaitableCallback warmup =
+ FileSystem.Watcher.OnTriggered();
+ FileSystem.File.WriteAllText("foo.txt", "some-text");
+ warmup.Wait(timeout: 30000);
+
+ using IAwaitableCallback onEvent = FileSystem.Watcher
+ .OnTriggeredOrReplay(predicate: c => c.ChangeType == WatcherChangeTypes.Created);
+
+ WatcherChangeDescription[] replayed =
+ await onEvent.WaitAsync(timeout: TimeSpan.FromSeconds(5));
+
+ await That(replayed.Length).IsEqualTo(1);
+ await That(replayed[0].Path).IsEqualTo(FileSystem.Path.GetFullPath("foo.txt"));
+ }
+
+ [Test]
+ public async Task OnTriggeredOrReplay_WithoutNotificationHistory_ShouldThrow()
+ {
+ MockFileSystem fileSystem = new(o => o.WithoutNotificationHistory());
+
+ void Act() => fileSystem.Watcher.OnTriggeredOrReplay();
+
+ await That(Act).Throws()
+ .WithMessage("*WithoutNotificationHistory*").AsWildcard();
}
[Test]
@@ -189,7 +294,8 @@ public async Task Watcher_ShouldNotTriggerWhenFileSystemWatcherDoesNotMatch()
IFileSystemWatcher watcher = FileSystem.FileSystemWatcher.New("bar");
watcher.EnableRaisingEvents = true;
- using IAwaitableCallback onEvent = FileSystem.Watcher.OnTriggered();
+ using IAwaitableCallback onEvent =
+ FileSystem.Watcher.OnTriggered();
FileSystem.File.WriteAllText(@"foo.txt", "some-text");
@@ -209,7 +315,8 @@ public async Task Watcher_ShouldTriggerWhenFileSystemWatcherSendsNotification()
watcher.Created += (_, _) => isTriggered = true;
watcher.EnableRaisingEvents = true;
- using IAwaitableCallback onEvent = FileSystem.Watcher.OnTriggered();
+ using IAwaitableCallback onEvent =
+ FileSystem.Watcher.OnTriggered();
FileSystem.File.WriteAllText(@"foo.txt", "some-text");
onEvent.Wait(timeout: 30000);