From 9f4549dd11782520468fc1379be983b7bd3fee2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Wed, 29 Jul 2026 14:54:58 +0200 Subject: [PATCH 1/2] feat!: narrow indexer surfaces with more than four keys The accessor narrowing introduced for indexers (#830) stopped at four keys: the narrowed setup/verify types only ship with the library for arities 1-4, so a five-key get-only indexer still offered OnSet and Verify[...].Set(...), which compiled and silently did nothing. Since the setup types for indexers with more than four keys are generated per-compilation anyway, generate the narrowed surface there too: - IndexerSetups.g.cs now emits IIndexerGetterOnlySetup / IIndexerSetterOnlySetup interface hierarchies (builders included) for each affected arity, implements them on the generated IndexerSetup class, and adds the Forever/OnlyOnce extensions for the narrowed when-builders. - Narrowed VerificationIndexerGetterResult / -SetterResult classes are generated in Mockolate.Verify, dispatching through the predicate-based MockRegistry.IndexerGot/IndexerSet overloads that the full VerificationIndexerResult already uses above four keys. Each takes only the member id and predicate of the accessor it exposes. - The facade selection in the mock emission now uses the plain accessor classification for every arity; the narrowed hierarchies are only generated for arities that need them, keyed on the same Getter/Setter nullity that guards body emission. BREAKING CHANGE: on an indexer with more than four keys that the mock reads or writes but not both, Setup[...].OnSet/OnGet, Setup[...].Returns/Throws/InitializeWith and Verify[...].Set(...)/Got() no longer compile for the absent accessor. Each previously compiled and silently had no effect. This closes the gap the four-key narrowing left; the shipped library API is unchanged. --- Docs/pages/setup/03-indexers.md | 3 +- .../MockGenerator.cs | 65 +- .../Sources/Sources.IndexerSetups.cs | 711 +++++++++++++++++- .../Sources/Sources.MockClass.cs | 104 ++- .../MockTests.ClassTests.IndexerTests.cs | 50 +- .../IndexerSetups.g.cs | 565 +++++++++++++- .../Mock.IComprehensiveInterface.g.cs | 385 +++++++++- .../IComprehensiveInterface.cs | 4 +- ...tupIndexerTests.AccessorRestrictedTests.cs | 51 ++ 9 files changed, 1789 insertions(+), 149 deletions(-) diff --git a/Docs/pages/setup/03-indexers.md b/Docs/pages/setup/03-indexers.md index 2fca4875..4a9ad5ad 100644 --- a/Docs/pages/setup/03-indexers.md +++ b/Docs/pages/setup/03-indexers.md @@ -119,7 +119,8 @@ interactions. See [Mockolate0002](../analyzers#mockolate0002) for when such a ty Both facades are fully restricted: the fluent builders returned by `Returns`, `Throws`, `Do` and `TransitionTo` stay on the narrowed surface, so no amount of chaining reaches the accessor the mock -does not intercept. Indexers with more than four keys keep the full surface. +does not intercept. This applies to any number of keys: up to four keys the narrowed types ship with +the library, for more keys they are generated per-compilation. **Notes:** diff --git a/Source/Mockolate.SourceGenerators/MockGenerator.cs b/Source/Mockolate.SourceGenerators/MockGenerator.cs index 2e9a077e..cec5c56b 100644 --- a/Source/Mockolate.SourceGenerators/MockGenerator.cs +++ b/Source/Mockolate.SourceGenerators/MockGenerator.cs @@ -77,17 +77,27 @@ void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext }); // Aggregate inputs derived from per-mock projections — each aggregate caches independently. - IncrementalValueProvider> indexerSetupArities = collectedMocks - .Select(static (arr, _) => CollectIndexerSetupArities(arr)); + IncrementalValueProvider> indexerSetupKeys = collectedMocks + .Select(static (arr, _) => CollectIndexerSetupKeys(arr)); - context.RegisterSourceOutput(indexerSetupArities, static (spc, arities) => - { - if (arities.Count > 0) + context.RegisterSourceOutput( + indexerSetupKeys.Combine(hasOverloadResolutionPriority), + static (spc, source) => { + if (source.Left.Count == 0) + { + return; + } + + Dictionary indexerSetups = new(); + foreach (IndexerSetupKey key in source.Left) + { + indexerSetups[key.Arity] = (key.NeedsGetterOnly, key.NeedsSetterOnly); + } + spc.AddSource("IndexerSetups.g.cs", - ToSource(Sources.Sources.IndexerSetups(ToHashSet(arities)))); - } - }); + ToSource(Sources.Sources.IndexerSetups(indexerSetups, source.Right))); + }); IncrementalValueProvider> methodSetupKeys = collectedMocks .Select(static (arr, _) => CollectMethodSetupKeys(arr)); @@ -212,18 +222,6 @@ private static EquatableArray Distinct(ImmutableArray mock return new EquatableArray(distinct.ToArray()); } - private static HashSet ToHashSet(EquatableArray arities) - { - int[] arr = arities.AsArray(); - HashSet set = new(); - foreach (int item in arr) - { - set.Add(item); - } - - return set; - } - private static HashSet<(int, bool)> ToMethodSetupHashSet(EquatableArray keys) { MethodSetupKey[] arr = keys.AsArray(); @@ -236,24 +234,37 @@ private static HashSet ToHashSet(EquatableArray arities) return set; } - private static EquatableArray CollectIndexerSetupArities(EquatableArray mocks) + private static EquatableArray CollectIndexerSetupKeys(EquatableArray mocks) { MockClass[] arr = mocks.AsArray(); - HashSet set = new(); + Dictionary map = new(); foreach (MockClass mc in arr) { foreach (Property property in mc.AllProperties()) { if (property.IndexerParameters?.Count > 4) { - set.Add(property.IndexerParameters.Value.Count); + int arity = property.IndexerParameters.Value.Count; + bool needsGetterOnly = property is { Getter: not null, Setter: null, }; + bool needsSetterOnly = property is { Getter: null, Setter: not null, }; + if (map.TryGetValue(arity, out (bool NeedsGetterOnly, bool NeedsSetterOnly) existing)) + { + map[arity] = (existing.NeedsGetterOnly || needsGetterOnly, + existing.NeedsSetterOnly || needsSetterOnly); + } + else + { + map[arity] = (needsGetterOnly, needsSetterOnly); + } } } } - int[] sorted = set.ToArray(); - Array.Sort(sorted); - return new EquatableArray(sorted); + IndexerSetupKey[] sorted = map + .OrderBy(kvp => kvp.Key) + .Select(kvp => new IndexerSetupKey(kvp.Key, kvp.Value.NeedsGetterOnly, kvp.Value.NeedsSetterOnly)) + .ToArray(); + return new EquatableArray(sorted); } private static EquatableArray CollectMethodSetupKeys(EquatableArray mocks) @@ -544,6 +555,8 @@ private static bool IsValidMockDeclaration(MockClass mockClass) internal readonly record struct RefStructIndexerSetup(int Arity, bool HasGetter, bool HasSetter); +internal readonly record struct IndexerSetupKey(int Arity, bool NeedsGetterOnly, bool NeedsSetterOnly); + internal readonly record struct MockAsExtensionPair( string SourceName, string SourceFullName, diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs index 2b113bbf..1148712b 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs @@ -4,7 +4,9 @@ namespace Mockolate.SourceGenerators.Sources; internal static partial class Sources { - public static string IndexerSetups(HashSet indexerSetups) + public static string IndexerSetups( + Dictionary indexerSetups, + bool hasOverloadResolutionPriority) { StringBuilder sb = InitializeBuilder(); @@ -14,10 +16,19 @@ public static string IndexerSetups(HashSet indexerSetups) namespace Mockolate.Setup { """); - foreach (int item in indexerSetups) + foreach (KeyValuePair item in indexerSetups) { sb.AppendLine(); - AppendIndexerSetup(sb, item); + AppendIndexerSetup(sb, item.Key, item.Value.NeedsGetterOnly, item.Value.NeedsSetterOnly); + if (item.Value.NeedsGetterOnly) + { + AppendGetterOnlyIndexerInterfaces(sb, item.Key); + } + + if (item.Value.NeedsSetterOnly) + { + AppendSetterOnlyIndexerInterfaces(sb, item.Key); + } } sb.AppendLine(); @@ -35,8 +46,9 @@ namespace Mockolate internal static class IndexerSetupExtensions { """).AppendLine(); - foreach (int item in indexerSetups) + foreach (KeyValuePair setup in indexerSetups) { + int item = setup.Key; string types = GetGenericTypeParameters(item); sb.Append($$""" /// @@ -83,6 +95,62 @@ public void Forever() => setup.Only(1); } """).AppendLine(); + + if (setup.Value.NeedsGetterOnly) + { + sb.AppendLine(); + sb.Append($$""" + /// + /// Extensions for setups of get-only indexers with {{item}} parameters. + /// + extension(Mockolate.Setup.IIndexerGetterOnlySetupReturnWhenBuilder setup) + { + /// + /// Returns/throws forever. + /// + public void Forever() + { + setup.For(int.MaxValue); + } + + /// + /// Uses the return value only once. + /// + public global::Mockolate.Setup.IIndexerGetterOnlySetup OnlyOnce() + => setup.Only(1); + } + + /// + /// Extensions for getter callback setups of get-only indexers with {{item}} parameters. + /// + extension(Mockolate.Setup.IIndexerGetterOnlySetupCallbackWhenBuilder setup) + { + /// + /// Executes the callback only once. + /// + public global::Mockolate.Setup.IIndexerGetterOnlySetup OnlyOnce() + => setup.Only(1); + } + """).AppendLine(); + } + + if (setup.Value.NeedsSetterOnly) + { + sb.AppendLine(); + sb.Append($$""" + /// + /// Extensions for setter callback setups of set-only indexers with {{item}} parameters. + /// + extension(Mockolate.Setup.IIndexerSetterOnlySetupCallbackWhenBuilder setup) + { + /// + /// Executes the callback only once. + /// + public global::Mockolate.Setup.IIndexerSetterOnlySetup OnlyOnce() + => setup.Only(1); + } + """).AppendLine(); + } } sb.Append(""" @@ -91,7 +159,7 @@ public void Forever() """).AppendLine(); sb.Append("namespace Mockolate.Interactions").AppendLine(); sb.Append("{").AppendLine(); - foreach (int count in indexerSetups) + foreach (int count in indexerSetups.Keys) { AppendIndexerGetterAccess(sb, count); AppendIndexerSetterAccess(sb, count); @@ -100,6 +168,27 @@ public void Forever() sb.Append("}").AppendLine(); sb.AppendLine(); + if (indexerSetups.Values.Any(v => v.NeedsGetterOnly || v.NeedsSetterOnly)) + { + sb.Append("namespace Mockolate.Verify").AppendLine(); + sb.Append("{").AppendLine(); + foreach (KeyValuePair item in indexerSetups) + { + if (item.Value.NeedsGetterOnly) + { + AppendIndexerVerifyGetterResult(sb, item.Key); + } + + if (item.Value.NeedsSetterOnly) + { + AppendIndexerVerifySetterResult(sb, item.Key, hasOverloadResolutionPriority); + } + } + + sb.Append("}").AppendLine(); + sb.AppendLine(); + } + sb.AppendLine("#nullable disable"); return sb.ToString(); } @@ -209,7 +298,8 @@ private static void AppendParameterHooks(StringBuilder sb, int numberOfParameter sb.Append("\t\t}").AppendLine(); } - private static void AppendIndexerSetup(StringBuilder sb, int numberOfParameters) + private static void AppendIndexerSetup(StringBuilder sb, int numberOfParameters, + bool needsGetterOnly, bool needsSetterOnly) { string typeParams = GetGenericTypeParameters(numberOfParameters); string outTypeParams = GetOutGenericTypeParameters(numberOfParameters); @@ -472,7 +562,25 @@ private static void AppendIndexerSetup(StringBuilder sb, int numberOfParameters) sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterSetupCallbackBuilder,").AppendLine(); sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetupReturnBuilder,").AppendLine(); sb.Append("\t\tglobal::Mockolate.Setup.IIndexerGetterSetupWithCallback,").AppendLine(); - sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterSetupWithCallback").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterSetupWithCallback"); + if (needsGetterOnly) + { + sb.Append(",").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerGetterOnlySetup,").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerGetterOnlyGetterSetup,").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerGetterOnlySetupCallbackBuilder,").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerGetterOnlySetupReturnBuilder"); + } + + if (needsSetterOnly) + { + sb.Append(",").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterOnlySetup,").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterOnlySetterSetup,").AppendLine(); + sb.Append("\t\tglobal::Mockolate.Setup.IIndexerSetterOnlySetupCallbackBuilder"); + } + + sb.AppendLine(); sb.Append("\t{").AppendLine(); sb.Append("\t\tprivate Callbacks>? _getterCallbacks;") @@ -1183,6 +1291,595 @@ private static void AppendIndexerSetup(StringBuilder sb, int numberOfParameters) Enumerable.Range(1, numberOfParameters).Select(i => $"{{parameter{i}}}"))).Append("]\";").AppendLine(); sb.AppendLine(); + if (needsGetterOnly) + { + AppendGetterOnlyIndexerImplementation(sb, numberOfParameters); + } + + if (needsSetterOnly) + { + AppendSetterOnlyIndexerImplementation(sb, numberOfParameters); + } + sb.Append("\t}").AppendLine(); } + + private static void AppendGetterOnlyIndexerInterfaces(StringBuilder sb, int numberOfParameters) + { + string tp = GetGenericTypeParameters(numberOfParameters); + string outTp = GetOutGenericTypeParameters(numberOfParameters); + string description = GetTypeParametersDescription(numberOfParameters); + + sb.AppendLine(); + sb.Append($$""" + /// + /// Setup for a mocked indexer for {{description}} that the mock only reads. + /// + /// + /// Used instead of when the mock has no setter to intercept, either + /// because the indexer is declared without one or because its setter is not accessible from the mock's assembly. + /// Writes then never reach the mock, so is not offered. + /// + internal interface IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlyGetterSetup OnGet { get; } + + /// + IIndexerGetterOnlySetup SkippingBaseClass(bool skipBaseClass = true); + + /// + /// + /// Seeds the value that reads return. Unlike a read-write indexer there is no setter to update the + /// slot afterwards, so it stays at unless a Returns entry applies. + /// + IIndexerGetterOnlySetup InitializeWith(TValue value); + + /// + IIndexerGetterOnlySetup InitializeWith(global::System.Func<{{tp}}, TValue> valueGenerator); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(TValue returnValue); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func<{{tp}}, TValue> callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func<{{tp}}, TValue, TValue> callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws() + where TException : global::System.Exception, new(); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Exception exception); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func<{{tp}}, global::System.Exception> callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func<{{tp}}, TValue, global::System.Exception> callback); + } + + /// + /// Setup for attaching side-effects to the getter of a get-only indexer for {{description}}. + /// + /// + /// The counterpart of for + /// : the returned builders stay on the getter-only surface, + /// so chaining can never reach . + /// + internal interface IIndexerGetterOnlyGetterSetup + { + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action<{{tp}}> callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action<{{tp}}, TValue> callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder TransitionTo(string scenario); + } + + /// + /// Sets up a callback for a get-only indexer for {{description}}. + /// + internal interface IIndexerGetterOnlySetupCallbackBuilder + : IIndexerGetterOnlySetupParallelCallbackBuilder + { + /// + IIndexerGetterOnlySetupParallelCallbackBuilder InParallel(); + } + + /// + /// Sets up a parallel callback for a get-only indexer for {{description}}. + /// + internal interface IIndexerGetterOnlySetupParallelCallbackBuilder + : IIndexerGetterOnlySetupCallbackWhenBuilder + { + /// + IIndexerGetterOnlySetupCallbackWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when callback for a get-only indexer for {{description}}. + /// + internal interface IIndexerGetterOnlySetupCallbackWhenBuilder + : IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlySetupCallbackWhenBuilder For(int times); + + /// + IIndexerGetterOnlySetup Only(int times); + } + + /// + /// Sets up a return/throw builder for a get-only indexer for {{description}}. + /// + internal interface IIndexerGetterOnlySetupReturnBuilder + : IIndexerGetterOnlySetupReturnWhenBuilder + { + /// + IIndexerGetterOnlySetupReturnWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when builder for returns/throws for a get-only indexer for {{description}}. + /// + internal interface IIndexerGetterOnlySetupReturnWhenBuilder + : IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlySetupReturnWhenBuilder For(int times); + + /// + IIndexerGetterOnlySetup Only(int times); + } + """).AppendLine(); + } + + private static void AppendSetterOnlyIndexerInterfaces(StringBuilder sb, int numberOfParameters) + { + string tp = GetGenericTypeParameters(numberOfParameters); + string outTp = GetOutGenericTypeParameters(numberOfParameters); + string description = GetTypeParametersDescription(numberOfParameters); + + sb.AppendLine(); + sb.Append($$""" + /// + /// Setup for a mocked indexer for {{description}} that the mock only writes. + /// + /// + /// The write-only counterpart of : the mock has no getter to + /// intercept, so , InitializeWith and the + /// Returns/Throws read-sequence are not offered. + /// + internal interface IIndexerSetterOnlySetup + { + /// + IIndexerSetterOnlySetterSetup OnSet { get; } + + /// + IIndexerSetterOnlySetup SkippingBaseClass(bool skipBaseClass = true); + } + + /// + /// Setup for attaching side-effects to the setter of a set-only indexer for {{description}}. + /// + /// + /// The counterpart of for + /// : the returned builders stay on the setter-only surface, + /// so chaining can never reach or the + /// Returns/Throws read-sequence. + /// + internal interface IIndexerSetterOnlySetterSetup + { + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action<{{tp}}, TValue> callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder TransitionTo(string scenario); + } + + /// + /// Sets up a setter callback for a set-only indexer for {{description}}. + /// + internal interface IIndexerSetterOnlySetupCallbackBuilder + : IIndexerSetterOnlySetupParallelCallbackBuilder + { + /// + IIndexerSetterOnlySetupParallelCallbackBuilder InParallel(); + } + + /// + /// Sets up a parallel setter callback for a set-only indexer for {{description}}. + /// + internal interface IIndexerSetterOnlySetupParallelCallbackBuilder + : IIndexerSetterOnlySetupCallbackWhenBuilder + { + /// + IIndexerSetterOnlySetupCallbackWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when setter callback for a set-only indexer for {{description}}. + /// + internal interface IIndexerSetterOnlySetupCallbackWhenBuilder + : IIndexerSetterOnlySetup + { + /// + IIndexerSetterOnlySetupCallbackWhenBuilder For(int times); + + /// + IIndexerSetterOnlySetup Only(int times); + } + """).AppendLine(); + } + + private static void AppendGetterOnlyIndexerImplementation(StringBuilder sb, int numberOfParameters) + { + string tp = GetGenericTypeParameters(numberOfParameters); + + sb.Append($$""" + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.SkippingBaseClass(bool skipBaseClass) + { + SkippingBaseClass(skipBaseClass); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.InitializeWith(TValue value) + { + InitializeWith(value); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.InitializeWith(global::System.Func<{{tp}}, TValue> valueGenerator) + { + InitializeWith(valueGenerator); + return this; + } + + /// + IIndexerGetterOnlyGetterSetup IIndexerGetterOnlySetup.OnGet + => this; + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action<{{tp}}> callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action<{{tp}}, TValue> callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder IIndexerGetterOnlyGetterSetup.TransitionTo(string scenario) + { + ((IIndexerGetterSetup)this).TransitionTo(scenario); + return this; + } + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder IIndexerGetterOnlySetupCallbackBuilder.InParallel() + { + ((IIndexerGetterSetupCallbackBuilder)this).InParallel(); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackWhenBuilder IIndexerGetterOnlySetupParallelCallbackBuilder.When(global::System.Func predicate) + { + ((IIndexerGetterSetupParallelCallbackBuilder)this).When(predicate); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackWhenBuilder IIndexerGetterOnlySetupCallbackWhenBuilder.For(int times) + { + ((IIndexerGetterSetupCallbackWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetupCallbackWhenBuilder.Only(int times) + { + ((IIndexerGetterSetupCallbackWhenBuilder)this).Only(times); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(TValue returnValue) + { + Returns(returnValue); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func<{{tp}}, TValue> callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func<{{tp}}, TValue, TValue> callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws() + { + Throws(); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Exception exception) + { + Throws(exception); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func<{{tp}}, global::System.Exception> callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func<{{tp}}, TValue, global::System.Exception> callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnWhenBuilder IIndexerGetterOnlySetupReturnBuilder.When(global::System.Func predicate) + { + ((IIndexerSetupReturnBuilder)this).When(predicate); + return this; + } + + /// + IIndexerGetterOnlySetupReturnWhenBuilder IIndexerGetterOnlySetupReturnWhenBuilder.For(int times) + { + ((IIndexerSetupReturnWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetupReturnWhenBuilder.Only(int times) + { + ((IIndexerSetupReturnWhenBuilder)this).Only(times); + return this; + } + + """).AppendLine(); + } + + private static void AppendSetterOnlyIndexerImplementation(StringBuilder sb, int numberOfParameters) + { + string tp = GetGenericTypeParameters(numberOfParameters); + + sb.Append($$""" + /// + IIndexerSetterOnlySetup IIndexerSetterOnlySetup.SkippingBaseClass(bool skipBaseClass) + { + SkippingBaseClass(skipBaseClass); + return this; + } + + /// + IIndexerSetterOnlySetterSetup IIndexerSetterOnlySetup.OnSet + => this; + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action<{{tp}}, TValue> callback) + { + ((IIndexerSetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder IIndexerSetterOnlySetterSetup.TransitionTo(string scenario) + { + ((IIndexerSetterSetup)this).TransitionTo(scenario); + return this; + } + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder IIndexerSetterOnlySetupCallbackBuilder.InParallel() + { + ((IIndexerSetterSetupCallbackBuilder)this).InParallel(); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackWhenBuilder IIndexerSetterOnlySetupParallelCallbackBuilder.When(global::System.Func predicate) + { + ((IIndexerSetterSetupParallelCallbackBuilder)this).When(predicate); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackWhenBuilder IIndexerSetterOnlySetupCallbackWhenBuilder.For(int times) + { + ((IIndexerSetterSetupCallbackWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerSetterOnlySetup IIndexerSetterOnlySetupCallbackWhenBuilder.Only(int times) + { + ((IIndexerSetterSetupCallbackWhenBuilder)this).Only(times); + return this; + } + + """).AppendLine(); + } + + private static void AppendIndexerVerifyGetterResult(StringBuilder sb, int numberOfParameters) + { + string tp = GetGenericTypeParameters(numberOfParameters); + string description = GetTypeParametersDescription(numberOfParameters); + + sb.Append($$""" + /// + /// Verifications on a {{numberOfParameters}}-key indexer for {{description}} that the mock only reads. + /// + /// + /// Used instead of when the + /// mock has no setter to intercept. Writes then never reach the mock, so offering Set(...) here would + /// always report zero interactions. + /// + """).AppendLine(); +#if !DEBUG + sb.Append("\t[global::System.Diagnostics.DebuggerNonUserCode]").AppendLine(); +#endif + sb.Append("\t[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]").AppendLine(); + sb.Append($$""" + internal class VerificationIndexerGetterResult( + TSubject subject, + global::Mockolate.MockRegistry mockRegistry, + int getMemberId, + global::System.Func gotPredicate, + global::System.Func parametersDescription) + { + /// + public global::Mockolate.Verify.VerificationResult Got() + => mockRegistry.IndexerGot(subject, getMemberId, gotPredicate, parametersDescription); + } + """).AppendLine(); + } + + private static void AppendIndexerVerifySetterResult(StringBuilder sb, int numberOfParameters, + bool hasOverloadResolutionPriority) + { + string tp = GetGenericTypeParameters(numberOfParameters); + string description = GetTypeParametersDescription(numberOfParameters); + + sb.Append($$""" + /// + /// Verifications on a {{numberOfParameters}}-key indexer of type for {{description}} that the mock only writes. + /// + /// + /// Used instead of when the + /// mock has no getter to intercept, so Got() is not offered. + /// + """).AppendLine(); +#if !DEBUG + sb.Append("\t[global::System.Diagnostics.DebuggerNonUserCode]").AppendLine(); +#endif + sb.Append("\t[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]").AppendLine(); + sb.Append($$""" + internal class VerificationIndexerSetterResult( + TSubject subject, + global::Mockolate.MockRegistry mockRegistry, + int setMemberId, + global::System.Func, bool> setPredicate, + global::System.Func parametersDescription) + { + /// + public global::Mockolate.Verify.VerificationResult Set(global::Mockolate.Parameters.IParameter value) + => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, + (global::Mockolate.Parameters.IParameterMatch)value, parametersDescription); + + """).AppendLine(); + if (hasOverloadResolutionPriority) + { + sb.Append("\t\t[global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)]").AppendLine(); + } + + sb.Append($$""" + /// + /// Verifies the indexer write access on the mock with the given . + /// + public global::Mockolate.Verify.VerificationResult Set(TParameter value) + => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, + (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.Is(value, value?.ToString() ?? "null"), parametersDescription); + } + """).AppendLine(); + } } diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs index e998058d..3448b692 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.MockClass.cs @@ -3190,23 +3190,14 @@ private static string GetPropertyVerifyType(Property property, string verifyName _ => $"global::Mockolate.Verify.VerificationPropertyResult<{verifyName}, {property.Type.Fullname}>", }; - /// - /// Which accessors of the the narrowed setup/verify facades follow. The - /// narrowed indexer types only exist for up to keys, so indexers - /// with more keys keep the full surface regardless of . - /// - private static PropertyAccessors GetIndexerInterceptedAccessors(Property indexer) - => indexer.IndexerParameters!.Value.Count <= MaxExplicitParameters - ? GetInterceptedAccessors(indexer) - : PropertyAccessors.Both; - /// /// The open-generic setup facade emitted for the , narrowed to the accessors - /// classified by . The caller appends the value and key type - /// arguments and the closing angle bracket. + /// classified by . The narrowed types are shipped for up to + /// keys and generated per-compilation for more. The caller appends + /// the value and key type arguments and the closing angle bracket. /// private static string GetIndexerSetupType(Property indexer) - => GetIndexerInterceptedAccessors(indexer) switch + => GetInterceptedAccessors(indexer) switch { PropertyAccessors.GetOnly => "global::Mockolate.Setup.IIndexerGetterOnlySetup<", PropertyAccessors.SetOnly => "global::Mockolate.Setup.IIndexerSetterOnlySetup<", @@ -3215,13 +3206,13 @@ private static string GetIndexerSetupType(Property indexer) /// /// Appends the verify facade emitted for the , narrowed to the accessors - /// classified by . The narrowed results are keyed by the + /// classified by . The narrowed results are keyed by the /// indexer parameters (the setter result additionally by the value type), the full /// VerificationIndexerResult only by the value type. /// private static void AppendIndexerVerifyType(StringBuilder sb, Property indexer, string verifyName) { - switch (GetIndexerInterceptedAccessors(indexer)) + switch (GetInterceptedAccessors(indexer)) { case PropertyAccessors.GetOnly: sb.Append("global::Mockolate.Verify.VerificationIndexerGetterResult<").Append(verifyName); @@ -4827,7 +4818,7 @@ private static void AppendIndexerVerifyImplementation(StringBuilder sb, Property bool useTypedVerify = indexer.IndexerParameters.Value.Count is >= 1 and <= 4; if (useTypedVerify) { - PropertyAccessors interceptedAccessors = GetIndexerInterceptedAccessors(indexer); + PropertyAccessors interceptedAccessors = GetInterceptedAccessors(indexer); string typedVerifyType = interceptedAccessors switch { PropertyAccessors.GetOnly => "VerificationIndexerGetterResult", @@ -4861,39 +4852,72 @@ private static void AppendIndexerVerifyImplementation(StringBuilder sb, Property } else { - sb.Append("\t\t\t\treturn new global::Mockolate.Verify.VerificationIndexerResult<").Append(verifyName) - .Append(", ").AppendTypeOrWrapper(indexer.Type).Append(">(this, this.").Append(mockRegistryName) - .Append(", ").Append(indexerGetMemberId).Append(", ").Append(indexerSetMemberId).Append(",").AppendLine(); + PropertyAccessors interceptedAccessors = GetInterceptedAccessors(indexer); + // The narrow views take only the member id and predicate of the accessor they expose. + switch (interceptedAccessors) + { + case PropertyAccessors.GetOnly: + sb.Append("\t\t\t\treturn new global::Mockolate.Verify.VerificationIndexerGetterResult<") + .Append(verifyName); + foreach (MethodParameter parameter in indexer.IndexerParameters.Value) + { + sb.Append(", ").AppendTypeOrWrapper(parameter.Type); + } - sb.Append("\t\t\t\t\tinteraction => interaction is global::Mockolate.Interactions.IndexerGetterAccess<"); - int ti = 0; - foreach (MethodParameter parameter in indexer.IndexerParameters.Value) + sb.Append(">(this, this.").Append(mockRegistryName) + .Append(", ").Append(indexerGetMemberId).Append(",").AppendLine(); + break; + case PropertyAccessors.SetOnly: + sb.Append("\t\t\t\treturn new global::Mockolate.Verify.VerificationIndexerSetterResult<") + .Append(verifyName); + foreach (MethodParameter parameter in indexer.IndexerParameters.Value) + { + sb.Append(", ").AppendTypeOrWrapper(parameter.Type); + } + + sb.Append(", ").AppendTypeOrWrapper(indexer.Type).Append(">(this, this.").Append(mockRegistryName) + .Append(", ").Append(indexerSetMemberId).Append(",").AppendLine(); + break; + default: + sb.Append("\t\t\t\treturn new global::Mockolate.Verify.VerificationIndexerResult<").Append(verifyName) + .Append(", ").AppendTypeOrWrapper(indexer.Type).Append(">(this, this.").Append(mockRegistryName) + .Append(", ").Append(indexerGetMemberId).Append(", ").Append(indexerSetMemberId).Append(",").AppendLine(); + break; + } + + if (interceptedAccessors != PropertyAccessors.SetOnly) { - if (ti > 0) + sb.Append("\t\t\t\t\tinteraction => interaction is global::Mockolate.Interactions.IndexerGetterAccess<"); + int ti = 0; + foreach (MethodParameter parameter in indexer.IndexerParameters.Value) { - sb.Append(", "); + if (ti > 0) + { + sb.Append(", "); + } + + sb.AppendTypeOrWrapper(parameter.Type); + ti++; } - sb.AppendTypeOrWrapper(parameter.Type); - ti++; + sb.Append("> g"); + AppendIndexerVerifyParameterMatches(sb, indexer.IndexerParameters.Value, valueFlags, "g"); + sb.Append(",").AppendLine(); } - sb.Append("> g"); - AppendIndexerVerifyParameterMatches(sb, indexer.IndexerParameters.Value, valueFlags, "g"); - sb.Append(",").AppendLine(); - - sb.Append( - "\t\t\t\t\t(interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess<"); - ti = 0; - foreach (MethodParameter parameter in indexer.IndexerParameters.Value) + if (interceptedAccessors != PropertyAccessors.GetOnly) { - sb.AppendTypeOrWrapper(parameter.Type).Append(", "); - ti++; - } + sb.Append( + "\t\t\t\t\t(interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess<"); + foreach (MethodParameter parameter in indexer.IndexerParameters.Value) + { + sb.AppendTypeOrWrapper(parameter.Type).Append(", "); + } - sb.AppendTypeOrWrapper(indexer.Type).Append("> s"); - AppendIndexerVerifyParameterMatches(sb, indexer.IndexerParameters.Value, valueFlags, "s"); - sb.Append(" && value.Matches(s.TypedValue),").AppendLine(); + sb.AppendTypeOrWrapper(indexer.Type).Append("> s"); + AppendIndexerVerifyParameterMatches(sb, indexer.IndexerParameters.Value, valueFlags, "s"); + sb.Append(" && value.Matches(s.TypedValue),").AppendLine(); + } } sb.Append("\t\t\t\t\t() => global::System.String.Format(\"["); diff --git a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs index fa6f49ab..2d627512 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/MockTests.ClassTests.IndexerTests.cs @@ -32,6 +32,21 @@ public sealed class IndexerTests "Verify.VerificationIndexerSetterResult", "Setup.IndexerSetup", "Verify.VerificationIndexerResult")] + [InlineData("string this[int a, int b, int c, int d, int e] { get; }", + "Setup.IIndexerGetterOnlySetup", + "Verify.VerificationIndexerGetterResult", + "Setup.IndexerSetup", + "Verify.VerificationIndexerResult")] + [InlineData("string this[int a, int b, int c, int d, int e] { set; }", + "Setup.IIndexerSetterOnlySetup", + "Verify.VerificationIndexerSetterResult", + "Setup.IndexerSetup", + "Verify.VerificationIndexerResult")] + [InlineData("string this[int a, int b, int c, int d, int e] { get; set; }", + "Setup.IndexerSetup", + "Verify.VerificationIndexerResult", + "Setup.IIndexerGetterOnlySetup", + "Verify.VerificationIndexerGetterResult")] public async Task IndexerSurface_ShouldOnlyExposeTheAccessorsTheMockIntercepts( string indexer, string expectedSetupType, string expectedVerifyType, string unexpectedSetupType, string unexpectedVerifyType) @@ -64,41 +79,6 @@ await That(result.Sources["Mock.IMyService.g.cs"]) .Because("a widened facade would silently re-expose an accessor the mock never intercepts"); } - [Theory] - [InlineData("string this[int a, int b, int c, int d, int e] { get; }")] - [InlineData("string this[int a, int b, int c, int d, int e] { set; }")] - public async Task IndexerWithMoreThanFourKeys_ShouldKeepTheFullSurface(string indexer) - { - GeneratorResult result = Generator - .Run($$""" - using Mockolate; - - namespace MyCode; - public class Program - { - public static void Main(string[] args) - { - _ = IMyService.CreateMock(); - } - } - - public interface IMyService - { - {{indexer}} - } - """); - - await That(result.Diagnostics).IsEmpty(); - await That(result.Sources["Mock.IMyService.g.cs"]) - .Contains("global::Mockolate.Setup.IndexerSetup this[").And - .Contains("global::Mockolate.Verify.VerificationIndexerResult this[").And - .DoesNotContain("IIndexerGetterOnlySetup").And - .DoesNotContain("IIndexerSetterOnlySetup").And - .DoesNotContain("VerificationIndexerGetterResult").And - .DoesNotContain("VerificationIndexerSetterResult") - .Because("the narrowed indexer types only exist for up to four keys, so a wider indexer keeps the full surface"); - } - [Fact] public async Task InterfaceIndexerWithParameterNamedLikeLocal_ShouldGenerateUniqueLocalVariableName() { diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs index e2e703fc..d61e45f8 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs @@ -341,7 +341,14 @@ internal class IndexerSetup(global::Mockolate.MockRe global::Mockolate.Setup.IIndexerSetterSetupCallbackBuilder, global::Mockolate.Setup.IIndexerSetupReturnBuilder, global::Mockolate.Setup.IIndexerGetterSetupWithCallback, - global::Mockolate.Setup.IIndexerSetterSetupWithCallback + global::Mockolate.Setup.IIndexerSetterSetupWithCallback, + global::Mockolate.Setup.IIndexerGetterOnlySetup, + global::Mockolate.Setup.IIndexerGetterOnlyGetterSetup, + global::Mockolate.Setup.IIndexerGetterOnlySetupCallbackBuilder, + global::Mockolate.Setup.IIndexerGetterOnlySetupReturnBuilder, + global::Mockolate.Setup.IIndexerSetterOnlySetup, + global::Mockolate.Setup.IIndexerSetterOnlySetterSetup, + global::Mockolate.Setup.IIndexerSetterOnlySetupCallbackBuilder { private Callbacks>? _getterCallbacks; private Callbacks>? _setterCallbacks; @@ -842,6 +849,466 @@ private static bool TryExtractParameters(global::Mockolate.Interactions.IndexerA public override string ToString() => $"{FormatType(typeof(TValue))} this[{parameter1}, {parameter2}, {parameter3}, {parameter4}, {parameter5}]"; + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.SkippingBaseClass(bool skipBaseClass) + { + SkippingBaseClass(skipBaseClass); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.InitializeWith(TValue value) + { + InitializeWith(value); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetup.InitializeWith(global::System.Func valueGenerator) + { + InitializeWith(valueGenerator); + return this; + } + + /// + IIndexerGetterOnlyGetterSetup IIndexerGetterOnlySetup.OnGet + => this; + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackBuilder IIndexerGetterOnlyGetterSetup.Do(global::System.Action callback) + { + ((IIndexerGetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder IIndexerGetterOnlyGetterSetup.TransitionTo(string scenario) + { + ((IIndexerGetterSetup)this).TransitionTo(scenario); + return this; + } + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder IIndexerGetterOnlySetupCallbackBuilder.InParallel() + { + ((IIndexerGetterSetupCallbackBuilder)this).InParallel(); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackWhenBuilder IIndexerGetterOnlySetupParallelCallbackBuilder.When(global::System.Func predicate) + { + ((IIndexerGetterSetupParallelCallbackBuilder)this).When(predicate); + return this; + } + + /// + IIndexerGetterOnlySetupCallbackWhenBuilder IIndexerGetterOnlySetupCallbackWhenBuilder.For(int times) + { + ((IIndexerGetterSetupCallbackWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetupCallbackWhenBuilder.Only(int times) + { + ((IIndexerGetterSetupCallbackWhenBuilder)this).Only(times); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(TValue returnValue) + { + Returns(returnValue); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Returns(global::System.Func callback) + { + Returns(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws() + { + Throws(); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Exception exception) + { + Throws(exception); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnBuilder IIndexerGetterOnlySetup.Throws(global::System.Func callback) + { + Throws(callback); + return this; + } + + /// + IIndexerGetterOnlySetupReturnWhenBuilder IIndexerGetterOnlySetupReturnBuilder.When(global::System.Func predicate) + { + ((IIndexerSetupReturnBuilder)this).When(predicate); + return this; + } + + /// + IIndexerGetterOnlySetupReturnWhenBuilder IIndexerGetterOnlySetupReturnWhenBuilder.For(int times) + { + ((IIndexerSetupReturnWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerGetterOnlySetup IIndexerGetterOnlySetupReturnWhenBuilder.Only(int times) + { + ((IIndexerSetupReturnWhenBuilder)this).Only(times); + return this; + } + + /// + IIndexerSetterOnlySetup IIndexerSetterOnlySetup.SkippingBaseClass(bool skipBaseClass) + { + SkippingBaseClass(skipBaseClass); + return this; + } + + /// + IIndexerSetterOnlySetterSetup IIndexerSetterOnlySetup.OnSet + => this; + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetup)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackBuilder IIndexerSetterOnlySetterSetup.Do(global::System.Action callback) + { + ((IIndexerSetterSetupWithCallback)this).Do(callback); + return this; + } + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder IIndexerSetterOnlySetterSetup.TransitionTo(string scenario) + { + ((IIndexerSetterSetup)this).TransitionTo(scenario); + return this; + } + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder IIndexerSetterOnlySetupCallbackBuilder.InParallel() + { + ((IIndexerSetterSetupCallbackBuilder)this).InParallel(); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackWhenBuilder IIndexerSetterOnlySetupParallelCallbackBuilder.When(global::System.Func predicate) + { + ((IIndexerSetterSetupParallelCallbackBuilder)this).When(predicate); + return this; + } + + /// + IIndexerSetterOnlySetupCallbackWhenBuilder IIndexerSetterOnlySetupCallbackWhenBuilder.For(int times) + { + ((IIndexerSetterSetupCallbackWhenBuilder)this).For(times); + return this; + } + + /// + IIndexerSetterOnlySetup IIndexerSetterOnlySetupCallbackWhenBuilder.Only(int times) + { + ((IIndexerSetterSetupCallbackWhenBuilder)this).Only(times); + return this; + } + + } + + /// + /// Setup for a mocked indexer for , , , and that the mock only reads. + /// + /// + /// Used instead of IIndexerSetup<TValue, T1, T2, T3, T4, T5> when the mock has no setter to intercept, either + /// because the indexer is declared without one or because its setter is not accessible from the mock's assembly. + /// Writes then never reach the mock, so IIndexerSetup<TValue, T1, T2, T3, T4, T5>.OnSet is not offered. + /// + internal interface IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlyGetterSetup OnGet { get; } + + /// + IIndexerGetterOnlySetup SkippingBaseClass(bool skipBaseClass = true); + + /// + /// + /// Seeds the value that reads return. Unlike a read-write indexer there is no setter to update the + /// slot afterwards, so it stays at unless a Returns entry applies. + /// + IIndexerGetterOnlySetup InitializeWith(TValue value); + + /// + IIndexerGetterOnlySetup InitializeWith(global::System.Func valueGenerator); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(TValue returnValue); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Returns(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws() + where TException : global::System.Exception, new(); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Exception exception); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func callback); + + /// + IIndexerGetterOnlySetupReturnBuilder Throws(global::System.Func callback); + } + + /// + /// Setup for attaching side-effects to the getter of a get-only indexer for , , , and . + /// + /// + /// The counterpart of IIndexerGetterSetupWithCallback<TValue, T1, T2, T3, T4, T5> for + /// IIndexerGetterOnlySetup<TValue, T1, T2, T3, T4, T5>: the returned builders stay on the getter-only surface, + /// so chaining can never reach IIndexerSetup<TValue, T1, T2, T3, T4, T5>.OnSet. + /// + internal interface IIndexerGetterOnlyGetterSetup + { + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerGetterOnlySetupParallelCallbackBuilder TransitionTo(string scenario); + } + + /// + /// Sets up a callback for a get-only indexer for , , , and . + /// + internal interface IIndexerGetterOnlySetupCallbackBuilder + : IIndexerGetterOnlySetupParallelCallbackBuilder + { + /// + IIndexerGetterOnlySetupParallelCallbackBuilder InParallel(); + } + + /// + /// Sets up a parallel callback for a get-only indexer for , , , and . + /// + internal interface IIndexerGetterOnlySetupParallelCallbackBuilder + : IIndexerGetterOnlySetupCallbackWhenBuilder + { + /// + IIndexerGetterOnlySetupCallbackWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when callback for a get-only indexer for , , , and . + /// + internal interface IIndexerGetterOnlySetupCallbackWhenBuilder + : IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlySetupCallbackWhenBuilder For(int times); + + /// + IIndexerGetterOnlySetup Only(int times); + } + + /// + /// Sets up a return/throw builder for a get-only indexer for , , , and . + /// + internal interface IIndexerGetterOnlySetupReturnBuilder + : IIndexerGetterOnlySetupReturnWhenBuilder + { + /// + IIndexerGetterOnlySetupReturnWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when builder for returns/throws for a get-only indexer for , , , and . + /// + internal interface IIndexerGetterOnlySetupReturnWhenBuilder + : IIndexerGetterOnlySetup + { + /// + IIndexerGetterOnlySetupReturnWhenBuilder For(int times); + + /// + IIndexerGetterOnlySetup Only(int times); + } + + /// + /// Setup for a mocked indexer for , , , and that the mock only writes. + /// + /// + /// The write-only counterpart of IIndexerGetterOnlySetup<TValue, T1, T2, T3, T4, T5>: the mock has no getter to + /// intercept, so IIndexerSetup<TValue, T1, T2, T3, T4, T5>.OnGet, InitializeWith and the + /// Returns/Throws read-sequence are not offered. + /// + internal interface IIndexerSetterOnlySetup + { + /// + IIndexerSetterOnlySetterSetup OnSet { get; } + + /// + IIndexerSetterOnlySetup SkippingBaseClass(bool skipBaseClass = true); + } + + /// + /// Setup for attaching side-effects to the setter of a set-only indexer for , , , and . + /// + /// + /// The counterpart of IIndexerSetterSetupWithCallback<TValue, T1, T2, T3, T4, T5> for + /// IIndexerSetterOnlySetup<TValue, T1, T2, T3, T4, T5>: the returned builders stay on the setter-only surface, + /// so chaining can never reach IIndexerSetup<TValue, T1, T2, T3, T4, T5>.OnGet or the + /// Returns/Throws read-sequence. + /// + internal interface IIndexerSetterOnlySetterSetup + { + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupCallbackBuilder Do(global::System.Action callback); + + /// + IIndexerSetterOnlySetupParallelCallbackBuilder TransitionTo(string scenario); + } + + /// + /// Sets up a setter callback for a set-only indexer for , , , and . + /// + internal interface IIndexerSetterOnlySetupCallbackBuilder + : IIndexerSetterOnlySetupParallelCallbackBuilder + { + /// + IIndexerSetterOnlySetupParallelCallbackBuilder InParallel(); + } + + /// + /// Sets up a parallel setter callback for a set-only indexer for , , , and . + /// + internal interface IIndexerSetterOnlySetupParallelCallbackBuilder + : IIndexerSetterOnlySetupCallbackWhenBuilder + { + /// + IIndexerSetterOnlySetupCallbackWhenBuilder When(global::System.Func predicate); + } + + /// + /// Sets up a when setter callback for a set-only indexer for , , , and . + /// + internal interface IIndexerSetterOnlySetupCallbackWhenBuilder + : IIndexerSetterOnlySetup + { + /// + IIndexerSetterOnlySetupCallbackWhenBuilder For(int times); + + /// + IIndexerSetterOnlySetup Only(int times); } } @@ -894,6 +1361,50 @@ public void Forever() public global::Mockolate.Setup.IIndexerSetup OnlyOnce() => setup.Only(1); } + + /// + /// Extensions for setups of get-only indexers with 5 parameters. + /// + extension(Mockolate.Setup.IIndexerGetterOnlySetupReturnWhenBuilder setup) + { + /// + /// Returns/throws forever. + /// + public void Forever() + { + setup.For(int.MaxValue); + } + + /// + /// Uses the return value only once. + /// + public global::Mockolate.Setup.IIndexerGetterOnlySetup OnlyOnce() + => setup.Only(1); + } + + /// + /// Extensions for getter callback setups of get-only indexers with 5 parameters. + /// + extension(Mockolate.Setup.IIndexerGetterOnlySetupCallbackWhenBuilder setup) + { + /// + /// Executes the callback only once. + /// + public global::Mockolate.Setup.IIndexerGetterOnlySetup OnlyOnce() + => setup.Only(1); + } + + /// + /// Extensions for setter callback setups of set-only indexers with 5 parameters. + /// + extension(Mockolate.Setup.IIndexerSetterOnlySetupCallbackWhenBuilder setup) + { + /// + /// Executes the callback only once. + /// + public global::Mockolate.Setup.IIndexerSetterOnlySetup OnlyOnce() + => setup.Only(1); + } } } namespace Mockolate.Interactions @@ -1052,4 +1563,56 @@ public override string ToString() } } +namespace Mockolate.Verify +{ + /// + /// Verifications on a 5-key indexer for , , , and that the mock only reads. + /// + /// + /// Used instead of VerificationIndexerResult<TSubject, TParameter> when the + /// mock has no setter to intercept. Writes then never reach the mock, so offering Set(...) here would + /// always report zero interactions. + /// + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal class VerificationIndexerGetterResult( + TSubject subject, + global::Mockolate.MockRegistry mockRegistry, + int getMemberId, + global::System.Func gotPredicate, + global::System.Func parametersDescription) + { + /// + public global::Mockolate.Verify.VerificationResult Got() + => mockRegistry.IndexerGot(subject, getMemberId, gotPredicate, parametersDescription); + } + /// + /// Verifications on a 5-key indexer of type for , , , and that the mock only writes. + /// + /// + /// Used instead of VerificationIndexerResult<TSubject, TParameter> when the + /// mock has no getter to intercept, so Got() is not offered. + /// + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal class VerificationIndexerSetterResult( + TSubject subject, + global::Mockolate.MockRegistry mockRegistry, + int setMemberId, + global::System.Func, bool> setPredicate, + global::System.Func parametersDescription) + { + /// + public global::Mockolate.Verify.VerificationResult Set(global::Mockolate.Parameters.IParameter value) + => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, + (global::Mockolate.Parameters.IParameterMatch)value, parametersDescription); + + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)] + /// + /// Verifies the indexer write access on the mock with the given . + /// + public global::Mockolate.Verify.VerificationResult Set(TParameter value) + => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, + (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.Is(value, value?.ToString() ?? "null"), parametersDescription); + } +} + #nullable disable diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/Mock.IComprehensiveInterface.g.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/Mock.IComprehensiveInterface.g.cs index 02d92600..d5c29a48 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/Mock.IComprehensiveInterface.g.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/Mock.IComprehensiveInterface.g.cs @@ -43,40 +43,44 @@ internal class IComprehensiveInterface : internal const int MemberId_Indexer_int_Set = 19; internal const int MemberId_Indexer_int_int_int_int_int_Get = 20; internal const int MemberId_Indexer_int_int_int_int_int_Set = 21; - internal const int MemberId_Indexer_double_Get = 22; - internal const int MemberId_Indexer_double_Set = 23; - internal const int MemberId_Indexer_char_Get = 24; - internal const int MemberId_Indexer_char_Set = 25; - internal const int MemberId_StaticAbstractMethod = 26; - internal const int MemberId_WithModifiers = 27; - internal const int MemberId_WithDefaults = 28; - internal const int MemberId_WithCollidingNames = 29; - internal const int MemberId_GetMaybeNull = 30; - internal const int MemberId_TakeObject = 31; - internal const int MemberId_TakeTwoObjects = 32; - internal const int MemberId_TakeIntAndObject = 33; - internal const int MemberId_DoTask = 34; - internal const int MemberId_DoTaskOf = 35; - internal const int MemberId_DoVT = 36; - internal const int MemberId_DoVTOf = 37; - internal const int MemberId_GetTuple = 38; - internal const int MemberId_GetNullable = 39; - internal const int MemberId_GetSpan = 40; - internal const int MemberId_GetROSpan = 41; - internal const int MemberId_GetByRef = 42; - internal const int MemberId_GetByRefReadonly = 43; - internal const int MemberId_G1_T_ = 44; - internal const int MemberId_G2_T_ = 45; - internal const int MemberId_G3_T_ = 46; - internal const int MemberId_G4_T_ = 47; - internal const int MemberId_G5_T_ = 48; - internal const int MemberId_G6_T_ = 49; - internal const int MemberId_G7_T_ = 50; - internal const int MemberId_G8_T_ = 51; - internal const int MemberId_Five = 52; - internal const int MemberId_Seventeen = 53; - internal const int MemberId_SeventeenVoid = 54; - internal const int MemberCount = 55; + internal const int MemberId_Indexer_byte_byte_byte_byte_byte_Get = 22; + internal const int MemberId_Indexer_byte_byte_byte_byte_byte_Set = 23; + internal const int MemberId_Indexer_short_short_short_short_short_Get = 24; + internal const int MemberId_Indexer_short_short_short_short_short_Set = 25; + internal const int MemberId_Indexer_double_Get = 26; + internal const int MemberId_Indexer_double_Set = 27; + internal const int MemberId_Indexer_char_Get = 28; + internal const int MemberId_Indexer_char_Set = 29; + internal const int MemberId_StaticAbstractMethod = 30; + internal const int MemberId_WithModifiers = 31; + internal const int MemberId_WithDefaults = 32; + internal const int MemberId_WithCollidingNames = 33; + internal const int MemberId_GetMaybeNull = 34; + internal const int MemberId_TakeObject = 35; + internal const int MemberId_TakeTwoObjects = 36; + internal const int MemberId_TakeIntAndObject = 37; + internal const int MemberId_DoTask = 38; + internal const int MemberId_DoTaskOf = 39; + internal const int MemberId_DoVT = 40; + internal const int MemberId_DoVTOf = 41; + internal const int MemberId_GetTuple = 42; + internal const int MemberId_GetNullable = 43; + internal const int MemberId_GetSpan = 44; + internal const int MemberId_GetROSpan = 45; + internal const int MemberId_GetByRef = 46; + internal const int MemberId_GetByRefReadonly = 47; + internal const int MemberId_G1_T_ = 48; + internal const int MemberId_G2_T_ = 49; + internal const int MemberId_G3_T_ = 50; + internal const int MemberId_G4_T_ = 51; + internal const int MemberId_G5_T_ = 52; + internal const int MemberId_G6_T_ = 53; + internal const int MemberId_G7_T_ = 54; + internal const int MemberId_G8_T_ = 55; + internal const int MemberId_Five = 56; + internal const int MemberId_Seventeen = 57; + internal const int MemberId_SeventeenVoid = 58; + internal const int MemberCount = 59; internal static readonly global::Mockolate.Interactions.PropertyGetterAccess PropertyAccess_GetSet_Get = new global::Mockolate.Interactions.PropertyGetterAccess("global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface.GetSet"); internal static readonly global::Mockolate.Interactions.PropertyGetterAccess PropertyAccess_GetOnly_Get = new global::Mockolate.Interactions.PropertyGetterAccess("global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface.GetOnly"); internal static readonly global::Mockolate.Interactions.PropertyGetterAccess PropertyAccess_SetOnly_Get = new global::Mockolate.Interactions.PropertyGetterAccess("global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface.SetOnly"); @@ -522,6 +526,47 @@ public string this[int i] } } + /// + public long this[byte a, byte b, byte c, byte d, byte e] + { + get + { + global::Mockolate.Interactions.IndexerGetterAccess access = new(a, b, c, d, e); + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(access); + } + global::Mockolate.Setup.IndexerSetup? setup = this.MockRegistry.GetIndexerSetup>(access); + if (this.MockRegistry.Wraps is not global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface wraps) + { + return setup is null + ? this.MockRegistry.GetIndexerFallback(access, 2) + : this.MockRegistry.ApplyIndexerSetup(access, setup, 2); + } + long baseResult = wraps[a, b, c, d, e]; + return this.MockRegistry.ApplyIndexerGetter(access, setup, baseResult, 2); + } + } + + /// + public long this[short a, short b, short c, short d, short e] + { + set + { + global::Mockolate.Interactions.IndexerSetterAccess access = new(a, b, c, d, e, value); + if (this.MockRegistry.Behavior.SkipInteractionRecording == false) + { + this.MockRegistry.RegisterInteraction(access); + } + global::Mockolate.Setup.IndexerSetup? setup = this.MockRegistry.GetIndexerSetup>(access); + this.MockRegistry.ApplyIndexerSetter(access, setup, value, 3); + if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface wraps) + { + wraps[a, b, c, d, e] = value; + } + } + } + /// public string this[double key] { @@ -552,11 +597,11 @@ public string this[double key] if (this.MockRegistry.Wraps is not global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface wraps) { return setup is null - ? this.MockRegistry.GetIndexerFallback(access, 2) - : this.MockRegistry.ApplyIndexerSetup(access, setup, 2); + ? this.MockRegistry.GetIndexerFallback(access, 4) + : this.MockRegistry.ApplyIndexerSetup(access, setup, 4); } string baseResult = wraps[key]; - return this.MockRegistry.ApplyIndexerGetter(access, setup, baseResult, 2); + return this.MockRegistry.ApplyIndexerGetter(access, setup, baseResult, 4); } } @@ -587,7 +632,7 @@ public string this[char key] } global::Mockolate.Interactions.IndexerSetterAccess access = new(key, value); setup ??= this.MockRegistry.GetIndexerSetup>(access); - this.MockRegistry.ApplyIndexerSetter(access, setup, value, 3); + this.MockRegistry.ApplyIndexerSetter(access, setup, value, 5); if (this.MockRegistry.Wraps is global::Mockolate.Tests.GeneratorCoverage.IComprehensiveInterface wraps) { wraps[key] = value; @@ -2423,6 +2468,54 @@ public void SeventeenVoid(int a1, int a2, int a3, int a4, int a5, int a6, int a7 } } + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, CovariantParameterAdapter.Wrap(parameter1 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter2 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter3 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter4 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter5 ?? global::Mockolate.It.IsNull("null"))); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_byte_byte_byte_byte_byte_Get, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[byte parameter1, byte parameter2, byte parameter3, byte parameter4, byte parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter1), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter2), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter3), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter4), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter5)); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_byte_byte_byte_byte_byte_Get, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerSetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, CovariantParameterAdapter.Wrap(parameter1 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter2 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter3 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter4 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter5 ?? global::Mockolate.It.IsNull("null"))); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_short_short_short_short_short_Get, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerSetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[short parameter1, short parameter2, short parameter3, short parameter4, short parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter1), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter2), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter3), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter4), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter5)); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_short_short_short_short_short_Get, indexerSetup); + return indexerSetup; + } + } + /// [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1] @@ -3111,6 +3204,54 @@ void IMockRaiseOnIComprehensiveInterface.CustomEvent(global::Mockolate.Parameter } } + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerGetterResult(this, this.MockRegistry, -1, + interaction => interaction is global::Mockolate.Interactions.IndexerGetterAccess g && CovariantParameterAdapter.Wrap(a ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter1) && CovariantParameterAdapter.Wrap(b ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter2) && CovariantParameterAdapter.Wrap(c ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter3) && CovariantParameterAdapter.Wrap(d ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter4) && CovariantParameterAdapter.Wrap(e ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter5), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a ?? "null", (object?)b ?? "null", (object?)c ?? "null", (object?)d ?? "null", (object?)e ?? "null")); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[byte a, byte b, byte c, byte d, byte e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerGetterResult(this, this.MockRegistry, -1, + interaction => interaction is global::Mockolate.Interactions.IndexerGetterAccess g && global::System.Collections.Generic.EqualityComparer.Default.Equals(a, g.Parameter1) && global::System.Collections.Generic.EqualityComparer.Default.Equals(b, g.Parameter2) && global::System.Collections.Generic.EqualityComparer.Default.Equals(c, g.Parameter3) && global::System.Collections.Generic.EqualityComparer.Default.Equals(d, g.Parameter4) && global::System.Collections.Generic.EqualityComparer.Default.Equals(e, g.Parameter5), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a, (object?)b, (object?)c, (object?)d, (object?)e)); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerSetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerSetterResult(this, this.MockRegistry, -1, + (interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess s && CovariantParameterAdapter.Wrap(a ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter1) && CovariantParameterAdapter.Wrap(b ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter2) && CovariantParameterAdapter.Wrap(c ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter3) && CovariantParameterAdapter.Wrap(d ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter4) && CovariantParameterAdapter.Wrap(e ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter5) && value.Matches(s.TypedValue), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a ?? "null", (object?)b ?? "null", (object?)c ?? "null", (object?)d ?? "null", (object?)e ?? "null")); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerSetterResult IMockVerifyForIComprehensiveInterface.this[short a, short b, short c, short d, short e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerSetterResult(this, this.MockRegistry, -1, + (interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess s && global::System.Collections.Generic.EqualityComparer.Default.Equals(a, s.Parameter1) && global::System.Collections.Generic.EqualityComparer.Default.Equals(b, s.Parameter2) && global::System.Collections.Generic.EqualityComparer.Default.Equals(c, s.Parameter3) && global::System.Collections.Generic.EqualityComparer.Default.Equals(d, s.Parameter4) && global::System.Collections.Generic.EqualityComparer.Default.Equals(e, s.Parameter5) && value.Matches(s.TypedValue), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a, (object?)b, (object?)c, (object?)d, (object?)e)); + } + } + /// [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? key] @@ -3689,6 +3830,54 @@ private sealed class VerifyMonitorIComprehensiveInterface(global::Mockolate.Mock } } + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerGetterResult(this, this.MockRegistry, -1, + interaction => interaction is global::Mockolate.Interactions.IndexerGetterAccess g && CovariantParameterAdapter.Wrap(a ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter1) && CovariantParameterAdapter.Wrap(b ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter2) && CovariantParameterAdapter.Wrap(c ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter3) && CovariantParameterAdapter.Wrap(d ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter4) && CovariantParameterAdapter.Wrap(e ?? global::Mockolate.It.IsNull("null")).Matches(g.Parameter5), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a ?? "null", (object?)b ?? "null", (object?)c ?? "null", (object?)d ?? "null", (object?)e ?? "null")); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[byte a, byte b, byte c, byte d, byte e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerGetterResult(this, this.MockRegistry, -1, + interaction => interaction is global::Mockolate.Interactions.IndexerGetterAccess g && global::System.Collections.Generic.EqualityComparer.Default.Equals(a, g.Parameter1) && global::System.Collections.Generic.EqualityComparer.Default.Equals(b, g.Parameter2) && global::System.Collections.Generic.EqualityComparer.Default.Equals(c, g.Parameter3) && global::System.Collections.Generic.EqualityComparer.Default.Equals(d, g.Parameter4) && global::System.Collections.Generic.EqualityComparer.Default.Equals(e, g.Parameter5), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a, (object?)b, (object?)c, (object?)d, (object?)e)); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerSetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerSetterResult(this, this.MockRegistry, -1, + (interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess s && CovariantParameterAdapter.Wrap(a ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter1) && CovariantParameterAdapter.Wrap(b ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter2) && CovariantParameterAdapter.Wrap(c ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter3) && CovariantParameterAdapter.Wrap(d ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter4) && CovariantParameterAdapter.Wrap(e ?? global::Mockolate.It.IsNull("null")).Matches(s.Parameter5) && value.Matches(s.TypedValue), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a ?? "null", (object?)b ?? "null", (object?)c ?? "null", (object?)d ?? "null", (object?)e ?? "null")); + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Verify.VerificationIndexerSetterResult IMockVerifyForIComprehensiveInterface.this[short a, short b, short c, short d, short e] + { + get + { + return new global::Mockolate.Verify.VerificationIndexerSetterResult(this, this.MockRegistry, -1, + (interaction, value) => interaction is global::Mockolate.Interactions.IndexerSetterAccess s && global::System.Collections.Generic.EqualityComparer.Default.Equals(a, s.Parameter1) && global::System.Collections.Generic.EqualityComparer.Default.Equals(b, s.Parameter2) && global::System.Collections.Generic.EqualityComparer.Default.Equals(c, s.Parameter3) && global::System.Collections.Generic.EqualityComparer.Default.Equals(d, s.Parameter4) && global::System.Collections.Generic.EqualityComparer.Default.Equals(e, s.Parameter5) && value.Matches(s.TypedValue), + () => global::System.String.Format("[{0}, {1}, {2}, {3}, {4}]", (object?)a, (object?)b, (object?)c, (object?)d, (object?)e)); + } + } + /// [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] global::Mockolate.Verify.VerificationIndexerGetterResult IMockVerifyForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? key] @@ -4305,6 +4494,54 @@ public MockInScenarioForIComprehensiveInterface(global::Mockolate.MockRegistry m } } + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, CovariantParameterAdapter.Wrap(parameter1 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter2 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter3 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter4 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter5 ?? global::Mockolate.It.IsNull("null"))); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_byte_byte_byte_byte_byte_Get, _scenarioName, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[byte parameter1, byte parameter2, byte parameter3, byte parameter4, byte parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter1), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter2), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter3), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter4), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter5)); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_byte_byte_byte_byte_byte_Get, _scenarioName, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerSetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, CovariantParameterAdapter.Wrap(parameter1 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter2 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter3 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter4 ?? global::Mockolate.It.IsNull("null")), CovariantParameterAdapter.Wrap(parameter5 ?? global::Mockolate.It.IsNull("null"))); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_short_short_short_short_short_Get, _scenarioName, indexerSetup); + return indexerSetup; + } + } + + /// + [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] + global::Mockolate.Setup.IIndexerSetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[short parameter1, short parameter2, short parameter3, short parameter4, short parameter5] + { + get + { + var indexerSetup = new global::Mockolate.Setup.IndexerSetup(MockRegistry, (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter1), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter2), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter3), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter4), (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(parameter5)); + this.MockRegistry.SetupIndexer(global::Mockolate.Mock.IComprehensiveInterface.MemberId_Indexer_short_short_short_short_short_Get, _scenarioName, indexerSetup); + return indexerSetup; + } + } + /// [global::System.Diagnostics.DebuggerBrowsable(global::System.Diagnostics.DebuggerBrowsableState.Never)] global::Mockolate.Setup.IIndexerGetterOnlySetup global::Mockolate.Mock.IMockSetupForIComprehensiveInterface.this[global::Mockolate.Parameters.IParameter? parameter1] @@ -5040,6 +5277,42 @@ internal interface IMockSetupForIComprehensiveInterface [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] global::Mockolate.Setup.IndexerSetup this[int parameter1, int parameter2, int parameter3, int parameter4, int parameter5] { get; } + /// + /// Setup for the long indexer this[byte, byte, byte, byte, byte] + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue)] + global::Mockolate.Setup.IIndexerGetterOnlySetup this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] { get; } + + /// + /// Setup for the long indexer this[byte, byte, byte, byte, byte] + /// + /// + /// This overload accepts direct values for every parameter; each is treated as It.Is<T>(value). + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + global::Mockolate.Setup.IIndexerGetterOnlySetup this[byte parameter1, byte parameter2, byte parameter3, byte parameter4, byte parameter5] { get; } + + /// + /// Setup for the long indexer this[short, short, short, short, short] + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue)] + global::Mockolate.Setup.IIndexerSetterOnlySetup this[global::Mockolate.Parameters.IParameter? parameter1, global::Mockolate.Parameters.IParameter? parameter2, global::Mockolate.Parameters.IParameter? parameter3, global::Mockolate.Parameters.IParameter? parameter4, global::Mockolate.Parameters.IParameter? parameter5] { get; } + + /// + /// Setup for the long indexer this[short, short, short, short, short] + /// + /// + /// This overload accepts direct values for every parameter; each is treated as It.Is<T>(value). + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + global::Mockolate.Setup.IIndexerSetterOnlySetup this[short parameter1, short parameter2, short parameter3, short parameter4, short parameter5] { get; } + /// /// Setup for the string indexer this[double] /// @@ -5680,6 +5953,42 @@ internal interface IMockVerifyForIComprehensiveInterface [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] global::Mockolate.Verify.VerificationIndexerResult this[int a, int b, int c, int d, int e] { get; } + /// + /// Verify interactions with the long indexer this[byte, byte, byte, byte, byte]. + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue)] + global::Mockolate.Verify.VerificationIndexerGetterResult this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] { get; } + + /// + /// Verify interactions with the long indexer this[byte, byte, byte, byte, byte]. + /// + /// + /// This overload accepts direct values for every parameter and returns a VerificationResult<TVerify>.IgnoreParameters whose VerificationResult<TVerify>.AnyParameters() drops per-parameter matching entirely. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + global::Mockolate.Verify.VerificationIndexerGetterResult this[byte a, byte b, byte c, byte d, byte e] { get; } + + /// + /// Verify interactions with the long indexer this[short, short, short, short, short]. + /// + /// + /// This overload takes It argument matchers (e.g. It.IsAny<T>(), It.Is<T>(value)) for every parameter. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(int.MaxValue)] + global::Mockolate.Verify.VerificationIndexerSetterResult this[global::Mockolate.Parameters.IParameter? a, global::Mockolate.Parameters.IParameter? b, global::Mockolate.Parameters.IParameter? c, global::Mockolate.Parameters.IParameter? d, global::Mockolate.Parameters.IParameter? e] { get; } + + /// + /// Verify interactions with the long indexer this[short, short, short, short, short]. + /// + /// + /// This overload accepts direct values for every parameter and returns a VerificationResult<TVerify>.IgnoreParameters whose VerificationResult<TVerify>.AnyParameters() drops per-parameter matching entirely. + /// + [global::System.Runtime.CompilerServices.OverloadResolutionPriority(0)] + global::Mockolate.Verify.VerificationIndexerSetterResult this[short a, short b, short c, short d, short e] { get; } + /// /// Verify interactions with the string indexer this[double]. /// diff --git a/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs b/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs index 113b9076..a0f9e89d 100644 --- a/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs +++ b/Tests/Mockolate.Tests/GeneratorCoverage/IComprehensiveInterface.cs @@ -6,7 +6,7 @@ namespace Mockolate.Tests.GeneratorCoverage; /// /// Squeezes every interface-shaped generator branch we can fit into a single type: -/// property accessor combinations, indexers (single + arity-5, get-only + set-only), all three event flavors, +/// property accessor combinations, indexers (single + arity-5, get-only + set-only at both arities), all three event flavors, /// static abstract members, every parameter modifier, every default-value kind, /// every "reserved" parameter name, nullable annotations, async returns, special return /// shapes (Span/ReadOnlySpan/ref/ref-readonly/tuple/Nullable<T>), @@ -23,6 +23,8 @@ public interface IComprehensiveInterface string this[int i] { get; set; } string this[int a, int b, int c, int d, int e] { get; set; } + long this[byte a, byte b, byte c, byte d, byte e] { get; } + long this[short a, short b, short c, short d, short e] { set; } string this[double key] { get; } string this[char key] { set; } diff --git a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs index e72d6559..00c97ef6 100644 --- a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs @@ -378,6 +378,55 @@ public async Task SetOnlyFourKeyIndexer_Set_ShouldVerifyTheWrite() await That(sut.Mock.Verify[It.Is(1), It.Is(2), It.Is(3), It.Is(5)].Set(It.IsAny())).Never(); } + [Fact] + public async Task GetOnlyFiveKeyIndexer_ChainedReturns_ShouldStayOnGetterOnlySurface() + { + IAccessorService sut = IAccessorService.CreateMock(); + IIndexerGetterOnlySetup chained = sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .Returns(1).OnlyOnce(); + chained.Returns((k1, k2, k3, k4, k5) => k1 + k2 + k3 + k4 + k5); + + await That(sut[1, 2, 3, 4, 5]).IsEqualTo(1); + await That(sut[1, 2, 3, 4, 5]).IsEqualTo(15); + await That(sut.Mock.Verify[It.Is(1), It.Is(2), It.Is(3), It.Is(4), It.Is(5)].Got()).Exactly(2) + .Because("the narrowed surface is generated per-compilation for indexers with more than four keys"); + await That(sut.Mock.Verify[It.Is(1), It.Is(2), It.Is(3), It.Is(4), It.Is(6)].Got()).Never(); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_ReturnsForever_ShouldUseTheLastValueForever() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .Returns(2) + .Returns(4).Forever(); + + int[] result = new int[4]; + for (int i = 0; i < 4; i++) + { + result[i] = sut[i, i, i, i, i]; + } + + await That(result).IsEqualTo([2, 4, 4, 4,]); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetAndVerify_ShouldUseTheNarrowedSurface() + { + List written = new(); + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnSet.Do((k1, k2, k3, k4, k5, v) => written.Add($"{k1}{k2}{k3}{k4}{k5}-{v}")); + + sut["a", "b", "c", "d", "e"] = "x"; + + await That(written).IsEqualTo(["abcde-x",]); + await That(sut.Mock.Verify[It.Is("a"), It.Is("b"), It.Is("c"), It.Is("d"), It.Is("e")].Set("x")).Once(); + await That(sut.Mock.Verify[It.Is("a"), It.Is("b"), It.Is("c"), It.Is("d"), It.Is("e")].Set("y")).Never(); + } + public interface IAccessorService { int this[int key] { get; } @@ -385,6 +434,8 @@ public interface IAccessorService int this[int key1, int key2] { get; } string this[string key1, string key2] { set; } string this[int key1, int key2, int key3, int key4] { set; } + int this[int key1, int key2, int key3, int key4, int key5] { get; } + string this[string key1, string key2, string key3, string key4, string key5] { set; } } public class AccessorService From 85643c1f8bc3a270995dce560670ea6f7cc4e496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Wed, 29 Jul 2026 15:32:05 +0200 Subject: [PATCH 2/2] fix: format narrowed indexer setter verifications with It.IsValue The generated VerificationIndexerSetterResult for indexers with more than four keys described the expected value with It.Is(value, value?.ToString() ?? "null"). That renders strings unquoted, formats IFormattable values with the current culture instead of the invariant one, and builds the description even when the verification passes. It.IsValue is the matcher the generator already emits for by-value parameters elsewhere, and it exists precisely because generated code cannot see the internal CallerArgumentExpression polyfill the shipped result relies on. It defers formatting to the failure path and renders quoted, invariant values, so a literal now reads the same above four keys as it does on the shipped one-to-four key surface. Also: - Pass the collected IndexerSetupKey array straight into Sources.IndexerSetups instead of rebuilding a Dictionary in the source-output callback. The sorted order from CollectIndexerSetupKeys becomes the only ordering in play, and the repeated tuple type disappears from both files. Generated output is unchanged. - Cover the generated narrowed delegations at five keys: Throws, InitializeWith, SkippingBaseClass, the return-builder When, and OnGet/OnSet with OnlyOnce, When, InParallel, For, the access-counter Do overload and TransitionTo on both facades, plus the rendered expectation string. - Drop the doc sentence about where the narrowed types come from. --- Docs/pages/setup/03-indexers.md | 3 +- .../MockGenerator.cs | 20 +- .../Sources/Sources.IndexerSetups.cs | 46 +-- .../IndexerSetups.g.cs | 2 +- ...tupIndexerTests.AccessorRestrictedTests.cs | 312 ++++++++++++++++++ 5 files changed, 343 insertions(+), 40 deletions(-) diff --git a/Docs/pages/setup/03-indexers.md b/Docs/pages/setup/03-indexers.md index 4a9ad5ad..2657ed20 100644 --- a/Docs/pages/setup/03-indexers.md +++ b/Docs/pages/setup/03-indexers.md @@ -119,8 +119,7 @@ interactions. See [Mockolate0002](../analyzers#mockolate0002) for when such a ty Both facades are fully restricted: the fluent builders returned by `Returns`, `Throws`, `Do` and `TransitionTo` stay on the narrowed surface, so no amount of chaining reaches the accessor the mock -does not intercept. This applies to any number of keys: up to four keys the narrowed types ship with -the library, for more keys they are generated per-compilation. +does not intercept. **Notes:** diff --git a/Source/Mockolate.SourceGenerators/MockGenerator.cs b/Source/Mockolate.SourceGenerators/MockGenerator.cs index cec5c56b..9a22ffe8 100644 --- a/Source/Mockolate.SourceGenerators/MockGenerator.cs +++ b/Source/Mockolate.SourceGenerators/MockGenerator.cs @@ -89,14 +89,8 @@ void IIncrementalGenerator.Initialize(IncrementalGeneratorInitializationContext return; } - Dictionary indexerSetups = new(); - foreach (IndexerSetupKey key in source.Left) - { - indexerSetups[key.Arity] = (key.NeedsGetterOnly, key.NeedsSetterOnly); - } - spc.AddSource("IndexerSetups.g.cs", - ToSource(Sources.Sources.IndexerSetups(indexerSetups, source.Right))); + ToSource(Sources.Sources.IndexerSetups(source.Left, source.Right))); }); IncrementalValueProvider> methodSetupKeys = collectedMocks @@ -247,15 +241,9 @@ private static EquatableArray CollectIndexerSetupKeys(Equatable int arity = property.IndexerParameters.Value.Count; bool needsGetterOnly = property is { Getter: not null, Setter: null, }; bool needsSetterOnly = property is { Getter: null, Setter: not null, }; - if (map.TryGetValue(arity, out (bool NeedsGetterOnly, bool NeedsSetterOnly) existing)) - { - map[arity] = (existing.NeedsGetterOnly || needsGetterOnly, - existing.NeedsSetterOnly || needsSetterOnly); - } - else - { - map[arity] = (needsGetterOnly, needsSetterOnly); - } + map.TryGetValue(arity, out (bool NeedsGetterOnly, bool NeedsSetterOnly) existing); + map[arity] = (existing.NeedsGetterOnly || needsGetterOnly, + existing.NeedsSetterOnly || needsSetterOnly); } } } diff --git a/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs b/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs index 1148712b..89224d12 100644 --- a/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs +++ b/Source/Mockolate.SourceGenerators/Sources/Sources.IndexerSetups.cs @@ -5,7 +5,7 @@ namespace Mockolate.SourceGenerators.Sources; internal static partial class Sources { public static string IndexerSetups( - Dictionary indexerSetups, + IEnumerable indexerSetups, bool hasOverloadResolutionPriority) { StringBuilder sb = InitializeBuilder(); @@ -16,18 +16,18 @@ public static string IndexerSetups( namespace Mockolate.Setup { """); - foreach (KeyValuePair item in indexerSetups) + foreach (IndexerSetupKey item in indexerSetups) { sb.AppendLine(); - AppendIndexerSetup(sb, item.Key, item.Value.NeedsGetterOnly, item.Value.NeedsSetterOnly); - if (item.Value.NeedsGetterOnly) + AppendIndexerSetup(sb, item.Arity, item.NeedsGetterOnly, item.NeedsSetterOnly); + if (item.NeedsGetterOnly) { - AppendGetterOnlyIndexerInterfaces(sb, item.Key); + AppendGetterOnlyIndexerInterfaces(sb, item.Arity); } - if (item.Value.NeedsSetterOnly) + if (item.NeedsSetterOnly) { - AppendSetterOnlyIndexerInterfaces(sb, item.Key); + AppendSetterOnlyIndexerInterfaces(sb, item.Arity); } } @@ -46,9 +46,9 @@ namespace Mockolate internal static class IndexerSetupExtensions { """).AppendLine(); - foreach (KeyValuePair setup in indexerSetups) + foreach (IndexerSetupKey setup in indexerSetups) { - int item = setup.Key; + int item = setup.Arity; string types = GetGenericTypeParameters(item); sb.Append($$""" /// @@ -96,7 +96,7 @@ public void Forever() } """).AppendLine(); - if (setup.Value.NeedsGetterOnly) + if (setup.NeedsGetterOnly) { sb.AppendLine(); sb.Append($$""" @@ -134,7 +134,7 @@ public void Forever() """).AppendLine(); } - if (setup.Value.NeedsSetterOnly) + if (setup.NeedsSetterOnly) { sb.AppendLine(); sb.Append($$""" @@ -159,29 +159,29 @@ public void Forever() """).AppendLine(); sb.Append("namespace Mockolate.Interactions").AppendLine(); sb.Append("{").AppendLine(); - foreach (int count in indexerSetups.Keys) + foreach (IndexerSetupKey key in indexerSetups) { - AppendIndexerGetterAccess(sb, count); - AppendIndexerSetterAccess(sb, count); + AppendIndexerGetterAccess(sb, key.Arity); + AppendIndexerSetterAccess(sb, key.Arity); } sb.Append("}").AppendLine(); sb.AppendLine(); - if (indexerSetups.Values.Any(v => v.NeedsGetterOnly || v.NeedsSetterOnly)) + if (indexerSetups.Any(v => v.NeedsGetterOnly || v.NeedsSetterOnly)) { sb.Append("namespace Mockolate.Verify").AppendLine(); sb.Append("{").AppendLine(); - foreach (KeyValuePair item in indexerSetups) + foreach (IndexerSetupKey item in indexerSetups) { - if (item.Value.NeedsGetterOnly) + if (item.NeedsGetterOnly) { - AppendIndexerVerifyGetterResult(sb, item.Key); + AppendIndexerVerifyGetterResult(sb, item.Arity); } - if (item.Value.NeedsSetterOnly) + if (item.NeedsSetterOnly) { - AppendIndexerVerifySetterResult(sb, item.Key, hasOverloadResolutionPriority); + AppendIndexerVerifySetterResult(sb, item.Arity, hasOverloadResolutionPriority); } } @@ -1872,13 +1872,17 @@ internal class VerificationIndexerSetterResult( sb.Append("\t\t[global::System.Runtime.CompilerServices.OverloadResolutionPriority(1)]").AppendLine(); } + // It.IsValue instead of It.Is: this file is emitted into the consumer's compilation, which + // cannot see the internal CallerArgumentExpression polyfill the shipped result relies on. + // IsValue formats the value lazily and invariantly, so a literal renders exactly as it does + // on the shipped surface, and no description is built on the passing path. sb.Append($$""" /// /// Verifies the indexer write access on the mock with the given . /// public global::Mockolate.Verify.VerificationResult Set(TParameter value) => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, - (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.Is(value, value?.ToString() ?? "null"), parametersDescription); + (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(value), parametersDescription); } """).AppendLine(); } diff --git a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs index d61e45f8..426a1255 100644 --- a/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs +++ b/Tests/Mockolate.SourceGenerators.Tests/Snapshot/Expected/ComprehensiveInterface_CanBeCreated/IndexerSetups.g.cs @@ -1611,7 +1611,7 @@ internal class VerificationIndexerSetterResult public global::Mockolate.Verify.VerificationResult Set(TParameter value) => mockRegistry.IndexerSet(subject, setMemberId, setPredicate, - (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.Is(value, value?.ToString() ?? "null"), parametersDescription); + (global::Mockolate.Parameters.IParameterMatch)global::Mockolate.It.IsValue(value), parametersDescription); } } diff --git a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs index 00c97ef6..647477fc 100644 --- a/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs +++ b/Tests/Mockolate.Tests/MockIndexers/SetupIndexerTests.AccessorRestrictedTests.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Mockolate.Setup; +using Mockolate.Verify; namespace Mockolate.Tests.MockIndexers; @@ -427,6 +428,317 @@ public async Task SetOnlyFiveKeyIndexer_OnSetAndVerify_ShouldUseTheNarrowedSurfa await That(sut.Mock.Verify[It.Is("a"), It.Is("b"), It.Is("c"), It.Is("d"), It.Is("e")].Set("y")).Never(); } + [Fact] + public async Task GetOnlyFiveKeyIndexer_Throws_ShouldIterateThroughAllRegisteredExceptions() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .Throws() + .Throws(new Exception("foo")) + .Throws(() => new Exception("bar")) + .Throws((k1, k2, k3, k4, k5) => new Exception($"baz-{k1 + k2 + k3 + k4 + k5}")) + .Throws((k1, k2, k3, k4, k5, v) => new Exception($"qux-{k1 + k2 + k3 + k4 + k5}-{v}")); + + void Act() => _ = sut[1, 2, 3, 4, 5]; + + await That(Act).Throws(); + Exception? result2 = Record.Exception(Act); + Exception? result3 = Record.Exception(Act); + Exception? result4 = Record.Exception(Act); + Exception? result5 = Record.Exception(Act); + await That(result2).HasMessage("foo"); + await That(result3).HasMessage("bar"); + await That(result4).HasMessage("baz-15"); + await That(result5).HasMessage("qux-15-0"); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_InitializeWith_ShouldSeedTheReadValue() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .InitializeWith(3) + .Returns((_, _, _, _, _, v) => 4 * v); + + await That(sut[1, 2, 3, 4, 5]).IsEqualTo(12); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_InitializeWithGenerator_ShouldSeedTheReadValue() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .InitializeWith((k1, k2, k3, k4, k5) => 10 * (k1 + k2 + k3 + k4 + k5)); + + await That(sut[1, 2, 3, 4, 5]).IsEqualTo(150); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_ReturnsWhen_ShouldOnlyUseValueWhenPredicateIsTrue() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .Returns(() => 4).When(i => i > 0); + + int result1 = sut[1, 2, 3, 4, 5]; + int result2 = sut[1, 2, 3, 4, 5]; + + await That(result1).IsEqualTo(0); + await That(result2).IsEqualTo(4); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_SkippingBaseClass_ShouldStayOnGetterOnlySurface() + { + IAccessorService sut = IAccessorService.CreateMock(); + IIndexerGetterOnlySetup chained = sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .SkippingBaseClass(); + chained.Returns(7); + + await That(sut[1, 2, 3, 4, 5]).IsEqualTo(7); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetChain_ShouldStayOnGetterOnlySurface() + { + int callCount1 = 0; + int callCount2 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + IIndexerGetterOnlySetup chained = sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do(() => { callCount1++; }) + .OnGet.Do((k1, k2, k3, k4, k5) => { callCount2++; }).OnlyOnce(); + chained.Returns(9); + + int result = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + + await That(result).IsEqualTo(9); + await That(callCount1).IsEqualTo(3); + await That(callCount2).IsEqualTo(1); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetWhen_ShouldOnlyInvokeCallbackWhenPredicateIsTrue() + { + int callCount = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do(() => { callCount++; }).When(i => i > 0); + + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + + await That(callCount).IsEqualTo(2); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetInParallel_ShouldInvokeParallelCallbacksAlways() + { + int callCount1 = 0; + int callCount2 = 0; + int callCount3 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do(() => { callCount1++; }) + .OnGet.Do((k1, k2, k3, k4, k5) => { callCount2++; }).InParallel() + .OnGet.Do((k1, k2, k3, k4, k5, v) => { callCount3++; }); + + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + _ = sut[1, 2, 3, 4, 5]; + + await That(callCount1).IsEqualTo(2); + await That(callCount2).IsEqualTo(4); + await That(callCount3).IsEqualTo(2); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetFor_ShouldRepeatCallbackTheGivenNumberOfTimes() + { + int callCount1 = 0; + int callCount2 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do(() => { callCount1++; }).For(2) + .OnGet.Do(() => { callCount2++; }); + + for (int i = 0; i < 6; i++) + { + _ = sut[1, 2, 3, 4, 5]; + } + + await That(callCount1).IsEqualTo(4); + await That(callCount2).IsEqualTo(2); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetDoWithCounter_ShouldReceiveTheAccessCounter() + { + List invocations = []; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do((i, _, _, _, _, _, _) => { invocations.Add(i); }); + + for (int i = 0; i < 3; i++) + { + _ = sut[1, 2, 3, 4, 5]; + } + + await That(invocations).IsEqualTo([0, 1, 2,]); + } + + [Fact] + public async Task GetOnlyFiveKeyIndexer_OnGetTransitionTo_ShouldSwitchScenario() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.InScenario("a") + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()] + .OnGet.Do(() => { }) + .OnGet.TransitionTo("b"); + sut.Mock.TransitionTo("a"); + + _ = sut[1, 2, 3, 4, 5]; + + await That(((IMock)sut).MockRegistry.Scenario).IsEqualTo("b"); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetChain_ShouldStayOnSetterOnlySurface() + { + int callCount1 = 0; + int callCount2 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + IIndexerSetterOnlySetup chained = sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do(() => { callCount1++; }) + .OnSet.Do(v => { callCount2++; }).OnlyOnce(); + chained.SkippingBaseClass(); + + sut["a", "b", "c", "d", "e"] = "1"; + sut["a", "b", "c", "d", "e"] = "2"; + sut["a", "b", "c", "d", "e"] = "3"; + sut["a", "b", "c", "d", "e"] = "4"; + + await That(callCount1).IsEqualTo(3); + await That(callCount2).IsEqualTo(1); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetWhen_ShouldOnlyInvokeCallbackWhenPredicateIsTrue() + { + int callCount = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do(() => { callCount++; }).When(i => i > 0); + + sut["a", "b", "c", "d", "e"] = "1"; + sut["a", "b", "c", "d", "e"] = "2"; + sut["a", "b", "c", "d", "e"] = "3"; + + await That(callCount).IsEqualTo(2); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetInParallel_ShouldInvokeParallelCallbacksAlways() + { + int callCount1 = 0; + int callCount2 = 0; + int callCount3 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do(() => { callCount1++; }) + .OnSet.Do((k1, k2, k3, k4, k5, v) => { callCount2++; }).InParallel() + .OnSet.Do(() => { callCount3++; }); + + sut["a", "b", "c", "d", "e"] = "1"; + sut["a", "b", "c", "d", "e"] = "2"; + sut["a", "b", "c", "d", "e"] = "3"; + sut["a", "b", "c", "d", "e"] = "4"; + + await That(callCount1).IsEqualTo(2); + await That(callCount2).IsEqualTo(4); + await That(callCount3).IsEqualTo(2); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetFor_ShouldRepeatCallbackTheGivenNumberOfTimes() + { + int callCount1 = 0; + int callCount2 = 0; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do(() => { callCount1++; }).For(2) + .OnSet.Do(() => { callCount2++; }); + + for (int i = 0; i < 6; i++) + { + sut["a", "b", "c", "d", "e"] = "1"; + } + + await That(callCount1).IsEqualTo(4); + await That(callCount2).IsEqualTo(2); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetDoWithCounter_ShouldReceiveTheAccessCounter() + { + List invocations = []; + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do((i, _, _, _, _, _, _) => { invocations.Add(i); }); + + for (int i = 0; i < 3; i++) + { + sut["a", "b", "c", "d", "e"] = "1"; + } + + await That(invocations).IsEqualTo([0, 1, 2,]); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_OnSetTransitionTo_ShouldSwitchScenario() + { + IAccessorService sut = IAccessorService.CreateMock(); + sut.Mock.InScenario("a") + .Setup[It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny()] + .OnSet.Do(() => { }) + .OnSet.TransitionTo("b"); + sut.Mock.TransitionTo("a"); + + sut["a", "b", "c", "d", "e"] = "x"; + + await That(((IMock)sut).MockRegistry.Scenario).IsEqualTo("b"); + } + + [Fact] + public async Task SetOnlyFiveKeyIndexer_SetExpectation_ShouldFormatTheValueLikeTheShippedSurface() + { + IAccessorService sut = IAccessorService.CreateMock(); + + IVerificationResult result = (IVerificationResult)sut.Mock + .Verify[It.Is("a"), It.Is("b"), It.Is("c"), It.Is("d"), It.Is("e")].Set("x"); + + await That(result.Expectation) + .IsEqualTo("set indexer [\"a\", \"b\", \"c\", \"d\", \"e\"] to \"x\"") + .Because("the generated narrowed result must render the value like the shipped one"); + } + public interface IAccessorService { int this[int key] { get; }