diff --git a/Docs/pages/setup/03-indexers.md b/Docs/pages/setup/03-indexers.md index 2fca4875..2657ed20 100644 --- a/Docs/pages/setup/03-indexers.md +++ b/Docs/pages/setup/03-indexers.md @@ -119,7 +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. Indexers with more than four keys keep the full surface. +does not intercept. **Notes:** diff --git a/Source/Mockolate.SourceGenerators/MockGenerator.cs b/Source/Mockolate.SourceGenerators/MockGenerator.cs index 2e9a077e..9a22ffe8 100644 --- a/Source/Mockolate.SourceGenerators/MockGenerator.cs +++ b/Source/Mockolate.SourceGenerators/MockGenerator.cs @@ -77,17 +77,21 @@ 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; + } + spc.AddSource("IndexerSetups.g.cs", - ToSource(Sources.Sources.IndexerSetups(ToHashSet(arities)))); - } - }); + ToSource(Sources.Sources.IndexerSetups(source.Left, source.Right))); + }); IncrementalValueProvider> methodSetupKeys = collectedMocks .Select(static (arr, _) => CollectMethodSetupKeys(arr)); @@ -212,18 +216,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 +228,31 @@ 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, }; + map.TryGetValue(arity, out (bool NeedsGetterOnly, bool NeedsSetterOnly) existing); + map[arity] = (existing.NeedsGetterOnly || needsGetterOnly, + existing.NeedsSetterOnly || 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 +543,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..89224d12 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( + IEnumerable 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 (IndexerSetupKey item in indexerSetups) { sb.AppendLine(); - AppendIndexerSetup(sb, item); + AppendIndexerSetup(sb, item.Arity, item.NeedsGetterOnly, item.NeedsSetterOnly); + if (item.NeedsGetterOnly) + { + AppendGetterOnlyIndexerInterfaces(sb, item.Arity); + } + + if (item.NeedsSetterOnly) + { + AppendSetterOnlyIndexerInterfaces(sb, item.Arity); + } } sb.AppendLine(); @@ -35,8 +46,9 @@ namespace Mockolate internal static class IndexerSetupExtensions { """).AppendLine(); - foreach (int item in indexerSetups) + foreach (IndexerSetupKey setup in indexerSetups) { + int item = setup.Arity; string types = GetGenericTypeParameters(item); sb.Append($$""" /// @@ -83,6 +95,62 @@ public void Forever() => setup.Only(1); } """).AppendLine(); + + if (setup.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.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,15 +159,36 @@ public void Forever() """).AppendLine(); sb.Append("namespace Mockolate.Interactions").AppendLine(); sb.Append("{").AppendLine(); - foreach (int count in indexerSetups) + 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.Any(v => v.NeedsGetterOnly || v.NeedsSetterOnly)) + { + sb.Append("namespace Mockolate.Verify").AppendLine(); + sb.Append("{").AppendLine(); + foreach (IndexerSetupKey item in indexerSetups) + { + if (item.NeedsGetterOnly) + { + AppendIndexerVerifyGetterResult(sb, item.Arity); + } + + if (item.NeedsSetterOnly) + { + AppendIndexerVerifySetterResult(sb, item.Arity, 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,599 @@ 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(); + } + + // 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.IsValue(value), 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..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 @@ -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.IsValue(value), 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..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; @@ -378,6 +379,366 @@ 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(); + } + + [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; } @@ -385,6 +746,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