File: ComponentFactoryTest.cs
Web Access
Project: src\src\Components\Components\test\Microsoft.AspNetCore.Components.Tests.csproj (Microsoft.AspNetCore.Components.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Reflection;
using Microsoft.AspNetCore.Components.RenderTree;
using Microsoft.AspNetCore.Components.Test.Helpers;
using Microsoft.Extensions.DependencyInjection;
 
namespace Microsoft.AspNetCore.Components;
 
public class ComponentFactoryTest
{
    [Fact]
    public void InstantiateComponent_CreatesInstance()
    {
        // Arrange
        var componentType = typeof(EmptyComponent);
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        Assert.IsType<EmptyComponent>(instance);
    }
 
    [Fact]
    public void InstantiateComponent_CreatesInstance_NonComponent()
    {
        // Arrange
        var componentType = typeof(List<string>);
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Assert
        var ex = Assert.Throws<ArgumentException>(() => factory.InstantiateComponent(GetServiceProvider(), componentType, null, null));
        Assert.StartsWith($"The type {componentType.FullName} does not implement {nameof(IComponent)}.", ex.Message);
    }
 
    [Fact]
    public void InstantiateComponent_CreatesInstance_WithCustomActivator()
    {
        // Arrange
        var componentType = typeof(EmptyComponent);
        var factory = new ComponentFactory(new CustomComponentActivator<ComponentWithInjectProperties>(), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        var component = Assert.IsType<ComponentWithInjectProperties>(instance); // Custom activator returns a different type
 
        // Public, and non-public properties, and properties with non-public setters should get assigned
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.GetProperty2());
        Assert.NotNull(component.Property3);
        Assert.NotNull(component.Property4);
    }
 
    [Fact]
    public void InstantiateComponent_ThrowsForNullInstance()
    {
        // Arrange
        var componentType = typeof(EmptyComponent);
        var factory = new ComponentFactory(new NullResultComponentActivator(), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Act
        var ex = Assert.Throws<InvalidOperationException>(() => factory.InstantiateComponent(GetServiceProvider(), componentType, null, null));
        Assert.Equal($"The component activator returned a null value for a component of type {componentType.FullName}.", ex.Message);
    }
 
    [Fact]
    public void InstantiateComponent_AssignsPropertiesWithInjectAttributeOnBaseType()
    {
        // Arrange
        var componentType = typeof(DerivedComponent);
        var factory = new ComponentFactory(new CustomComponentActivator<DerivedComponent>(), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(GetServiceProvider(), componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        var component = Assert.IsType<DerivedComponent>(instance);
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.GetProperty2());
        Assert.NotNull(component.Property3);
        Assert.NotNull(component.KeyedProperty);
 
        // Property on derived type without [Inject] should not be assigned
        Assert.Null(component.Property4);
        // Property on the base type with the [Inject] attribute should
        Assert.NotNull(((ComponentWithInjectProperties)component).Property4);
        // Property on derived type with [Inject] should be assigned
        Assert.NotNull(component.Property5);
    }
 
    [Fact]
    public void InstantiateComponent_IgnoresPropertiesWithoutInjectAttribute()
    {
        // Arrange
        var componentType = typeof(ComponentWithNonInjectableProperties);
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new DefaultComponentPropertyActivator(), new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        var component = Assert.IsType<ComponentWithNonInjectableProperties>(instance);
        // Public, and non-public properties, and properties with non-public setters should get assigned
        Assert.NotNull(component.Property1);
        Assert.Null(component.Property2);
    }
 
    [Fact]
    public void InstantiateComponent_WithNoRenderMode_DoesNotUseRenderModeResolver()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var renderer = new RendererWithResolveComponentForRenderMode(
            /* won't be used */ new ComponentWithRenderMode());
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(new DefaultComponentActivator(serviceProvider), new DefaultComponentPropertyActivator(), renderer);
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
 
        // Assert
        Assert.IsType<ComponentWithInjectProperties>(instance);
        Assert.False(renderer.ResolverWasCalled);
    }
 
    [Fact]
    public void InstantiateComponent_WithRenderModeOnComponent_UsesRenderModeResolver()
    {
        // Arrange
        var resolvedComponent = new ComponentWithInjectProperties();
        var componentType = typeof(ComponentWithRenderMode);
        var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
        var serviceProvider = GetServiceProvider();
        var componentActivator = new DefaultComponentActivator(serviceProvider);
        var factory = new ComponentFactory(componentActivator, new DefaultComponentPropertyActivator(), renderer);
 
        // Act
        var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(serviceProvider, componentType, null, 1234);
 
        // Assert
        Assert.True(renderer.ResolverWasCalled);
        Assert.Same(resolvedComponent, instance);
        Assert.NotNull(instance.Property1);
        Assert.Equal(componentType, renderer.RequestedComponentType);
        Assert.Equal(1234, renderer.SuppliedParentComponentId);
        Assert.Same(componentActivator, renderer.SuppliedActivator);
        Assert.IsType<TestRenderMode>(renderer.SuppliedRenderMode);
    }
 
    [Fact]
    public void InstantiateComponent_WithDerivedRenderModeOnDerivedComponent_CausesAmbiguousMatchException()
    {
        // We could allow derived components to override the rendermode, but:
        // [1] It's unclear how that would be legitimate. If the base specifies a rendermode, it's saying
        //     it only works in that mode. It wouldn't be safe for a derived type to change that.
        // [2] If we did want to implement this, we'd need to implement our own inheritance chain walking
        //     to make sure we find the rendermode from the *closest* ancestor type. GetCustomAttributes
        //     on its own isn't documented to return the results in any specific order.
        // Since issue [1] makes it unclear we'd want to support this, for now we don't.
 
        // Arrange
        var resolvedComponent = new ComponentWithInjectProperties();
        var componentType = typeof(DerivedComponentWithRenderMode);
        var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
        var serviceProvider = GetServiceProvider();
        var componentActivator = new DefaultComponentActivator(serviceProvider);
        var factory = new ComponentFactory(componentActivator, new DefaultComponentPropertyActivator(), renderer);
 
        // Act/Assert
        Assert.Throws<AmbiguousMatchException>(
            () => factory.InstantiateComponent(serviceProvider, componentType, null, 1234));
    }
 
    [Fact]
    public void InstantiateComponent_WithRenderModeOnCallSite_UsesRenderModeResolver()
    {
        // Arrange
        // Notice that the requested component type is not the same as the resolved component type. This
        // is intentional and shows that component factories are allowed to substitute other component types.
        var resolvedComponent = new ComponentWithInjectProperties();
        var componentType = typeof(ComponentWithNonInjectableProperties);
        var callSiteRenderMode = new TestRenderMode();
        var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
        var serviceProvider = GetServiceProvider();
        var componentActivator = new DefaultComponentActivator(serviceProvider);
        var factory = new ComponentFactory(componentActivator, new DefaultComponentPropertyActivator(), renderer);
 
        // Act
        var instance = (ComponentWithInjectProperties)factory.InstantiateComponent(serviceProvider, componentType, callSiteRenderMode, 1234);
 
        // Assert
        Assert.Same(resolvedComponent, instance);
        Assert.NotNull(instance.Property1);
        Assert.Equal(componentType, renderer.RequestedComponentType);
        Assert.Same(componentActivator, renderer.SuppliedActivator);
        Assert.Same(callSiteRenderMode, renderer.SuppliedRenderMode);
        Assert.Equal(1234, renderer.SuppliedParentComponentId);
    }
 
    [Fact]
    public void InstantiateComponent_WithRenderModeOnComponentAndCallSite_Throws()
    {
        // Arrange
        var resolvedComponent = new ComponentWithInjectProperties();
        var componentType = typeof(ComponentWithRenderMode);
        var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
        var serviceProvider = GetServiceProvider();
        var componentActivator = new DefaultComponentActivator(serviceProvider);
        var factory = new ComponentFactory(componentActivator, new DefaultComponentPropertyActivator(), renderer);
 
        // Even though the two rendermodes are literally the same object, we don't allow specifying any nonnull
        // rendermode at the callsite if there's a nonnull fixed rendermode
        var callsiteRenderMode = componentType.GetCustomAttribute<RenderModeAttribute>().Mode;
 
        // Act/Assert
        var ex = Assert.Throws<InvalidOperationException>(() =>
            factory.InstantiateComponent(GetServiceProvider(), componentType, callsiteRenderMode, 1234));
        Assert.Equal($"The component type '{componentType}' has a fixed rendermode of '{typeof(TestRenderMode)}', so it is not valid to specify any rendermode when using this component.", ex.Message);
    }
 
    [Fact]
    public void InstantiateComponent_CreatesInstance_WithTypeActivation()
    {
        // Arrange
        var serviceProvider = GetServiceProvider();
        var componentType = typeof(ComponentWithConstructorInjection);
        var resolvedComponent = new ComponentWithInjectProperties();
        var renderer = new RendererWithResolveComponentForRenderMode(resolvedComponent);
        var defaultComponentActivator = new DefaultComponentActivator(serviceProvider);
        var factory = new ComponentFactory(defaultComponentActivator, new DefaultComponentPropertyActivator(), renderer);
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        var component = Assert.IsType<ComponentWithConstructorInjection>(instance);
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.Property2);
        Assert.NotNull(component.Property3); // Property injection should still work.
    }
 
    [Fact]
    public void InstantiateComponent_WithCustomPropertyActivator_UsesCustomActivator()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var serviceProvider = GetServiceProvider();
        var customPropertyActivator = new CustomPropertyActivator();
        var factory = new ComponentFactory(
            new DefaultComponentActivator(serviceProvider),
            customPropertyActivator,
            new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, componentType, null, null);
 
        // Assert
        Assert.NotNull(instance);
        var component = Assert.IsType<ComponentWithInjectProperties>(instance);
        Assert.True(customPropertyActivator.GetActivatorCalled);
        Assert.Equal(componentType, customPropertyActivator.RequestedType);
        Assert.True(customPropertyActivator.ActivatorInvoked);
    }
 
    [Fact]
    public void InstantiateComponent_WithCustomPropertyActivator_ReceivesCorrectServiceProvider()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var serviceProvider = GetServiceProvider();
        var customPropertyActivator = new CustomPropertyActivator();
        var factory = new ComponentFactory(
            new DefaultComponentActivator(serviceProvider),
            customPropertyActivator,
            new TestRenderer());
 
        // Act
        factory.InstantiateComponent(serviceProvider, componentType, null, null);
 
        // Assert
        Assert.Same(serviceProvider, customPropertyActivator.ReceivedServiceProvider);
    }
 
    [Fact]
    public void InstantiateComponent_WithCustomPropertyActivator_WhenActivatorReturnsDifferentType_StillUsesCustomActivator()
    {
        // Arrange
        // The component activator returns a different type than requested
        var requestedType = typeof(EmptyComponent);
        var actualType = typeof(ComponentWithInjectProperties);
        var serviceProvider = GetServiceProvider();
        var customPropertyActivator = new CustomPropertyActivator();
        var factory = new ComponentFactory(
            new CustomComponentActivator<ComponentWithInjectProperties>(),
            customPropertyActivator,
            new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, requestedType, null, null);
 
        // Assert
        Assert.IsType<ComponentWithInjectProperties>(instance);
        // The property activator should be called with the actual type, not the requested type
        Assert.True(customPropertyActivator.GetActivatorCalled);
        // Since the types differ, GetActivator is called twice (once for requested, once for actual)
        // But the activator should have been invoked for the actual component type
        Assert.True(customPropertyActivator.ActivatorInvoked);
    }
 
    [Fact]
    public void InstantiateComponent_WhenActivatorReturnsSubtype_InjectsAllSubtypeProperties()
    {
        // Arrange
        // Request base type but activator returns derived type
        var requestedType = typeof(ComponentWithInjectProperties);
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(
            new CustomComponentActivator<DerivedComponent>(),
            new DefaultComponentPropertyActivator(),
            new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, requestedType, null, null);
 
        // Assert
        var component = Assert.IsType<DerivedComponent>(instance);
        
        // All base type properties with [Inject] should be injected
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.GetProperty2());
        Assert.NotNull(component.Property3);
        Assert.NotNull(((ComponentWithInjectProperties)component).Property4);
        Assert.NotNull(component.KeyedProperty);
        
        // Derived type properties with [Inject] should also be injected
        Assert.NotNull(component.Property5);
        
        // Derived type property without [Inject] should not be injected
        Assert.Null(component.Property4);
    }
 
    [Fact]
    public void InstantiateComponent_WhenActivatorReturnsExactType_InjectsPropertiesIdenticallyToSubtype()
    {
        // Arrange
        // This test validates that there's no difference in property injection behavior
        // whether the activator returns the exact type or a subtype
        var requestedType = typeof(DerivedComponent);
        var serviceProvider = GetServiceProvider();
        var factory = new ComponentFactory(
            new CustomComponentActivator<DerivedComponent>(),
            new DefaultComponentPropertyActivator(),
            new TestRenderer());
 
        // Act
        var instance = factory.InstantiateComponent(serviceProvider, requestedType, null, null);
 
        // Assert
        var component = Assert.IsType<DerivedComponent>(instance);
        
        // All base type properties with [Inject] should be injected
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.GetProperty2());
        Assert.NotNull(component.Property3);
        Assert.NotNull(((ComponentWithInjectProperties)component).Property4);
        Assert.NotNull(component.KeyedProperty);
        
        // Derived type properties with [Inject] should also be injected
        Assert.NotNull(component.Property5);
        
        // Derived type property without [Inject] should not be injected
        Assert.Null(component.Property4);
    }
 
    [Fact]
    public void DefaultComponentPropertyActivator_GetActivator_ReturnsActivatorThatInjectsProperties()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var serviceProvider = GetServiceProvider();
        var propertyActivator = new DefaultComponentPropertyActivator();
        var component = new ComponentWithInjectProperties();
 
        // Act
        var activator = propertyActivator.GetActivator(componentType);
        activator(serviceProvider, component);
 
        // Assert
        Assert.NotNull(component.Property1);
        Assert.NotNull(component.GetProperty2());
        Assert.NotNull(component.Property3);
        Assert.NotNull(component.Property4);
        Assert.NotNull(component.KeyedProperty);
    }
 
    [Fact]
    public void DefaultComponentPropertyActivator_GetActivator_CachesActivator()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var propertyActivator = new DefaultComponentPropertyActivator();
 
        // Act
        var activator1 = propertyActivator.GetActivator(componentType);
        var activator2 = propertyActivator.GetActivator(componentType);
 
        // Assert
        Assert.Same(activator1, activator2);
    }
 
    [Fact]
    public void DefaultComponentPropertyActivator_GetActivator_ReturnsNoOpForComponentWithoutInjectableProperties()
    {
        // Arrange
        var componentType = typeof(EmptyComponent);
        var serviceProvider = GetServiceProvider();
        var propertyActivator = new DefaultComponentPropertyActivator();
        var component = new EmptyComponent();
 
        // Act
        var activator = propertyActivator.GetActivator(componentType);
 
        // Should not throw
        activator(serviceProvider, component);
 
        // Assert - nothing to verify, just ensuring no exception
    }
 
    [Fact]
    public void DefaultComponentPropertyActivator_GetActivator_ThrowsForMissingService()
    {
        // Arrange
        var componentType = typeof(ComponentWithInjectProperties);
        var serviceProvider = new ServiceCollection().BuildServiceProvider(); // Empty provider
        var propertyActivator = new DefaultComponentPropertyActivator();
        var component = new ComponentWithInjectProperties();
 
        // Act
        var activator = propertyActivator.GetActivator(componentType);
        var ex = Assert.Throws<InvalidOperationException>(() => activator(serviceProvider, component));
 
        // Assert
        Assert.Contains("There is no registered service of type", ex.Message);
    }
 
    [Fact]
    public void DefaultComponentPropertyActivator_GetActivator_ThrowsForMissingKeyedService()
    {
        // Arrange
        var componentType = typeof(ComponentWithOnlyKeyedProperty);
        var serviceProvider = new ServiceCollection().BuildServiceProvider(); // Empty provider
        var propertyActivator = new DefaultComponentPropertyActivator();
        var component = new ComponentWithOnlyKeyedProperty();
 
        // Act
        var activator = propertyActivator.GetActivator(componentType);
        var ex = Assert.Throws<InvalidOperationException>(() => activator(serviceProvider, component));
 
        // Assert
        Assert.Contains("registered keyed service", ex.Message);
    }
 
    private const string KeyedServiceKey = "my-keyed-service";
 
    private static IServiceProvider GetServiceProvider()
    {
        return new ServiceCollection()
            .AddTransient<TestService1>()
            .AddTransient<TestService2>()
            .AddKeyedTransient<TestService3>(KeyedServiceKey)
            .BuildServiceProvider();
    }
 
    private class EmptyComponent : IComponent
    {
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
    }
 
    private class ComponentWithInjectProperties : IComponent
    {
        [Inject]
        public TestService1 Property1 { get; set; }
 
        [Inject]
        private TestService2 Property2 { get; set; }
 
        [Inject]
        public TestService1 Property3 { get; private set; }
 
        [Inject]
        public TestService1 Property4 { get; set; }
 
        [Inject(Key = KeyedServiceKey)]
        public TestService3 KeyedProperty { get; set; }
 
        public TestService2 GetProperty2() => Property2;
 
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
    }
 
    private class ComponentWithNonInjectableProperties : IComponent
    {
        [Inject]
        public TestService1 Property1 { get; set; }
 
        public TestService1 Property2 { get; set; }
 
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
    }
 
    public class ComponentWithConstructorInjection : IComponent
    {
        public ComponentWithConstructorInjection(TestService1 property1, TestService2 property2)
        {
            Property1 = property1;
            Property2 = property2;
        }
 
        public TestService1 Property1 { get; }
        public TestService2 Property2 { get; }
 
        [Inject]
        public TestService2 Property3 { get; set; }
 
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
    }
 
    private class DerivedComponent : ComponentWithInjectProperties
    {
        public new TestService2 Property4 { get; set; }
 
        [Inject]
        public TestService2 Property5 { get; set; }
    }
 
    public class TestService1 { }
    public class TestService2 { }
    public class TestService3 { }
 
    private class ComponentWithOnlyKeyedProperty : IComponent
    {
        [Inject(Key = KeyedServiceKey)]
        public TestService3 KeyedProperty { get; set; }
 
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
    }
 
    private class CustomComponentActivator<TResult> : IComponentActivator where TResult : IComponent, new()
    {
        public IComponent CreateInstance(Type componentType)
        {
            return new TResult();
        }
    }
 
    private class NullResultComponentActivator : IComponentActivator
    {
        public IComponent CreateInstance(Type componentType)
        {
            return null;
        }
    }
 
    private class CustomPropertyActivator : IComponentPropertyActivator
    {
        public bool GetActivatorCalled { get; private set; }
        public Type RequestedType { get; private set; }
        public bool ActivatorInvoked { get; private set; }
        public IServiceProvider ReceivedServiceProvider { get; private set; }
 
        public Action<IServiceProvider, IComponent> GetActivator(Type componentType)
        {
            GetActivatorCalled = true;
            RequestedType = componentType;
 
            // Return an activator that tracks invocation and delegates to the default
            var defaultActivator = new DefaultComponentPropertyActivator();
            var defaultAction = defaultActivator.GetActivator(componentType);
 
            return (sp, component) =>
            {
                ActivatorInvoked = true;
                ReceivedServiceProvider = sp;
                defaultAction(sp, component);
            };
        }
    }
 
    private class TestRenderMode : IComponentRenderMode { }
    private class DerivedComponentRenderMode : IComponentRenderMode { }
 
    [DerivedComponentRenderMode]
    private class DerivedComponentWithRenderMode : ComponentWithRenderMode
    {
        class DerivedComponentRenderModeAttribute : RenderModeAttribute
        {
            public override IComponentRenderMode Mode => new DerivedComponentRenderMode();
        }
    }
 
    [OwnRenderMode]
    private class ComponentWithRenderMode : IComponent
    {
        public void Attach(RenderHandle renderHandle)
        {
            throw new NotImplementedException();
        }
 
        public Task SetParametersAsync(ParameterView parameters)
        {
            throw new NotImplementedException();
        }
 
        class OwnRenderMode : RenderModeAttribute
        {
            public override IComponentRenderMode Mode => new TestRenderMode();
        }
    }
 
    private class RendererWithResolveComponentForRenderMode : TestRenderer
    {
        private readonly IComponent _componentToReturn;
 
        public RendererWithResolveComponentForRenderMode(IComponent componentToReturn) : base()
        {
            _componentToReturn = componentToReturn;
        }
 
        public bool ResolverWasCalled { get; private set; }
        public Type RequestedComponentType { get; private set; }
        public int? SuppliedParentComponentId { get; private set; }
        public IComponentActivator SuppliedActivator { get; private set; }
        public IComponentRenderMode SuppliedRenderMode { get; private set; }
 
        public override Dispatcher Dispatcher => throw new NotImplementedException();
 
        protected override void HandleException(Exception exception)
        {
            throw new NotImplementedException();
        }
 
        protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
        {
            throw new NotImplementedException();
        }
 
        protected internal override IComponent ResolveComponentForRenderMode(Type componentType, int? parentComponentId, IComponentActivator componentActivator, IComponentRenderMode renderMode)
        {
            ResolverWasCalled = true;
            RequestedComponentType = componentType;
            SuppliedParentComponentId = parentComponentId;
            SuppliedActivator = componentActivator;
            SuppliedRenderMode = renderMode;
            return _componentToReturn;
        }
    }
}