diff --git a/docs/combinations.md b/docs/combinations.md index ebcd5c37f..82f82242c 100644 --- a/docs/combinations.md +++ b/docs/combinations.md @@ -198,6 +198,7 @@ public class CombinationResultsConverter : var items = results.Items; if (items.Count == 0) { + writer.WriteEndObject(); return; } @@ -363,8 +364,11 @@ public class CombinationResultsConverter : builder.Append(trimmed); if (!trimmed.EndsWith('.')) { - builder.Append(". "); + builder.Append('.'); } + + // separate from the next line even when the sentence was already terminated + builder.Append(' '); } builder.TrimEnd(); @@ -372,7 +376,7 @@ public class CombinationResultsConverter : } } ``` -snippet source | anchor +snippet source | anchor diff --git a/src/Verify.Tests/CombinationTests.EmptyResults.verified.txt b/src/Verify.Tests/CombinationTests.EmptyResults.verified.txt new file mode 100644 index 000000000..22fdca1b2 --- /dev/null +++ b/src/Verify.Tests/CombinationTests.EmptyResults.verified.txt @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/src/Verify.Tests/CombinationTests.EmptyResultsNested.verified.txt b/src/Verify.Tests/CombinationTests.EmptyResultsNested.verified.txt new file mode 100644 index 000000000..ab63efffc --- /dev/null +++ b/src/Verify.Tests/CombinationTests.EmptyResultsNested.verified.txt @@ -0,0 +1,4 @@ +{ + results: {}, + after: TheValue +} \ No newline at end of file diff --git a/src/Verify.Tests/CombinationTests.MultiLineArgumentException.verified.txt b/src/Verify.Tests/CombinationTests.MultiLineArgumentException.verified.txt new file mode 100644 index 000000000..62a8b7063 --- /dev/null +++ b/src/Verify.Tests/CombinationTests.MultiLineArgumentException.verified.txt @@ -0,0 +1,4 @@ +{ + 1: ArgumentException: Value cannot be null. Parameter name: p., + 10: ArgumentException: Value cannot be null. Parameter name: p. +} \ No newline at end of file diff --git a/src/Verify.Tests/CombinationTests.cs b/src/Verify.Tests/CombinationTests.cs index e9bebf7c6..962fbf199 100644 --- a/src/Verify.Tests/CombinationTests.cs +++ b/src/Verify.Tests/CombinationTests.cs @@ -159,6 +159,38 @@ public Task RecordingWithExceptionPausedTest() .IgnoreStackTrace(); } + static string ThrowMultiLine(int value) => + throw new ArgumentException( + """ + Value cannot be null. + Parameter name: p + """); + + // ArgumentException messages are flattened onto one line, so the parts of the + // message need a separator between them + [Fact] + public Task MultiLineArgumentException() => + Combination(captureExceptions: true) + .Verify( + ThrowMultiLine, + params1); + + // Only reachable by constructing the results directly, since the runner requires + // every list to have at least one item + [Fact] + public Task EmptyResults() => + Verify(new CombinationResults([], [], null)); + + // an unclosed object would swallow everything written after it + [Fact] + public Task EmptyResultsNested() => + Verify( + new + { + results = new CombinationResults([], [], null), + after = "TheValue" + }); + [Fact] public Task RecordingPausedTest() { diff --git a/src/Verify/Combinations/CombinationResultsConverter.cs b/src/Verify/Combinations/CombinationResultsConverter.cs index 6094398f4..b7d5f1b2e 100644 --- a/src/Verify/Combinations/CombinationResultsConverter.cs +++ b/src/Verify/Combinations/CombinationResultsConverter.cs @@ -10,6 +10,7 @@ public override void Write(VerifyJsonWriter writer, CombinationResults results) var items = results.Items; if (items.Count == 0) { + writer.WriteEndObject(); return; } @@ -175,8 +176,11 @@ static string FlattenMessage(string message) builder.Append(trimmed); if (!trimmed.EndsWith('.')) { - builder.Append(". "); + builder.Append('.'); } + + // separate from the next line even when the sentence was already terminated + builder.Append(' '); } builder.TrimEnd(); diff --git a/src/todo.md b/src/todo.md index 03784f69b..61adbb3dc 100644 --- a/src/todo.md +++ b/src/todo.md @@ -67,10 +67,10 @@ All six resolved 2026-08-16 (five fixed here; the inline item resolved as by-des - [ ] **MSTest source generator ignores `record` test classes.** `Verify.MSTest.SourceGenerator/UsesVerifyGenerator.cs:125-126` — only `ClassDeclarationSyntax` is eligible; `[UsesVerify] [TestClass] partial record` compiles then fails at runtime with the misleading "TestContext is null" error. `Parser.GetParentClasses` similarly stops at a `record struct` parent. -- [ ] **Unclosed JSON object for empty `CombinationResults`.** +- [x] **Unclosed JSON object for empty `CombinationResults`.** `Verify/Combinations/CombinationResultsConverter.cs:8-14` — `WriteStartObject()` then early `return` with no `WriteEndObject()`. Only reachable by constructing `CombinationResults([], ...)` directly. -- [ ] **`FlattenMessage` omits the joining space after a line ending in `.`.** +- [x] **`FlattenMessage` omits the joining space after a line ending in `.`.** `Verify/Combinations/CombinationResultsConverter.cs:168-183` — two-line messages (net48 `ArgumentNullException`) render as `"Value cannot be null.Parameter name: p"`. - [ ] **Negative sub-hour offsets render unsigned.**