diff --git a/docs/guids.md b/docs/guids.md
index 526743de7..16a66b1e8 100644
--- a/docs/guids.md
+++ b/docs/guids.md
@@ -23,7 +23,7 @@ var target = new GuidTarget
await Verify(target);
```
-snippet source | anchor
+snippet source | anchor
Results in the following:
diff --git a/docs/members-throw.md b/docs/members-throw.md
index 1324480da..847c1f8d6 100644
--- a/docs/members-throw.md
+++ b/docs/members-throw.md
@@ -35,7 +35,7 @@ public Task CustomExceptionPropFluent()
.IgnoreMembersThatThrow();
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -45,7 +45,7 @@ Or globally:
```cs
VerifierSettings.IgnoreMembersThatThrow();
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -82,7 +82,7 @@ public Task ExceptionMessagePropFluent()
.IgnoreMembersThatThrow(_ => _.Message == "Ignore");
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -92,7 +92,7 @@ Or globally:
```cs
VerifierSettings.IgnoreMembersThatThrow(_ => _.Message == "Ignore");
```
-snippet source | anchor
+snippet source | anchor
Result:
diff --git a/docs/obsolete-members.md b/docs/obsolete-members.md
index c42a33f81..bd3f9d2bc 100644
--- a/docs/obsolete-members.md
+++ b/docs/obsolete-members.md
@@ -31,7 +31,7 @@ public Task WithObsoleteProp()
return Verify(target);
}
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -79,7 +79,7 @@ public Task WithObsoletePropIncludedFluent()
.IncludeObsoletes();
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -89,7 +89,7 @@ Or globally:
```cs
VerifierSettings.IncludeObsoletes();
```
-snippet source | anchor
+snippet source | anchor
Result:
diff --git a/docs/serializer-settings.md b/docs/serializer-settings.md
index ecf1e3a37..b9134a101 100644
--- a/docs/serializer-settings.md
+++ b/docs/serializer-settings.md
@@ -132,7 +132,7 @@ var settings = new JsonSerializerSettings
DefaultValueHandling = DefaultValueHandling.Ignore
};
```
-snippet source | anchor
+snippet source | anchor
@@ -495,7 +495,7 @@ public Task ScopedSerializerFluent()
.AddExtraSettings(_ => _.TypeNameHandling = TypeNameHandling.All);
}
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -623,7 +623,7 @@ public Task IgnoreTypeFluent()
.IgnoreMembersWithType();
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -633,7 +633,7 @@ Or globally:
```cs
VerifierSettings.IgnoreMembersWithType();
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -770,7 +770,7 @@ public Task ScrubTypeFluent()
.ScrubMembersWithType();
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -780,7 +780,7 @@ Or globally:
```cs
VerifierSettings.ScrubMembersWithType();
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -859,7 +859,7 @@ public Task AddIgnoreInstanceFluent()
.IgnoreInstance(_ => _.Property == "Ignore");
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -869,7 +869,7 @@ Or globally:
```cs
VerifierSettings.IgnoreInstance(_ => _.Property == "Ignore");
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -931,7 +931,7 @@ public Task AddScrubInstanceFluent()
.ScrubInstance(_ => _.Property == "Ignore");
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -941,7 +941,7 @@ Or globally:
```cs
VerifierSettings.ScrubInstance(_ => _.Property == "Ignore");
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1004,7 +1004,7 @@ public Task IgnoreMemberByExpressionFluent()
_ => _.PropertyThatThrows);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally
@@ -1019,7 +1019,7 @@ VerifierSettings.IgnoreMembers(
_ => _.GetOnlyProperty,
_ => _.PropertyThatThrows);
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1079,7 +1079,7 @@ public Task ScrubMemberByExpressionFluent()
_ => _.PropertyThatThrows);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally
@@ -1094,7 +1094,7 @@ VerifierSettings.ScrubMembers(
_ => _.GetOnlyProperty,
_ => _.PropertyThatThrows);
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1173,7 +1173,7 @@ public Task IgnoreMemberByNameFluent()
.IgnoreMember(_ => _.PropertyThatThrows);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -1193,7 +1193,7 @@ VerifierSettings.IgnoreMember("Field");
// For a specific type with expression
VerifierSettings.IgnoreMember(_ => _.PropertyThatThrows);
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1268,7 +1268,7 @@ public Task ScrubMemberByNameFluent()
.ScrubMember(_ => _.PropertyThatThrows);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -1288,7 +1288,7 @@ VerifierSettings.ScrubMember("Field");
// For a specific type with expression
VerifierSettings.ScrubMember(_ => _.PropertyThatThrows);
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1381,7 +1381,7 @@ public Task IgnoreDictionaryByPredicate()
return Verify(target, settings);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -1393,7 +1393,7 @@ VerifierSettings.IgnoreMembers(
_=>_.DeclaringType == typeof(TargetClass) &&
_.Name == "Proprty");
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1482,7 +1482,7 @@ public Task ScrubDictionaryByPredicate()
return Verify(target, settings);
}
```
-snippet source | anchor
+snippet source | anchor
Or globally:
@@ -1494,7 +1494,7 @@ VerifierSettings.ScrubMembers(
_=>_.DeclaringType == typeof(TargetClass) &&
_.Name == "Proprty");
```
-snippet source | anchor
+snippet source | anchor
Result:
@@ -1548,7 +1548,7 @@ public Task MemberConverterByExpression()
return Verify(input);
}
```
-snippet source | anchor
+snippet source | anchor
diff --git a/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMemberGeneric.verified.txt b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMemberGeneric.verified.txt
new file mode 100644
index 000000000..801eed9fe
--- /dev/null
+++ b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMemberGeneric.verified.txt
@@ -0,0 +1,4 @@
+{
+ Value: {Scrubbed},
+ Other: keep
+}
\ No newline at end of file
diff --git a/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersGeneric.verified.txt b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersGeneric.verified.txt
new file mode 100644
index 000000000..801eed9fe
--- /dev/null
+++ b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersGeneric.verified.txt
@@ -0,0 +1,4 @@
+{
+ Value: {Scrubbed},
+ Other: keep
+}
\ No newline at end of file
diff --git a/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersType.verified.txt b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersType.verified.txt
new file mode 100644
index 000000000..801eed9fe
--- /dev/null
+++ b/src/StaticSettingsTests/ScrubMemberGlobalTests.ScrubMembersType.verified.txt
@@ -0,0 +1,4 @@
+{
+ Value: {Scrubbed},
+ Other: keep
+}
\ No newline at end of file
diff --git a/src/StaticSettingsTests/ScrubMemberGlobalTests.cs b/src/StaticSettingsTests/ScrubMemberGlobalTests.cs
new file mode 100644
index 000000000..3090f6cc7
--- /dev/null
+++ b/src/StaticSettingsTests/ScrubMemberGlobalTests.cs
@@ -0,0 +1,33 @@
+public class ScrubMemberGlobalTests :
+ BaseTest
+{
+ // The static ScrubMember(s) overloads previously delegated to Ignore*, so a
+ // member was silently removed instead of rendered as "Scrubbed".
+
+ [Fact]
+ public Task ScrubMemberGeneric()
+ {
+ VerifierSettings.ScrubMember("Value");
+ return Verify(new Target());
+ }
+
+ [Fact]
+ public Task ScrubMembersGeneric()
+ {
+ VerifierSettings.ScrubMembers("Value");
+ return Verify(new Target());
+ }
+
+ [Fact]
+ public Task ScrubMembersType()
+ {
+ VerifierSettings.ScrubMembers(typeof(Target), "Value");
+ return Verify(new Target());
+ }
+
+ public class Target
+ {
+ public string Value { get; set; } = "secret";
+ public string Other { get; set; } = "keep";
+ }
+}
diff --git a/src/Verify.Tests/Serialization/SerializationSettingsCopyTests.cs b/src/Verify.Tests/Serialization/SerializationSettingsCopyTests.cs
new file mode 100644
index 000000000..0838633e0
--- /dev/null
+++ b/src/Verify.Tests/Serialization/SerializationSettingsCopyTests.cs
@@ -0,0 +1,15 @@
+public class SerializationSettingsCopyTests
+{
+ [Fact]
+ public void PreservesOrderDictionaries()
+ {
+ var settings = new SerializationSettings
+ {
+ OrderDictionaries = false
+ };
+
+ var copy = new SerializationSettings(settings);
+
+ Assert.False(copy.OrderDictionaries);
+ }
+}
diff --git a/src/Verify.Tests/Serialization/SerializationTests.MultilineStringInArray.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.MultilineStringInArray.verified.txt
new file mode 100644
index 000000000..44fc82a65
--- /dev/null
+++ b/src/Verify.Tests/Serialization/SerializationTests.MultilineStringInArray.verified.txt
@@ -0,0 +1,5 @@
+[
+ single,
+ multi
+line
+]
\ No newline at end of file
diff --git a/src/Verify.Tests/Serialization/SerializationTests.ScrubNumericIdsUlong.verified.txt b/src/Verify.Tests/Serialization/SerializationTests.ScrubNumericIdsUlong.verified.txt
new file mode 100644
index 000000000..f74382af3
--- /dev/null
+++ b/src/Verify.Tests/Serialization/SerializationTests.ScrubNumericIdsUlong.verified.txt
@@ -0,0 +1,3 @@
+{
+ Id: Id_1
+}
\ No newline at end of file
diff --git a/src/Verify.Tests/Serialization/SerializationTests.cs b/src/Verify.Tests/Serialization/SerializationTests.cs
index c8c5439ad..9a118a855 100644
--- a/src/Verify.Tests/Serialization/SerializationTests.cs
+++ b/src/Verify.Tests/Serialization/SerializationTests.cs
@@ -2227,6 +2227,53 @@ public async Task PartialNamedTuple()
public Task NamedTupleWithNull() =>
VerifyTuple(() => MethodWithNamedTupleWithNull());
+ [Fact]
+ public void NineNamedTupleElements()
+ {
+ // 8+ element tuples nest a Rest ValueTuple, which used to throw the
+ // "all parts are named" error even when every part is named.
+ var dictionary = TupleConverter.ExpressionToDictionary(() => MethodWithNineNamedTuple());
+ Assert.Equal(9, dictionary.Count);
+ Assert.Equal(1, (int) dictionary["a"]!);
+ Assert.Equal(8, (int) dictionary["h"]!);
+ Assert.Equal(9, (int) dictionary["i"]!);
+ }
+
+ static (int a, int b, int c, int d, int e, int f, int g, int h, int i) MethodWithNineNamedTuple() =>
+ (1, 2, 3, 4, 5, 6, 7, 8, 9);
+
+ [Fact]
+ public void NestedNamedTupleThrows()
+ {
+ var exception = Assert.Throws(
+ () => TupleConverter.ExpressionToDictionary(() => MethodWithNestedTuple()));
+ Assert.Contains("Nested", exception.Message);
+ }
+
+ static (int a, (int c, int d) b) MethodWithNestedTuple() =>
+ (1, (2, 3));
+
+ [Fact]
+ public Task MultilineStringInArray()
+ {
+ // The array delimiter must be preserved for a multiline string element.
+ string[] target = ["single", "multi\nline"];
+ return Verify(target);
+ }
+
+ [Fact]
+ public Task ScrubNumericIdsUlong()
+ {
+ // ulong.MaxValue is outside Int64 range; the previous Convert.ToInt64
+ // threw an OverflowException mid-serialization.
+ var target = new
+ {
+ Id = ulong.MaxValue
+ };
+ return Verify(target)
+ .ScrubNumericIds();
+ }
+
[Fact]
public Task Claim() =>
Verify(new Claim("TheType", "TheValue"));
diff --git a/src/Verify/Counter_NumericId.cs b/src/Verify/Counter_NumericId.cs
index 9ce59109c..598764c2c 100644
--- a/src/Verify/Counter_NumericId.cs
+++ b/src/Verify/Counter_NumericId.cs
@@ -2,18 +2,35 @@ namespace VerifyTests;
public partial class Counter
{
- Dictionary> numericIdCache = [];
+ Dictionary> numericIdCache = [];
Dictionary numericIdCounters = [];
public int NextNumericId(string entityName, long input) =>
- NextNumericIdValue(entityName, input)
+ NextNumericIdValue(entityName, input.ToString(CultureInfo.InvariantCulture))
.intValue;
public string NextNumericIdString(string entityName, long input) =>
- NextNumericIdValue(entityName, input)
+ NextNumericIdValue(entityName, input.ToString(CultureInfo.InvariantCulture))
.stringValue;
- (int intValue, string stringValue) NextNumericIdValue(string entityName, long input)
+ // Keys on the invariant string form of the value rather than converting to
+ // long, so ulong/decimal/double ids outside Int64 range (and NaN) don't throw,
+ // and distinct fractional values are not collapsed by rounding.
+ internal string NextNumericIdString(string entityName, object value) =>
+ NextNumericIdValue(entityName, ToKey(value))
+ .stringValue;
+
+ static string ToKey(object value)
+ {
+ if (value is IFormattable formattable)
+ {
+ return formattable.ToString(null, CultureInfo.InvariantCulture);
+ }
+
+ return value.ToString()!;
+ }
+
+ (int intValue, string stringValue) NextNumericIdValue(string entityName, string input)
{
if (!numericIdCache.TryGetValue(entityName, out var cache))
{
diff --git a/src/Verify/Serialization/NumericIdScrubProvider.cs b/src/Verify/Serialization/NumericIdScrubProvider.cs
index acf2e59ee..21edaf067 100644
--- a/src/Verify/Serialization/NumericIdScrubProvider.cs
+++ b/src/Verify/Serialization/NumericIdScrubProvider.cs
@@ -12,7 +12,6 @@ public object GetValue(object target)
return $"{entityName}_null";
}
- var longValue = Convert.ToInt64(value);
- return Counter.Current.NextNumericIdString(entityName, longValue);
+ return Counter.Current.NextNumericIdString(entityName, value);
}
}
diff --git a/src/Verify/Serialization/ReflectionHelpers.cs b/src/Verify/Serialization/ReflectionHelpers.cs
index c8f8a79bd..d0d17a879 100644
--- a/src/Verify/Serialization/ReflectionHelpers.cs
+++ b/src/Verify/Serialization/ReflectionHelpers.cs
@@ -107,7 +107,7 @@ public static bool TryGetCollectionOrDictionary(this object target, [NotNullWhen
}
if (type.FullName != null &&
- type.FullName.StartsWith("System.Linq.ILookup"))
+ type.FullName.StartsWith("System.Linq.ILookup", StringComparison.Ordinal))
{
enumerable = enumerableTarget;
isEmpty = IsEmpty(enumerable);
@@ -117,7 +117,7 @@ public static bool TryGetCollectionOrDictionary(this object target, [NotNullWhen
var interfaces = type.GetInterfaces();
if (interfaces.Any(_ => _.FullName != null &&
- _.FullName.StartsWith("System.Linq.ILookup")))
+ _.FullName.StartsWith("System.Linq.ILookup", StringComparison.Ordinal)))
{
enumerable = enumerableTarget;
isEmpty = IsEmpty(enumerable);
@@ -145,7 +145,7 @@ static bool IsEmpty(IEnumerable enumerable)
}
static bool IsEnumerableEmpty(this Type type) =>
- type.FullName?.StartsWith("System.Linq.EmptyPartition") == true;
+ type.FullName?.StartsWith("System.Linq.EmptyPartition", StringComparison.Ordinal) == true;
public static bool IsGeneric(this Type type, params Type[] generics)
{
@@ -175,12 +175,20 @@ static bool ImplementsGenericCollection(this Type type) =>
typeof(ICollection<>),
typeof(IReadOnlyCollection<>));
+ static ConcurrentDictionary isDefaultOrEmptyGetters = new();
+
static bool IsDefaultOrEmptyImmutableArray(object target)
{
- var targetType = target.GetType();
+ var getter = isDefaultOrEmptyGetters.GetOrAdd(target.GetType(), BuildIsDefaultOrEmptyGetter);
+ return getter != null &&
+ (bool)getter.Invoke(target, null)!;
+ }
+
+ static MethodInfo? BuildIsDefaultOrEmptyGetter(Type targetType)
+ {
if (!targetType.IsGeneric(typeof(ImmutableArray<>)))
{
- return false;
+ return null;
}
var isDefaultOrEmptyProperty = targetType.GetProperty(
@@ -188,7 +196,6 @@ static bool IsDefaultOrEmptyImmutableArray(object target)
bindingAttr: BindingFlags.Public | BindingFlags.Instance)
?? throw new NotSupportedException("There is no IsDefaultOrEmpty property on ImmutableArray.");
- return (bool)isDefaultOrEmptyProperty.GetMethod!.Invoke(target, null)!;
+ return isDefaultOrEmptyProperty.GetMethod!;
}
-
}
diff --git a/src/Verify/Serialization/Scrubbers/ApplyScrubbers.cs b/src/Verify/Serialization/Scrubbers/ApplyScrubbers.cs
index 7084fdbd1..4dba9a4e0 100644
--- a/src/Verify/Serialization/Scrubbers/ApplyScrubbers.cs
+++ b/src/Verify/Serialization/Scrubbers/ApplyScrubbers.cs
@@ -47,6 +47,24 @@ public static void ApplyForExtension(string extension, StringBuilder target, Ver
public static string ApplyForPropertyValue(CharSpan value, VerifySettings settings, Counter counter)
{
+ // Fast path: when nothing can change the value, skip the StringBuilder
+ // round-trip and the directory-replacement scan (this is the hottest loop
+ // in the library — one call per serialized string value).
+ if (!value.Contains('\r'))
+ {
+ if (!settings.ScrubbersEnabled)
+ {
+ return value.ToString();
+ }
+
+ if (settings.InstanceScrubbers == null &&
+ VerifierSettings.GlobalScrubbers.Count == 0 &&
+ value.Length < DirectoryReplacements.ShortestFindLength)
+ {
+ return value.ToString();
+ }
+ }
+
var builder = new StringBuilder(value.Length);
builder.Append(value);
ApplyForPropertyValue(settings, counter, builder);
diff --git a/src/Verify/Serialization/Scrubbers/DirectoryReplacements.cs b/src/Verify/Serialization/Scrubbers/DirectoryReplacements.cs
index 7987b631e..ca22140ae 100644
--- a/src/Verify/Serialization/Scrubbers/DirectoryReplacements.cs
+++ b/src/Verify/Serialization/Scrubbers/DirectoryReplacements.cs
@@ -3,6 +3,11 @@
static partial class DirectoryReplacements
{
static List items = [];
+ static int shortestFindLength = int.MaxValue;
+
+ // Length of the shortest Find, so callers can cheaply skip values that are
+ // too short to contain any replacement. int.MaxValue when there are none.
+ public static int ShortestFindLength => shortestFindLength;
public static void Replace(StringBuilder builder) =>
Replace(builder, items);
@@ -46,6 +51,7 @@ public static void UseAssembly(string? solutionDir, string projectDir)
items = values
.OrderByDescending(_ => _.Find.Length)
.ToList();
+ shortestFindLength = items.Count == 0 ? int.MaxValue : items[^1].Find.Length;
}
static void AddProjectAndSolutionReplacements(string? solutionDir, string projectDir, List replacements)
diff --git a/src/Verify/Serialization/Scrubbers/DirectoryReplacements_StringBuilder.cs b/src/Verify/Serialization/Scrubbers/DirectoryReplacements_StringBuilder.cs
index 752e23dc9..3484f62db 100644
--- a/src/Verify/Serialization/Scrubbers/DirectoryReplacements_StringBuilder.cs
+++ b/src/Verify/Serialization/Scrubbers/DirectoryReplacements_StringBuilder.cs
@@ -56,6 +56,13 @@ static List FindMatches(StringBuilder builder, List pairs)
return [];
}
+ // pairs are ordered by length desc, so the last is the shortest. If the
+ // builder is shorter than that, no pair can match.
+ if (builder.Length < pairs[^1].Find.Length)
+ {
+ return [];
+ }
+
var matches = new List();
// Track matched positions
var matchedRanges = new List<(int Start, int End)>();
diff --git a/src/Verify/Serialization/SerializationSettings.cs b/src/Verify/Serialization/SerializationSettings.cs
index 8acd0abeb..86f9185f6 100644
--- a/src/Verify/Serialization/SerializationSettings.cs
+++ b/src/Verify/Serialization/SerializationSettings.cs
@@ -70,6 +70,7 @@ public SerializationSettings(SerializationSettings settings)
ScrubGuids = settings.ScrubGuids;
ScrubNumericIds = settings.ScrubNumericIds;
+ OrderDictionaries = settings.OrderDictionaries;
includeObsoletes = settings.includeObsoletes;
ignoredMemberPredicatesByString = settings.ignoredMemberPredicatesByString?.Clone();
ignoredMemberPredicatesByMember = settings.ignoredMemberPredicatesByMember?.Clone();
diff --git a/src/Verify/Serialization/TupleConverter.cs b/src/Verify/Serialization/TupleConverter.cs
index 03d77636e..03d1422b3 100644
--- a/src/Verify/Serialization/TupleConverter.cs
+++ b/src/Verify/Serialization/TupleConverter.cs
@@ -7,11 +7,23 @@ static class TupleConverter
var methodCall = (MethodCallExpression) unary.Operand;
var attribute = ReadTupleElementNamesAttribute(methodCall.Method);
var transforms = attribute.TransformNames;
- var dictionary = new Dictionary(transforms.Count);
var result = expression
.Compile()
.Invoke();
- for (var index = 0; index < transforms.Count; index++)
+
+ // For 8+ element tuples TransformNames carries trailing nulls for the
+ // compiler-synthesized Rest slots; the leaf names come first, aligned
+ // with the flattened ITuple indexer. A nested named tuple instead
+ // flattens to more names than the tuple has elements, which cannot be
+ // mapped positionally.
+ var length = result.Length;
+ if (transforms.Count(_ => _ is not null) > length)
+ {
+ throw new("Nested named tuples cannot be used. Use a flat tuple where every part is named.");
+ }
+
+ var dictionary = new Dictionary(length);
+ for (var index = 0; index < length; index++)
{
var transform = transforms[index];
if (transform is null)
diff --git a/src/Verify/Serialization/VerifierSettings_MemberConverter.cs b/src/Verify/Serialization/VerifierSettings_MemberConverter.cs
index a500df149..2f867b32a 100644
--- a/src/Verify/Serialization/VerifierSettings_MemberConverter.cs
+++ b/src/Verify/Serialization/VerifierSettings_MemberConverter.cs
@@ -14,9 +14,9 @@ public static partial class VerifierSettings
{
foreach (var pair in membersConverters)
{
- if (pair.Key.IsAssignableFrom(declaringType))
+ if (pair.Key.IsAssignableFrom(declaringType) &&
+ pair.Value.TryGetValue(name, out var membersConverter))
{
- pair.Value.TryGetValue(name, out var membersConverter);
return membersConverter;
}
}
diff --git a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs
index 3d3f02bee..1c9c051dc 100644
--- a/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs
+++ b/src/Verify/Serialization/VerifierSettings_SerializationMaps.cs
@@ -109,7 +109,7 @@ public static void ScrubMembers(params string[] names)
where T : notnull
{
InnerVerifier.ThrowIfVerifyHasBeenRun();
- serialization.IgnoreMembers(names);
+ serialization.ScrubMembers(names);
}
public static void IgnoreMember(string name)
@@ -123,7 +123,7 @@ public static void ScrubMember(string name)
where T : notnull
{
InnerVerifier.ThrowIfVerifyHasBeenRun();
- serialization.IgnoreMember(name);
+ serialization.ScrubMember(name);
}
public static void IgnoreMembers(Type declaringType, params string[] names)
@@ -135,7 +135,7 @@ public static void IgnoreMembers(Type declaringType, params string[] names)
public static void ScrubMembers(Type declaringType, params string[] names)
{
InnerVerifier.ThrowIfVerifyHasBeenRun();
- serialization.IgnoreMembers(declaringType, names);
+ serialization.ScrubMembers(declaringType, names);
}
public static void IgnoreMember(Type declaringType, string name)
diff --git a/src/Verify/Serialization/VerifyJsonWriter.cs b/src/Verify/Serialization/VerifyJsonWriter.cs
index c6c892d27..e4bbe941e 100644
--- a/src/Verify/Serialization/VerifyJsonWriter.cs
+++ b/src/Verify/Serialization/VerifyJsonWriter.cs
@@ -130,6 +130,15 @@ public override void WriteValue(CharSpan value)
if (value.Contains('\n'))
{
+ if (WriteState != Argon.WriteState.Property)
+ {
+ // In array (or any non-property) position the value already sits
+ // on its own indented line; removing the auto-completed char here
+ // would eat the ',' delimiter between items.
+ WriteRawValue(value);
+ return;
+ }
+
base.Flush();
var builderLength = builder.Length;
if (value[0] != '\n')