diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 7b94efa69..ebcf504c5 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -17,11 +17,11 @@ "rollForward": false }, "dotnet-dump": { - "version": "8.0.532401", + "version": "8.0.547301", "commands": [ "dotnet-dump" ], "rollForward": false } } -} \ No newline at end of file +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e77d2e6e..8a852770f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,15 @@ All notable changes to **bUnit** will be documented in this file. The project ad ## [Unreleased] +### Added + +- `bunit.generators` respect parameters from the base class. +- Supports components using constructor injection in `net9.0`. + +### Fixed + +- Use latest `System.Text.Json` due to CVE in `8.0.4`. + ## [1.32.7] - 2024-10-04 ### Fixed diff --git a/Directory.Packages.props b/Directory.Packages.props index 3726679b1..9ea352b21 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + @@ -20,7 +20,7 @@ - + @@ -28,6 +28,11 @@ + + + + + @@ -40,9 +45,6 @@ - - - @@ -57,9 +59,6 @@ - - - @@ -73,9 +72,6 @@ - - - @@ -89,9 +85,6 @@ - - - @@ -105,9 +98,6 @@ - - - @@ -121,8 +111,6 @@ - - diff --git a/src/bunit.core/Rendering/BunitComponentActivator.cs b/src/bunit.core/Rendering/BunitComponentActivator.cs index e7afdd5c8..3d1e9c1de 100644 --- a/src/bunit.core/Rendering/BunitComponentActivator.cs +++ b/src/bunit.core/Rendering/BunitComponentActivator.cs @@ -7,10 +7,13 @@ internal class BunitComponentActivator : IComponentActivator private readonly ComponentFactoryCollection factories; private readonly IComponentActivator componentActivator; - public BunitComponentActivator(ComponentFactoryCollection factories, IComponentActivator? externalComponentActivator) + public BunitComponentActivator( + IServiceProvider serviceProvider, + ComponentFactoryCollection factories, + IComponentActivator? externalComponentActivator) { this.factories = factories ?? throw new ArgumentNullException(nameof(factories)); - this.componentActivator = externalComponentActivator ?? DefaultComponentActivator.Instance; + this.componentActivator = externalComponentActivator ?? new DefaultComponentActivator(serviceProvider); } public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType) @@ -43,12 +46,17 @@ public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessed private sealed class DefaultComponentActivator : IComponentActivator { - public static IComponentActivator Instance { get; } = new DefaultComponentActivator(); + private readonly IServiceProvider serviceProvider; + + public DefaultComponentActivator(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } /// public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType) - { - return (IComponent)Activator.CreateInstance(componentType)!; + { + return (IComponent)ActivatorUtilities.CreateInstance(serviceProvider, componentType); } } } diff --git a/src/bunit.core/Rendering/TestRenderer.cs b/src/bunit.core/Rendering/TestRenderer.cs index bf7449b4e..61d4deecf 100644 --- a/src/bunit.core/Rendering/TestRenderer.cs +++ b/src/bunit.core/Rendering/TestRenderer.cs @@ -79,7 +79,7 @@ public TestRenderer(IRenderedComponentActivator renderedComponentActivator, Test /// Initializes a new instance of the class. /// public TestRenderer(IRenderedComponentActivator renderedComponentActivator, TestServiceProvider services, ILoggerFactory loggerFactory) - : base(services, loggerFactory, new BunitComponentActivator(services.GetRequiredService(), null)) + : base(services, loggerFactory, new BunitComponentActivator(services, services.GetRequiredService(), null)) { logger = loggerFactory.CreateLogger(); this.activator = renderedComponentActivator; @@ -89,7 +89,7 @@ public TestRenderer(IRenderedComponentActivator renderedComponentActivator, Test /// Initializes a new instance of the class. /// public TestRenderer(IRenderedComponentActivator renderedComponentActivator, TestServiceProvider services, ILoggerFactory loggerFactory, IComponentActivator componentActivator) - : base(services, loggerFactory, new BunitComponentActivator(services.GetRequiredService(), componentActivator)) + : base(services, loggerFactory, new BunitComponentActivator(services, services.GetRequiredService(), componentActivator)) { logger = loggerFactory.CreateLogger(); this.activator = renderedComponentActivator; diff --git a/src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/AddStubGenerator.cs b/src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/AddStubGenerator.cs index 6c7933786..c7fadff33 100644 --- a/src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/AddStubGenerator.cs +++ b/src/bunit.generators/Web.Stubs/AddStubMethodStubGenerator/AddStubGenerator.cs @@ -31,8 +31,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static AddStubClassInfo? GetStubClassInfo(GeneratorSyntaxContext context) { - var invocation = context.Node as InvocationExpressionSyntax; - if (invocation is null || !IsComponentFactoryStubMethod(invocation, context.SemanticModel)) + if (context.Node is not InvocationExpressionSyntax invocation || !IsComponentFactoryStubMethod(invocation, context.SemanticModel)) { return null; } @@ -56,7 +55,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var line = lineSpan.StartLinePosition.Line + 1; var column = lineSpan.Span.Start.Character + context.Node.ToString().IndexOf("AddStub", StringComparison.Ordinal) + 1; - var properties = symbol.GetMembers() + var properties = symbol.GetAllMembersRecursively() .OfType() .Where(IsParameterOrCascadingParameter) .Select(CreateFromProperty) diff --git a/src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs b/src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs index d36fcf184..ee778bb1f 100644 --- a/src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs +++ b/src/bunit.generators/Web.Stubs/AttributeStubGenerator/ComponentStubAttributeGenerator.cs @@ -62,7 +62,7 @@ private static bool IsClassWithComponentStubAttribute(SyntaxNode s) => } var parameter = attribute.AttributeClass!.TypeArguments - .SelectMany(s => s.GetMembers()) + .SelectMany(s => s.GetAllMembersRecursively()) .OfType() .Where(IsParameterOrCascadingParameter) .Select(CreateFromProperty) diff --git a/src/bunit.generators/Web.Stubs/MemberRetriever.cs b/src/bunit.generators/Web.Stubs/MemberRetriever.cs new file mode 100644 index 000000000..00ce8950f --- /dev/null +++ b/src/bunit.generators/Web.Stubs/MemberRetriever.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; + +namespace Bunit.Web.Stubs; + +internal static class MemberRetriever +{ + public static IEnumerable GetAllMembersRecursively(this ITypeSymbol type) + { + var currentType = type; + while (currentType is not null) + { + foreach (var member in currentType.GetMembers()) + { + yield return member; + } + currentType = currentType.BaseType; + } + } +} diff --git a/tests/bunit.generators.tests/Web.Stub/AddStubGeneratorTests.cs b/tests/bunit.generators.tests/Web.Stub/AddStubGeneratorTests.cs index df54ed6f2..17c3dc44c 100644 --- a/tests/bunit.generators.tests/Web.Stub/AddStubGeneratorTests.cs +++ b/tests/bunit.generators.tests/Web.Stub/AddStubGeneratorTests.cs @@ -61,7 +61,42 @@ public void Generated_stub_via_attribute_has_same_parameters() var stub = cut.FindComponent(); Assert.Equal("test", stub.Instance.Text); } + + [Fact] + public void Generated_Stub_respects_base_class_parameters() + { + ComponentFactories.AddStub(); + + var cut = RenderComponent(c => c.AddChildContent()); + + var stub = cut.FindComponent(); + Assert.Equal(0, stub.Instance.BaseCount); + } + + [Fact] + public void Generated_stub_via_attribute_respects_base_class_parameters() + { + ComponentFactories.Add(); + + var cut = RenderComponent(c => c.AddChildContent()); + + var stub = cut.FindComponent(); + Assert.Equal(0, stub.Instance.BaseCount); + } } [ComponentStub] -public partial class ButtonComponentStub; \ No newline at end of file +public partial class ButtonComponentStub; + +public abstract class BaseComponent : ComponentBase +{ + [Parameter] public int BaseCount { get; set; } +} + +public class DerivedComponent : BaseComponent +{ + [Parameter] public int DerivedCount { get; set; } +} + +[ComponentStub] +public partial class DerivedComponentStubViaAttributeAnnotation; \ No newline at end of file diff --git a/tests/bunit.generators.tests/Web.Stub/Components/ContainerComponent.cs b/tests/bunit.generators.tests/Web.Stub/Components/ContainerComponent.cs new file mode 100644 index 000000000..5cbbbb298 --- /dev/null +++ b/tests/bunit.generators.tests/Web.Stub/Components/ContainerComponent.cs @@ -0,0 +1,14 @@ +namespace Bunit.Web.Stub.Components; + +public class ContainerComponent : ComponentBase +{ + [Parameter] + public RenderFragment ChildContent { get; set; } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, "div"); + builder.AddContent(1, ChildContent); + builder.CloseElement(); + } +} diff --git a/tests/bunit.testassets/SampleComponents/ConstructorInjectionComponent.cs b/tests/bunit.testassets/SampleComponents/ConstructorInjectionComponent.cs new file mode 100644 index 000000000..7b99ac4b1 --- /dev/null +++ b/tests/bunit.testassets/SampleComponents/ConstructorInjectionComponent.cs @@ -0,0 +1,13 @@ +using Microsoft.JSInterop; + +namespace Bunit.TestAssets.SampleComponents; + +public class ConstructorInjectionComponent : ComponentBase +{ + public IJSRuntime JSRuntime { get; } + + public ConstructorInjectionComponent(IJSRuntime jsRuntime) + { + JSRuntime = jsRuntime; + } +} diff --git a/tests/bunit.web.tests/Rendering/RenderedComponentTest.cs b/tests/bunit.web.tests/Rendering/RenderedComponentTest.cs index a4dd4a0d9..412779aad 100644 --- a/tests/bunit.web.tests/Rendering/RenderedComponentTest.cs +++ b/tests/bunit.web.tests/Rendering/RenderedComponentTest.cs @@ -57,4 +57,14 @@ public void Test020() Should.Throw(() => target.Instance); } + #if NET9_0_OR_GREATER + + [Fact(DisplayName = "Component with constructor dependencies is resolved when rendered")] + public void Test021() + { + var cut = RenderComponent(); + + cut.Instance.JSRuntime.ShouldNotBeNull(); + } + #endif } diff --git a/version.json b/version.json index 556cfe4ec..fc864ecdf 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.32", + "version": "1.33", "assemblyVersion": { "precision": "revision" },