Skip to content

Commit 760b601

Browse files
authored
feat: implement Combine for simulated Path (#572)
Implement the `Combine` methods for `Path`.
1 parent 2932ab1 commit 760b601

File tree

3 files changed

+146
-2
lines changed

3 files changed

+146
-2
lines changed

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

+60-2
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,66 @@ public bool TryJoin(ReadOnlySpan<char> path1,
468468

469469
#endregion
470470

471-
private static string CombineInternal(string[] paths)
472-
=> System.IO.Path.Combine(paths);
471+
private string CombineInternal(string[] paths)
472+
{
473+
string NormalizePath(string path, bool ignoreStartingSeparator)
474+
{
475+
if (!ignoreStartingSeparator && (
476+
path[0] == DirectorySeparatorChar ||
477+
path[0] == AltDirectorySeparatorChar))
478+
{
479+
path = path.Substring(1);
480+
}
481+
482+
if (path[path.Length - 1] == DirectorySeparatorChar ||
483+
path[path.Length - 1] == AltDirectorySeparatorChar)
484+
{
485+
path = path.Substring(0, path.Length - 1);
486+
}
487+
488+
return NormalizeDirectorySeparators(path);
489+
}
490+
491+
if (paths == null)
492+
{
493+
throw new ArgumentNullException(nameof(paths));
494+
}
495+
496+
StringBuilder sb = new();
497+
498+
bool isFirst = true;
499+
bool endsWithDirectorySeparator = false;
500+
foreach (string path in paths)
501+
{
502+
if (path == null)
503+
{
504+
throw new ArgumentNullException(nameof(paths));
505+
}
506+
507+
if (string.IsNullOrEmpty(path))
508+
{
509+
continue;
510+
}
511+
512+
if (IsPathRooted(path))
513+
{
514+
sb.Clear();
515+
isFirst = true;
516+
}
517+
518+
sb.Append(NormalizePath(path, isFirst));
519+
sb.Append(DirectorySeparatorChar);
520+
endsWithDirectorySeparator = path.EndsWith(DirectorySeparatorChar) ||
521+
path.EndsWith(AltDirectorySeparatorChar);
522+
}
523+
524+
if (!endsWithDirectorySeparator)
525+
{
526+
return sb.ToString(0, sb.Length - 1);
527+
}
528+
529+
return sb.ToString();
530+
}
473531

474532
protected abstract int GetRootLength(string path);
475533
protected abstract bool IsDirectorySeparator(char c);

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

+1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ private bool IncludeSimulatedTests(ClassModel @class)
232232
string[] supportedPathTests =
233233
[
234234
"ChangeExtensionTests",
235+
"CombineTests",
235236
"EndsInDirectorySeparatorTests",
236237
"GetDirectoryNameTests",
237238
"GetExtensionTests",

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

+85
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,25 @@ public void Combine_2Paths_Rooted_ShouldReturnLastRootedPath(
4545
result.Should().Be(path2);
4646
}
4747

48+
[SkippableTheory]
49+
[InlineAutoData("/foo/", "/bar/", "/bar/")]
50+
[InlineAutoData("foo/", "/bar", "/bar")]
51+
[InlineAutoData("foo/", "bar", "foo/bar")]
52+
[InlineAutoData("foo", "/bar", "/bar")]
53+
[InlineAutoData("foo", "bar", "foo/bar")]
54+
[InlineAutoData("/foo", "bar/", "/foo/bar/")]
55+
public void Combine_2Paths_ShouldReturnExpectedResult(
56+
string path1, string path2, string expectedResult)
57+
{
58+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
59+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
60+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
61+
62+
string result = FileSystem.Path.Combine(path1, path2);
63+
64+
result.Should().Be(expectedResult);
65+
}
66+
4867
[SkippableTheory]
4968
[InlineAutoData]
5069
[InlineAutoData(" ")]
@@ -109,6 +128,27 @@ public void Combine_3Paths_Rooted_ShouldReturnLastRootedPath(
109128
result.Should().Be(path3);
110129
}
111130

131+
[SkippableTheory]
132+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/baz/")]
133+
[InlineAutoData("foo/", "/bar/", "/baz", "/baz")]
134+
[InlineAutoData("foo/", "bar", "/baz", "/baz")]
135+
[InlineAutoData("foo", "/bar", "/baz", "/baz")]
136+
[InlineAutoData("foo", "/bar/", "baz", "/bar/baz")]
137+
[InlineAutoData("foo", "bar", "baz", "foo/bar/baz")]
138+
[InlineAutoData("/foo", "bar", "baz/", "/foo/bar/baz/")]
139+
public void Combine_3Paths_ShouldReturnExpectedResult(
140+
string path1, string path2, string path3, string expectedResult)
141+
{
142+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
143+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
144+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
145+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
146+
147+
string result = FileSystem.Path.Combine(path1, path2, path3);
148+
149+
result.Should().Be(expectedResult);
150+
}
151+
112152
[SkippableTheory]
113153
[InlineAutoData]
114154
[InlineAutoData(" ")]
@@ -183,6 +223,28 @@ public void Combine_4Paths_Rooted_ShouldReturnLastRootedPath(
183223
result.Should().Be(path4);
184224
}
185225

226+
[SkippableTheory]
227+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/muh/")]
228+
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/muh")]
229+
[InlineAutoData("foo/", "bar", "/baz", "/muh", "/muh")]
230+
[InlineAutoData("foo", "/bar", "/baz", "/muh", "/muh")]
231+
[InlineAutoData("foo", "/bar/", "baz/", "muh", "/bar/baz/muh")]
232+
[InlineAutoData("foo", "bar", "baz", "muh", "foo/bar/baz/muh")]
233+
[InlineAutoData("/foo", "bar", "baz", "muh/", "/foo/bar/baz/muh/")]
234+
public void Combine_4Paths_ShouldReturnExpectedResult(
235+
string path1, string path2, string path3, string path4, string expectedResult)
236+
{
237+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
238+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
239+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
240+
path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar);
241+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
242+
243+
string result = FileSystem.Path.Combine(path1, path2, path3, path4);
244+
245+
result.Should().Be(expectedResult);
246+
}
247+
186248
[SkippableTheory]
187249
[InlineAutoData]
188250
[InlineAutoData(" ")]
@@ -281,6 +343,29 @@ public void Combine_ParamPaths_Rooted_ShouldReturnLastRootedPath(
281343
result.Should().Be(path5);
282344
}
283345

346+
[SkippableTheory]
347+
[InlineAutoData("/foo/", "/bar/", "/baz/", "/muh/", "/maeh/", "/maeh/")]
348+
[InlineAutoData("foo/", "/bar/", "/baz/", "/muh", "/maeh", "/maeh")]
349+
[InlineAutoData("foo/", "bar", "/baz", "/muh", "/maeh", "/maeh")]
350+
[InlineAutoData("foo", "/bar", "/baz", "/muh", "/maeh", "/maeh")]
351+
[InlineAutoData("foo", "/bar/", "baz/", "muh/", "maeh", "/bar/baz/muh/maeh")]
352+
[InlineAutoData("foo", "bar", "baz", "muh", "maeh", "foo/bar/baz/muh/maeh")]
353+
[InlineAutoData("/foo", "bar", "baz", "muh", "maeh/", "/foo/bar/baz/muh/maeh/")]
354+
public void Combine_ParamPaths_ShouldReturnExpectedResult(
355+
string path1, string path2, string path3, string path4, string path5, string expectedResult)
356+
{
357+
path1 = path1.Replace('/', FileSystem.Path.DirectorySeparatorChar);
358+
path2 = path2.Replace('/', FileSystem.Path.DirectorySeparatorChar);
359+
path3 = path3.Replace('/', FileSystem.Path.DirectorySeparatorChar);
360+
path4 = path4.Replace('/', FileSystem.Path.DirectorySeparatorChar);
361+
path5 = path5.Replace('/', FileSystem.Path.DirectorySeparatorChar);
362+
expectedResult = expectedResult.Replace('/', FileSystem.Path.DirectorySeparatorChar);
363+
364+
string result = FileSystem.Path.Combine(path1, path2, path3, path4, path5);
365+
366+
result.Should().Be(expectedResult);
367+
}
368+
284369
[SkippableTheory]
285370
[InlineAutoData]
286371
[InlineAutoData(" ")]

0 commit comments

Comments
 (0)