Skip to content

Commit e1c23f8

Browse files
authored
feat: implement Join for simulated Path (#567)
Implement the `Join` methods for `Path`.
1 parent a43d1f0 commit e1c23f8

File tree

3 files changed

+152
-59
lines changed

3 files changed

+152
-59
lines changed

Source/Testably.Abstractions.Testing/Helpers/Execute.SimulatedPath.cs

+45-59
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Diagnostics.CodeAnalysis;
3+
using System.Text;
4+
35
#if FEATURE_FILESYSTEM_NET7
46
using Testably.Abstractions.Testing.Storage;
57
#endif
@@ -342,15 +344,15 @@ public bool IsPathRooted(ReadOnlySpan<char> path)
342344
#if FEATURE_PATH_JOIN
343345
/// <inheritdoc cref="IPath.Join(ReadOnlySpan{char}, ReadOnlySpan{char})" />
344346
public string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
345-
=> System.IO.Path.Join(path1, path2);
347+
=> JoinInternal([path1.ToString(), path2.ToString()]);
346348
#endif
347349

348350
#if FEATURE_PATH_JOIN
349351
/// <inheritdoc cref="IPath.Join(ReadOnlySpan{char}, ReadOnlySpan{char}, ReadOnlySpan{char})" />
350352
public string Join(ReadOnlySpan<char> path1,
351353
ReadOnlySpan<char> path2,
352354
ReadOnlySpan<char> path3)
353-
=> System.IO.Path.Join(path1, path2, path3);
355+
=> JoinInternal([path1.ToString(), path2.ToString(), path3.ToString()]);
354356
#endif
355357

356358
#if FEATURE_PATH_ADVANCED
@@ -366,70 +368,19 @@ public string Join(ReadOnlySpan<char> path1,
366368
#if FEATURE_PATH_ADVANCED
367369
/// <inheritdoc cref="IPath.Join(string, string)" />
368370
public string Join(string? path1, string? path2)
369-
{
370-
if (string.IsNullOrEmpty(path1))
371-
{
372-
return path2 ?? string.Empty;
373-
}
374-
375-
if (string.IsNullOrEmpty(path2))
376-
{
377-
return path1;
378-
}
379-
380-
return JoinInternal([path1, path2]);
381-
}
371+
=> JoinInternal([path1, path2]);
382372
#endif
383373

384374
#if FEATURE_PATH_ADVANCED
385375
/// <inheritdoc cref="IPath.Join(string, string, string)" />
386376
public string Join(string? path1, string? path2, string? path3)
387-
{
388-
if (string.IsNullOrEmpty(path1))
389-
{
390-
return Join(path2, path3);
391-
}
392-
393-
if (string.IsNullOrEmpty(path2))
394-
{
395-
return Join(path1, path3);
396-
}
397-
398-
if (string.IsNullOrEmpty(path3))
399-
{
400-
return Join(path1, path2);
401-
}
402-
403-
return JoinInternal([path1, path2, path3]);
404-
}
377+
=> JoinInternal([path1, path2, path3]);
405378
#endif
406379

407380
#if FEATURE_PATH_ADVANCED
408381
/// <inheritdoc cref="IPath.Join(string, string, string, string)" />
409382
public string Join(string? path1, string? path2, string? path3, string? path4)
410-
{
411-
if (string.IsNullOrEmpty(path1))
412-
{
413-
return Join(path2, path3, path4);
414-
}
415-
416-
if (string.IsNullOrEmpty(path2))
417-
{
418-
return Join(path1, path3, path4);
419-
}
420-
421-
if (string.IsNullOrEmpty(path3))
422-
{
423-
return Join(path1, path2, path4);
424-
}
425-
426-
if (string.IsNullOrEmpty(path4))
427-
{
428-
return Join(path1, path2, path3);
429-
}
430-
431-
return JoinInternal([path1, path2, path3, path4]);
432-
}
383+
=> JoinInternal([path1, path2, path3, path4]);
433384
#endif
434385

435386
#if FEATURE_PATH_ADVANCED
@@ -476,9 +427,44 @@ private static string CombineInternal(string[] paths)
476427

477428
protected abstract bool IsDirectorySeparator(char c);
478429

479-
#if FEATURE_PATH_ADVANCED
480-
private static string JoinInternal(string?[] paths)
481-
=> System.IO.Path.Join(paths);
430+
#if FEATURE_PATH_JOIN || FEATURE_PATH_ADVANCED
431+
private string JoinInternal(string?[] paths)
432+
{
433+
if (paths == null)
434+
{
435+
throw new ArgumentNullException(nameof(paths));
436+
}
437+
438+
if (paths.Length == 0)
439+
{
440+
return string.Empty;
441+
}
442+
443+
StringBuilder sb = new StringBuilder();
444+
foreach (string? path in paths)
445+
{
446+
if (string.IsNullOrEmpty(path))
447+
{
448+
continue;
449+
}
450+
451+
if (sb.Length == 0)
452+
{
453+
sb.Append(path);
454+
}
455+
else
456+
{
457+
if (!IsDirectorySeparator(sb[sb.Length - 1]) && !IsDirectorySeparator(path[0]))
458+
{
459+
sb.Append(DirectorySeparatorChar);
460+
}
461+
462+
sb.Append(path);
463+
}
464+
}
465+
466+
return sb.ToString();
467+
}
482468
#endif
483469

484470
private bool TryGetExtensionIndex(string path, [NotNullWhen(true)] out int? dotIndex)

Tests/Helpers/Testably.Abstractions.Tests.SourceGenerator/ClassGenerators/FileSystemClassGenerator.cs

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ private bool IncludeSimulatedTests(ClassModel @class)
240240
"GetRandomFileNameTests",
241241
"GetTempPathTests",
242242
"IsPathRootedTests",
243+
"JoinTests",
243244
"Tests"
244245
];
245246
return @class.Namespace

Tests/Testably.Abstractions.Tests/FileSystem/Path/JoinTests.cs

+106
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,25 @@ public void Join_2Paths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts(
1919
result2.Should().Be(path);
2020
}
2121

22+
[SkippableTheory]
23+
[InlineAutoData("/foo/", "/bar/", "/foo//bar/")]
24+
[InlineAutoData("foo/", "/bar", "foo//bar")]
25+
[InlineAutoData("foo/", "bar", "foo/bar")]
26+
[InlineAutoData("foo", "/bar", "foo/bar")]
27+
[InlineAutoData("foo", "bar", "foo/bar")]
28+
[InlineAutoData("/foo", "bar/", "/foo/bar/")]
29+
public void Join_2Paths_ShouldReturnExpectedResult(
30+
string path1, string path2, string expectedResult)
31+
{
32+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
33+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
34+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
35+
36+
string result = FileSystem.Path.Join(path1, path2);
37+
38+
result.Should().Be(expectedResult);
39+
}
40+
2241
[SkippableTheory]
2342
[AutoData]
2443
public void Join_2Paths_ShouldReturnPathsCombinedByDirectorySeparatorChar(
@@ -64,6 +83,27 @@ public void Join_3Paths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts(
6483
result3.Should().Be(expectedPath);
6584
}
6685

86+
[SkippableTheory]
87+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/foo//bar//baz/")]
88+
[InlineAutoData("foo/", "/bar/", "/baz", "foo//bar//baz")]
89+
[InlineAutoData("foo/", "bar", "/baz", "foo/bar/baz")]
90+
[InlineAutoData("foo", "/bar", "/baz", "foo/bar/baz")]
91+
[InlineAutoData("foo", "/bar/", "baz", "foo/bar/baz")]
92+
[InlineAutoData("foo", "bar", "baz", "foo/bar/baz")]
93+
[InlineAutoData("/foo", "bar", "baz/", "/foo/bar/baz/")]
94+
public void Join_3Paths_ShouldReturnExpectedResult(
95+
string path1, string path2, string path3, string expectedResult)
96+
{
97+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
98+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
99+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
100+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
101+
102+
string result = FileSystem.Path.Join(path1, path2, path3);
103+
104+
result.Should().Be(expectedResult);
105+
}
106+
67107
[SkippableTheory]
68108
[AutoData]
69109
public void Join_3Paths_ShouldReturnPathsCombinedByDirectorySeparatorChar(
@@ -115,6 +155,28 @@ public void Join_4Paths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts(
115155
result4.Should().Be(expectedPath);
116156
}
117157

158+
[SkippableTheory]
159+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/foo//bar//baz//muh/")]
160+
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "foo//bar//baz//muh")]
161+
[InlineAutoData("foo/", "bar", "/baz", "/muh", "foo/bar/baz/muh")]
162+
[InlineAutoData("foo", "/bar", "/baz", "/muh", "foo/bar/baz/muh")]
163+
[InlineAutoData("foo", "/bar/", "baz/", "muh", "foo/bar/baz/muh")]
164+
[InlineAutoData("foo", "bar", "baz", "muh", "foo/bar/baz/muh")]
165+
[InlineAutoData("/foo", "bar", "baz", "muh/", "/foo/bar/baz/muh/")]
166+
public void Join_4Paths_ShouldReturnExpectedResult(
167+
string path1, string path2, string path3, string path4, string expectedResult)
168+
{
169+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
170+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
171+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
172+
path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar);
173+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
174+
175+
string result = FileSystem.Path.Join(path1, path2, path3, path4);
176+
177+
result.Should().Be(expectedResult);
178+
}
179+
118180
[SkippableTheory]
119181
[AutoData]
120182
public void Join_4Paths_ShouldReturnPathsCombinedByDirectorySeparatorChar(
@@ -149,6 +211,27 @@ public void Join_4Paths_Span_ShouldReturnPathsCombinedByDirectorySeparatorChar(
149211
result.Should().Be(expectedResult);
150212
}
151213

214+
[SkippableFact]
215+
public void Join_ParamPaths_Empty_ShouldReturnEmptyString()
216+
{
217+
string?[] paths = Array.Empty<string?>();
218+
219+
string result = FileSystem.Path.Join(paths);
220+
221+
result.Should().Be(string.Empty);
222+
}
223+
224+
[SkippableFact]
225+
public void Join_ParamPaths_Null_ShouldThrow()
226+
{
227+
Exception? exception = Record.Exception(() =>
228+
{
229+
_ = FileSystem.Path.Join(null!);
230+
});
231+
232+
exception.Should().BeException<ArgumentNullException>(paramName: "paths");
233+
}
234+
152235
[SkippableTheory]
153236
[InlineAutoData((string?)null)]
154237
[InlineAutoData("")]
@@ -176,6 +259,29 @@ public void Join_ParamPaths_OneNullOrEmpty_ShouldReturnCombinationOfOtherParts(
176259
result5.Should().Be(expectedPath);
177260
}
178261

262+
[SkippableTheory]
263+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/foo//bar//baz//muh//maeh/")]
264+
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "foo//bar//baz//muh/maeh")]
265+
[InlineAutoData("foo/", "bar", "/baz", "/muh", "/maeh", "foo/bar/baz/muh/maeh")]
266+
[InlineAutoData("foo", "/bar", "/baz", "/muh", "/maeh", "foo/bar/baz/muh/maeh")]
267+
[InlineAutoData("foo", "/bar/", "baz/", "muh/", "maeh", "foo/bar/baz/muh/maeh")]
268+
[InlineAutoData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")]
269+
[InlineAutoData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")]
270+
public void Join_ParamPaths_ShouldReturnExpectedResult(
271+
string path1, string path2, string path3, string path4, string path5, string expectedResult)
272+
{
273+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
274+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
275+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
276+
path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar);
277+
path5 = path5.Replace('/', FileSystem.Path.DirectorySeparatorChar);
278+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
279+
280+
string result = FileSystem.Path.Join(path1, path2, path3, path4, path5);
281+
282+
result.Should().Be(expectedResult);
283+
}
284+
179285
[SkippableTheory]
180286
[AutoData]
181287
public void Join_ParamPaths_ShouldReturnPathsCombinedByDirectorySeparatorChar(

0 commit comments

Comments
 (0)