diff --git a/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs b/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
index fd43e17b..e1bff42a 100644
--- a/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
+++ b/Source/Testably.Abstractions.Testing/FileSystem/FileSystemWatcherMock.cs
@@ -38,6 +38,7 @@ internal sealed class FileSystemWatcherMock : Component, IFileSystemWatcher
NotifyFilters.LastWrite;
private string _path = string.Empty;
+ private string _fullPath = string.Empty;
private ISynchronizeInvoke? _synchronizingObject;
@@ -213,6 +214,7 @@ public string Path
}
_path = value;
+ FullPath = _path;
}
}
@@ -258,6 +260,32 @@ public ISynchronizeInvoke? SynchronizingObject
}
}
+ ///
+ /// Caches the full path of
+ ///
+ private string FullPath
+ {
+ get => _fullPath;
+ set
+ {
+ if (string.IsNullOrEmpty(value))
+ {
+ _fullPath = value;
+
+ return;
+ }
+
+ string fullPath = _fileSystem.Path.GetFullPath(value);
+
+ if (!fullPath.EndsWith(_fileSystem.Path.DirectorySeparatorChar))
+ {
+ fullPath += _fileSystem.Path.DirectorySeparatorChar;
+ }
+
+ _fullPath = fullPath;
+ }
+ }
+
///
public void BeginInit()
{
@@ -399,19 +427,19 @@ private void NotifyChange(ChangeDescription item)
if (item.ChangeType.HasFlag(WatcherChangeTypes.Created))
{
Created?.Invoke(this, ToFileSystemEventArgs(
- item.ChangeType, item.Path, item.Name));
+ item.ChangeType, item.Path));
}
if (item.ChangeType.HasFlag(WatcherChangeTypes.Deleted))
{
Deleted?.Invoke(this, ToFileSystemEventArgs(
- item.ChangeType, item.Path, item.Name));
+ item.ChangeType, item.Path));
}
if (item.ChangeType.HasFlag(WatcherChangeTypes.Changed))
{
Changed?.Invoke(this, ToFileSystemEventArgs(
- item.ChangeType, item.Path, item.Name));
+ item.ChangeType, item.Path));
}
if (item.ChangeType.HasFlag(WatcherChangeTypes.Renamed))
@@ -502,68 +530,6 @@ private void Stop()
_changeHandler?.Dispose();
}
- private FileSystemEventArgs ToFileSystemEventArgs(
- WatcherChangeTypes changeType,
- string changePath,
- string? changeName)
- {
- string path = TransformPathAndName(
- changePath,
- changeName,
- out string name);
-
- FileSystemEventArgs eventArgs = new(changeType, Path, name);
- if (_fileSystem.SimulationMode != SimulationMode.Native)
- {
- // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
- // HACK: Have to resort to Reflection to override this behavior!
-#if NETFRAMEWORK
- typeof(FileSystemEventArgs)
- .GetField("fullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, path);
-#else
- typeof(FileSystemEventArgs)
- .GetField("_fullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, path);
-#endif
- }
-
- return eventArgs;
- }
-
- private string TransformPathAndName(
- string changeDescriptionPath,
- string? changeDescriptionName,
- out string name)
- {
- string? transformedName = changeDescriptionName;
- string? path = changeDescriptionPath;
- if (!_fileSystem.Path.IsPathRooted(Path))
- {
- string rootedWatchedPath = _fileSystem.Directory.GetCurrentDirectory();
- if (!rootedWatchedPath.EndsWith(_fileSystem.Path.DirectorySeparatorChar))
- {
- rootedWatchedPath += _fileSystem.Path.DirectorySeparatorChar;
- }
-
- if (path.StartsWith(rootedWatchedPath, _fileSystem.Execute.StringComparisonMode))
- {
- path = path.Substring(rootedWatchedPath.Length);
- }
-
- transformedName = _fileSystem.Execute.Path.GetFileName(changeDescriptionPath);
- }
- else if (transformedName == null ||
- _fileSystem.Execute.Path.IsPathRooted(changeDescriptionName))
- {
- transformedName = _fileSystem.Execute.Path.GetFileName(changeDescriptionPath);
- }
-
- name = transformedName;
-
- return path ?? "";
- }
-
private void TriggerRenameNotification(ChangeDescription item)
{
if (_fileSystem.Execute.IsWindows)
@@ -578,13 +544,13 @@ private void TriggerRenameNotification(ChangeDescription item)
if (MatchesWatcherPath(item.OldPath))
{
Deleted?.Invoke(this, ToFileSystemEventArgs(
- WatcherChangeTypes.Deleted, item.OldPath, item.OldName));
+ WatcherChangeTypes.Deleted, item.OldPath));
}
if (MatchesWatcherPath(item.Path))
{
Created?.Invoke(this, ToFileSystemEventArgs(
- WatcherChangeTypes.Created, item.Path, item.Name));
+ WatcherChangeTypes.Created, item.Path));
}
}
}
@@ -601,54 +567,92 @@ private void TriggerRenameNotification(ChangeDescription item)
private bool TryMakeRenamedEventArgs(
ChangeDescription changeDescription,
- [NotNullWhen(true)] out RenamedEventArgs? eventArgs)
+ [NotNullWhen(true)] out RenamedEventArgs? eventArgs
+ )
{
if (changeDescription.OldPath == null)
{
eventArgs = null;
+
return false;
}
- string path = TransformPathAndName(
- changeDescription.Path,
- changeDescription.Name,
- out string name);
+ string name = TransformPathAndName(changeDescription.Path);
+
+ string oldName = TransformPathAndName(changeDescription.OldPath);
+
+ eventArgs = new RenamedEventArgs(changeDescription.ChangeType, Path, name, oldName);
+
+ SetFileSystemEventArgsFullPath(eventArgs, name);
+ SetRenamedEventArgsFullPath(eventArgs, oldName);
- string oldPath = TransformPathAndName(
- changeDescription.OldPath,
- changeDescription.OldName,
- out string oldName);
+ return _fileSystem.Execute.Path.GetDirectoryName(changeDescription.Path)?.Equals(
+ _fileSystem.Execute.Path.GetDirectoryName(changeDescription.OldPath),
+ _fileSystem.Execute.StringComparisonMode
+ )
+ ?? true;
+ }
+
+ private FileSystemEventArgs ToFileSystemEventArgs(
+ WatcherChangeTypes changeType,
+ string changePath)
+ {
+ string name = TransformPathAndName(changePath);
+
+ FileSystemEventArgs eventArgs = new(changeType, Path, name);
+
+ SetFileSystemEventArgsFullPath(eventArgs, name);
+
+ return eventArgs;
+ }
- eventArgs = new RenamedEventArgs(
- changeDescription.ChangeType,
- Path,
- name,
- oldName);
+ private string TransformPathAndName(string changeDescriptionPath)
+ {
+ return changeDescriptionPath.Substring(FullPath.Length).TrimStart(_fileSystem.Path.DirectorySeparatorChar);
+ }
- if (_fileSystem.SimulationMode != SimulationMode.Native)
+ private void SetFileSystemEventArgsFullPath(FileSystemEventArgs args, string name)
+ {
+ if (_fileSystem.SimulationMode == SimulationMode.Native)
{
- // RenamedEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs
- // HACK: Have to resort to Reflection to override this behavior!
+ return;
+ }
+
+ string fullPath = _fileSystem.Path.Combine(Path, name);
+
+ // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
+ // HACK: The combination uses the system separator, so to simulate the behavior, we must override it using reflection!
#if NETFRAMEWORK
typeof(FileSystemEventArgs)
.GetField("fullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, path);
+ .SetValue(args, fullPath);
+#else
+ typeof(FileSystemEventArgs)
+ .GetField("_fullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
+ .SetValue(args, fullPath);
+#endif
+ }
+
+ private void SetRenamedEventArgsFullPath(RenamedEventArgs args, string oldName)
+ {
+ if (_fileSystem.SimulationMode == SimulationMode.Native)
+ {
+ return;
+ }
+
+ string fullPath = _fileSystem.Path.Combine(Path, oldName);
+
+ // FileSystemEventArgs implicitly combines the path in https://github.com/dotnet/runtime/blob/v8.0.4/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
+ // HACK: The combination uses the system separator, so to simulate the behavior, we must override it using reflection!
+#if NETFRAMEWORK
typeof(RenamedEventArgs)
.GetField("oldFullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, oldPath);
+ .SetValue(args, fullPath);
#else
- typeof(FileSystemEventArgs)
- .GetField("_fullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, path);
- typeof(RenamedEventArgs)
- .GetField("_oldFullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
- .SetValue(eventArgs, oldPath);
+ typeof(RenamedEventArgs)
+ .GetField("_oldFullPath", BindingFlags.Instance | BindingFlags.NonPublic)?
+ .SetValue(args, fullPath);
#endif
- }
-
- return _fileSystem.Execute.Path.GetDirectoryName(changeDescription.Path)?
- .Equals(_fileSystem.Execute.Path.GetDirectoryName(changeDescription.OldPath),
- _fileSystem.Execute.StringComparisonMode) ?? true;
}
private IWaitForChangedResult WaitForChangedInternal(
diff --git a/Tests/Testably.Abstractions.Testing.Tests/FileSystem/FileSystemWatcherMockTests.cs b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/FileSystemWatcherMockTests.cs
index fd61f3a9..d49e8199 100644
--- a/Tests/Testably.Abstractions.Testing.Tests/FileSystem/FileSystemWatcherMockTests.cs
+++ b/Tests/Testably.Abstractions.Testing.Tests/FileSystem/FileSystemWatcherMockTests.cs
@@ -249,7 +249,6 @@ public async Task FileSystemEventArgs_ShouldUseDirectorySeparatorFromSimulatedFi
FileSystemEventArgs? result = null;
string expectedFullPath = fileSystem.Path.GetFullPath(
fileSystem.Path.Combine(parentDirectory, directoryName));
- string expectedName = fileSystem.Path.Combine(parentDirectory, directoryName);
using IFileSystemWatcher fileSystemWatcher =
fileSystem.FileSystemWatcher.New(fileSystem.Path.GetFullPath(parentDirectory));
@@ -269,12 +268,12 @@ public async Task FileSystemEventArgs_ShouldUseDirectorySeparatorFromSimulatedFi
};
fileSystemWatcher.NotifyFilter = NotifyFilters.DirectoryName;
fileSystemWatcher.EnableRaisingEvents = true;
- fileSystem.Directory.CreateDirectory(expectedName);
+ fileSystem.Directory.CreateDirectory(expectedFullPath);
ms.Wait(5000, TestContext.Current.CancellationToken);
await That(result).IsNotNull();
await That(result!.FullPath).IsEqualTo(expectedFullPath);
- await That(result.Name).IsEqualTo(expectedName);
+ await That(result.Name).IsEqualTo(directoryName);
await That(result.ChangeType).IsEqualTo(WatcherChangeTypes.Created);
}
#endif
diff --git a/Tests/Testably.Abstractions.Tests/FileSystem/FileSystemWatcher/IncludeSubdirectoriesTests.cs b/Tests/Testably.Abstractions.Tests/FileSystem/FileSystemWatcher/IncludeSubdirectoriesTests.cs
index 64e14b8e..32999384 100644
--- a/Tests/Testably.Abstractions.Tests/FileSystem/FileSystemWatcher/IncludeSubdirectoriesTests.cs
+++ b/Tests/Testably.Abstractions.Tests/FileSystem/FileSystemWatcher/IncludeSubdirectoriesTests.cs
@@ -9,15 +9,17 @@ public partial class IncludeSubdirectoriesTests
[Theory]
[AutoData]
public async Task IncludeSubdirectories_SetToFalse_ShouldNotTriggerNotification(
- string baseDirectory, string path)
+ string baseDirectory,
+ string path
+ )
{
- FileSystem.Initialize()
- .WithSubdirectory(baseDirectory).Initialized(s => s
- .WithSubdirectory(path));
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(path));
+
using ManualResetEventSlim ms = new();
FileSystemEventArgs? result = null;
- using IFileSystemWatcher fileSystemWatcher =
- FileSystem.FileSystemWatcher.New(BasePath);
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(BasePath);
+
fileSystemWatcher.Deleted += (_, eventArgs) =>
{
// ReSharper disable once AccessToDisposedClosure
@@ -31,6 +33,7 @@ public async Task IncludeSubdirectories_SetToFalse_ShouldNotTriggerNotification(
// Ignore any ObjectDisposedException
}
};
+
fileSystemWatcher.IncludeSubdirectories = false;
fileSystemWatcher.EnableRaisingEvents = true;
FileSystem.Directory.Delete(FileSystem.Path.Combine(baseDirectory, path));
@@ -42,16 +45,21 @@ public async Task IncludeSubdirectories_SetToFalse_ShouldNotTriggerNotification(
[Theory]
[AutoData]
public async Task IncludeSubdirectories_SetToTrue_ShouldOnlyTriggerNotificationOnSubdirectories(
- string baseDirectory, string subdirectoryName, string otherDirectory)
+ string baseDirectory,
+ string subdirectoryName,
+ string otherDirectory
+ )
{
- FileSystem.Initialize()
- .WithSubdirectory(baseDirectory).Initialized(s => s
- .WithSubdirectory(subdirectoryName))
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName))
.WithSubdirectory(otherDirectory);
+
using ManualResetEventSlim ms = new();
FileSystemEventArgs? result = null;
- using IFileSystemWatcher fileSystemWatcher =
- FileSystem.FileSystemWatcher.New(baseDirectory);
+
+ using IFileSystemWatcher fileSystemWatcher
+ = FileSystem.FileSystemWatcher.New(baseDirectory);
+
fileSystemWatcher.Deleted += (_, eventArgs) =>
{
// ReSharper disable once AccessToDisposedClosure
@@ -65,6 +73,7 @@ public async Task IncludeSubdirectories_SetToTrue_ShouldOnlyTriggerNotificationO
// Ignore any ObjectDisposedException
}
};
+
fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.EnableRaisingEvents = true;
FileSystem.Directory.Delete(otherDirectory);
@@ -76,17 +85,18 @@ public async Task IncludeSubdirectories_SetToTrue_ShouldOnlyTriggerNotificationO
[Theory]
[AutoData]
public async Task IncludeSubdirectories_SetToTrue_ShouldTriggerNotificationOnSubdirectories(
- string baseDirectory, string subdirectoryName)
+ string baseDirectory,
+ string subdirectoryName
+ )
{
- FileSystem.Initialize()
- .WithSubdirectory(baseDirectory).Initialized(s => s
- .WithSubdirectory(subdirectoryName));
- string subdirectoryPath =
- FileSystem.Path.Combine(baseDirectory, subdirectoryName);
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName));
+
+ string subdirectoryPath = FileSystem.Path.Combine(baseDirectory, subdirectoryName);
using ManualResetEventSlim ms = new();
FileSystemEventArgs? result = null;
- using IFileSystemWatcher fileSystemWatcher =
- FileSystem.FileSystemWatcher.New(BasePath);
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(BasePath);
+
fileSystemWatcher.Deleted += (_, eventArgs) =>
{
// ReSharper disable once AccessToDisposedClosure
@@ -100,6 +110,7 @@ public async Task IncludeSubdirectories_SetToTrue_ShouldTriggerNotificationOnSub
// Ignore any ObjectDisposedException
}
};
+
fileSystemWatcher.IncludeSubdirectories = true;
fileSystemWatcher.EnableRaisingEvents = true;
FileSystem.Directory.Delete(subdirectoryPath);
@@ -110,4 +121,280 @@ public async Task IncludeSubdirectories_SetToTrue_ShouldTriggerNotificationOnSub
await That(result.Name).IsEqualTo(subdirectoryPath);
await That(result!.ChangeType).IsEqualTo(WatcherChangeTypes.Deleted);
}
+
+ [Theory]
+ [InlineAutoData(true)]
+ [InlineAutoData(false)]
+ public async Task IncludeSubdirectories_SetToTrue_Created_ArgsNameShouldContainRelativePath(
+ bool watchRootedPath,
+ string baseDirectory,
+ string fileName
+ )
+ {
+ string subdirectoryName = "a";
+ string subSubdirectoryName = "b";
+ // Arrange
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName)
+ .Initialized(ss => ss.WithSubdirectory(subSubdirectoryName))
+ );
+
+ string filePath = FileSystem.Path.Combine(
+ baseDirectory, subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string expectedFileName = FileSystem.Path.Combine(
+ subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string watchPath = watchRootedPath
+ ? FileSystem.Path.Combine(FileSystem.Directory.GetCurrentDirectory(), baseDirectory)
+ : baseDirectory;
+
+ using ManualResetEventSlim createdMre = new();
+ FileSystemEventArgs? createdArgs = null;
+
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(watchPath);
+
+ fileSystemWatcher.Created += (_, eventArgs) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ try
+ {
+ createdArgs = eventArgs;
+ createdMre.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignore any ObjectDisposedException
+ }
+ };
+
+ fileSystemWatcher.IncludeSubdirectories = true;
+ fileSystemWatcher.EnableRaisingEvents = true;
+
+ // Act
+
+ FileSystem.File.Create(filePath).Dispose();
+
+ // Assert
+
+ await That(createdMre.Wait(ExpectTimeout, TestContext.Current.CancellationToken)).IsTrue();
+
+ await That(createdArgs).IsNotNull().And
+ .Satisfies(args => string.Equals(args?.Name, expectedFileName, StringComparison.Ordinal)
+ );
+ }
+
+ [Theory]
+ [InlineAutoData(true)]
+ [InlineAutoData(false)]
+ public async Task IncludeSubdirectories_SetToTrue_Changed_ArgsNameShouldContainRelativePath(
+ bool watchRootedPath,
+ string baseDirectory,
+ string fileName
+ )
+ {
+ string subdirectoryName = "a";
+ string subSubdirectoryName = "b";
+ // Arrange
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName)
+ .Initialized(ss => ss.WithSubdirectory(subSubdirectoryName))
+ );
+
+ string filePath = FileSystem.Path.Combine(
+ baseDirectory, subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string expectedFileName = FileSystem.Path.Combine(
+ subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string watchPath = watchRootedPath
+ ? FileSystem.Path.Combine(FileSystem.Directory.GetCurrentDirectory(), baseDirectory)
+ : baseDirectory;
+
+ using ManualResetEventSlim changedMre = new();
+ FileSystemEventArgs? changedArgs = null;
+
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(watchPath);
+
+ fileSystemWatcher.Changed += (_, eventArgs) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ try
+ {
+ // OS fires for subSubDir due to item changing
+ if (!string.Equals(eventArgs.Name, expectedFileName, StringComparison.Ordinal))
+ {
+ return;
+ }
+
+ changedArgs ??= eventArgs;
+ changedMre.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignore any ObjectDisposedException
+ }
+ };
+
+ fileSystemWatcher.IncludeSubdirectories = true;
+ fileSystemWatcher.EnableRaisingEvents = true;
+
+ // Act
+
+ FileSystem.File.Create(filePath).Dispose();
+ FileSystem.File.WriteAllText(filePath, "Hello World!");
+
+ // Assert
+
+ await That(changedMre.Wait(ExpectTimeout, TestContext.Current.CancellationToken)).IsTrue();
+
+ await That(changedArgs).IsNotNull().And
+ .Satisfies(args => string.Equals(args?.Name, expectedFileName, StringComparison.Ordinal)
+ );
+ }
+
+ [Theory]
+ [InlineAutoData(true)]
+ [InlineAutoData(false)]
+ public async Task IncludeSubdirectories_SetToTrue_Renamed_ArgsNameShouldContainRelativePath(
+ bool watchRootedPath,
+ string baseDirectory,
+ string fileName
+ )
+ {
+ string subdirectoryName = "a";
+ string subSubdirectoryName = "b";
+ // Arrange
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName)
+ .Initialized(ss => ss.WithSubdirectory(subSubdirectoryName))
+ );
+
+ string filePath = FileSystem.Path.Combine(
+ baseDirectory, subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string newFilePath = filePath + ".new";
+
+ string expectedFileName = FileSystem.Path.Combine(
+ subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string expectedNewFileName = expectedFileName + ".new";
+
+ string watchPath = watchRootedPath
+ ? FileSystem.Path.Combine(FileSystem.Directory.GetCurrentDirectory(), baseDirectory)
+ : baseDirectory;
+
+ using ManualResetEventSlim renamedMre = new();
+ RenamedEventArgs? renamedArgs = null;
+
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(watchPath);
+
+ fileSystemWatcher.Renamed += (_, eventArgs) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ try
+ {
+ renamedArgs = eventArgs;
+ renamedMre.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignore any ObjectDisposedException
+ }
+ };
+
+ fileSystemWatcher.IncludeSubdirectories = true;
+ fileSystemWatcher.EnableRaisingEvents = true;
+
+ // Act
+
+ FileSystem.File.Create(filePath).Dispose();
+ FileSystem.File.Move(filePath, newFilePath);
+
+ // Assert
+
+ await That(renamedMre.Wait(ExpectTimeout, TestContext.Current.CancellationToken)).IsTrue();
+
+ await That(renamedArgs).IsNotNull().And
+ .Satisfies(args => string.Equals(
+ args?.Name, expectedNewFileName, StringComparison.Ordinal
+ )
+ ).And.Satisfies(args => string.Equals(
+ args?.OldName, expectedFileName, StringComparison.Ordinal
+ )
+ );
+ }
+
+ [Theory]
+ [InlineAutoData(true)]
+ [InlineAutoData(false)]
+ public async Task IncludeSubdirectories_SetToTrue_Deleted_ArgsNameShouldContainRelativePath(
+ bool watchRootedPath,
+ string baseDirectory,
+ string fileName
+ )
+ {
+ string subdirectoryName = "a";
+ string subSubdirectoryName = "b";
+ // Arrange
+ FileSystem.Initialize().WithSubdirectory(baseDirectory)
+ .Initialized(s => s.WithSubdirectory(subdirectoryName)
+ .Initialized(ss => ss.WithSubdirectory(subSubdirectoryName))
+ );
+
+ string filePath = FileSystem.Path.Combine(
+ baseDirectory, subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string expectedFileName = FileSystem.Path.Combine(
+ subdirectoryName, subSubdirectoryName, fileName
+ );
+
+ string watchPath = watchRootedPath
+ ? FileSystem.Path.Combine(FileSystem.Directory.GetCurrentDirectory(), baseDirectory)
+ : baseDirectory;
+
+ using ManualResetEventSlim deletedMre = new();
+ FileSystemEventArgs? deletedArgs = null;
+
+ using IFileSystemWatcher fileSystemWatcher = FileSystem.FileSystemWatcher.New(watchPath);
+
+ fileSystemWatcher.Deleted += (_, eventArgs) =>
+ {
+ // ReSharper disable once AccessToDisposedClosure
+ try
+ {
+ deletedArgs ??= eventArgs;
+ deletedMre.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // Ignore any ObjectDisposedException
+ }
+ };
+
+ fileSystemWatcher.IncludeSubdirectories = true;
+ fileSystemWatcher.EnableRaisingEvents = true;
+
+ // Act
+
+ FileSystem.File.Create(filePath).Dispose();
+ FileSystem.File.Delete(filePath);
+
+ // Assert
+
+ await That(deletedMre.Wait(ExpectTimeout, TestContext.Current.CancellationToken)).IsTrue();
+
+ await That(deletedArgs).IsNotNull().And.Satisfies(args => string.Equals(
+ args?.Name, expectedFileName,
+ StringComparison.Ordinal
+ )
+ );
+ }
}