diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 19a770feef..3d08fa5b1b 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -55,6 +55,15 @@ Gets or sets a callback when a accordion item is changed. + + + + + + + + + Gets or sets the owning FluentTreeView. @@ -74,6 +83,11 @@ If both are set, this parameter will not be used. + + + Gets or sets the tooltip for the heading of the accordion item. + + Gets or sets a value indicating whether the item is expanded or collapsed. diff --git a/examples/Demo/Shared/Pages/Accordion/Examples/AccordionDefault.razor b/examples/Demo/Shared/Pages/Accordion/Examples/AccordionDefault.razor index 1eb5c0be29..a29209dbc4 100644 --- a/examples/Demo/Shared/Pages/Accordion/Examples/AccordionDefault.razor +++ b/examples/Demo/Shared/Pages/Accordion/Examples/AccordionDefault.razor @@ -10,10 +10,10 @@ Panel two content, using the 'end' slot for extra header content - + Panel three content - + Panel Four diff --git a/src/Core/Components/Accordion/FluentAccordionItem.razor.cs b/src/Core/Components/Accordion/FluentAccordionItem.razor.cs index ba3a603d71..9e16762560 100644 --- a/src/Core/Components/Accordion/FluentAccordionItem.razor.cs +++ b/src/Core/Components/Accordion/FluentAccordionItem.razor.cs @@ -3,11 +3,26 @@ // ------------------------------------------------------------------------ using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Extensions; +using Microsoft.JSInterop; namespace Microsoft.FluentUI.AspNetCore.Components; public partial class FluentAccordionItem : FluentComponentBase, IDisposable { + private const string JAVASCRIPT_FILE = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Accordion/FluentAccordionItem.razor.js"; + + /// + [Inject] + private LibraryConfiguration LibraryConfiguration { get; set; } = default!; + + /// + [Inject] + private IJSRuntime JSRuntime { get; set; } = default!; + + /// + private IJSObjectReference? Module { get; set; } + /// /// Gets or sets the owning FluentTreeView. /// @@ -30,6 +45,12 @@ public partial class FluentAccordionItem : FluentComponentBase, IDisposable [Parameter] public RenderFragment? HeadingTemplate { get; set; } + /// + /// Gets or sets the tooltip for the heading of the accordion item. + /// + [Parameter] + public string? HeadingTooltip { get; set; } + /// /// Gets or sets a value indicating whether the item is expanded or collapsed. /// @@ -65,6 +86,18 @@ protected override void OnInitialized() Owner?.Register(this); } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + if (HeadingTooltip != null && !string.IsNullOrEmpty(Id)) + { + Module ??= await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE.FormatCollocatedUrl(LibraryConfiguration)); + await Module.InvokeVoidAsync("setControlAttribute", Id, "title", HeadingTooltip); + } + } + } + private async Task HandleOnAccordionItemChangedAsync(AccordionChangeEventArgs args) { if (args is not null) diff --git a/src/Core/Components/Accordion/FluentAccordionItem.razor.js b/src/Core/Components/Accordion/FluentAccordionItem.razor.js new file mode 100644 index 0000000000..aa32ffb2cf --- /dev/null +++ b/src/Core/Components/Accordion/FluentAccordionItem.razor.js @@ -0,0 +1,7 @@ +export function setControlAttribute(id, attrName, value) { + const fieldElement = document.querySelector("#" + id)?.shadowRoot?.querySelector("[part='button']"); + + if (!!fieldElement) { + fieldElement?.setAttribute(attrName, value); + } +} diff --git a/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingAndTooltip.verified.html b/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingAndTooltip.verified.html new file mode 100644 index 0000000000..8bd07513d5 --- /dev/null +++ b/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingAndTooltip.verified.html @@ -0,0 +1,4 @@ + + + custom heading value + \ No newline at end of file diff --git a/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingTemplateAndTooltip.verified.html b/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingTemplateAndTooltip.verified.html new file mode 100644 index 0000000000..cf16a7fb55 --- /dev/null +++ b/tests/Core/Accordion/FluentAccordionItemTests.FluentAccordionItem_WithHeadingTemplateAndTooltip.verified.html @@ -0,0 +1,4 @@ + + +
custom heading content
+
\ No newline at end of file diff --git a/tests/Core/Accordion/FluentAccordionItemTests.cs b/tests/Core/Accordion/FluentAccordionItemTests.cs index cc8c934efe..082e8711bf 100644 --- a/tests/Core/Accordion/FluentAccordionItemTests.cs +++ b/tests/Core/Accordion/FluentAccordionItemTests.cs @@ -3,17 +3,32 @@ // ------------------------------------------------------------------------ using Bunit; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Accordion; -public class FluentAccordionItemTests : TestBase +public class FluentAccordionItemTests : TestContext { + private const string FluentAccordionItemRazorJs = "./_content/Microsoft.FluentUI.AspNetCore.Components/Components/Accordion/FluentAccordionItem.razor.js"; + + [Inject] + private LibraryConfiguration LibraryConfiguration { get; set; } = new LibraryConfiguration(); + + public FluentAccordionItemTests() + { + var script = JSInterop.SetupModule(FluentAccordionItemRazorJs); + script.SetupVoid("setControlAttribute", _ => true); + + Services.AddSingleton(LibraryConfiguration.ForUnitTests); + } + [Fact] public void FluentAccordionItem_WithChildContent_IsNull() { // Arrange & Act - var cut = TestContext.RenderComponent(); + var cut = RenderComponent(); // Assert cut.Verify(); @@ -23,7 +38,7 @@ public void FluentAccordionItem_WithChildContent_IsNull() public void FluentAccordionItem_WithProvided_Content() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.AddChildContent("child content"); }); @@ -35,8 +50,9 @@ public void FluentAccordionItem_WithProvided_Content() [Fact] public void FluentAccordionItem_WithCustomHeaderValue() { + // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Heading, "custom heading value"); }); @@ -49,7 +65,7 @@ public void FluentAccordionItem_WithCustomHeaderValue() public void FluentAccordionItem_WithHeadingTemplateAndHeading_IsProvidedBoth() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.HeadingTemplate, context => { @@ -63,13 +79,45 @@ public void FluentAccordionItem_WithHeadingTemplateAndHeading_IsProvidedBoth() cut.Verify(); } + [Fact] + public void FluentAccordionItem_WithHeadingAndTooltip() + { + // Arrange & Act + var cut = RenderComponent(parameters => + { + parameters.Add(p => p.Heading, "custom heading value"); + parameters.Add(p => p.HeadingTooltip, "my tooltip"); + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentAccordionItem_WithHeadingTemplateAndTooltip() + { + // Arrange & Act + var cut = RenderComponent(parameters => + { + parameters.Add(p => p.HeadingTemplate, context => + { + context.AddContent(0, "custom heading content"); + }); + + parameters.Add(p => p.HeadingTooltip, "my tooltip"); + }); + + // Assert + cut.Verify(); + } + [Theory] [InlineData(true)] [InlineData(false)] public void FluentAccordionItem_WithProvidedExpanded_Parameter(bool expanded) { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Expanded, expanded); }); @@ -82,7 +130,7 @@ public void FluentAccordionItem_WithProvidedExpanded_Parameter(bool expanded) public void FluentAccordionItem_WithAnAdditionalAttribute() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.AddUnmatched("unknown", "unknowns-value"); }); @@ -95,7 +143,7 @@ public void FluentAccordionItem_WithAnAdditionalAttribute() public void FluentAccordionItem_WithMultipleAdditionalAttributes() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.AddUnmatched("unknown1", "unknown1s-value"); parameters.AddUnmatched("unknown2", "unknown2s-value"); @@ -109,7 +157,7 @@ public void FluentAccordionItem_WithMultipleAdditionalAttributes() public void FluentAccordionItem_WhenAllParamsAdded_AndAdditionalAttributes_AndContent() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Expanded, true); parameters.Add(p => p.Heading, "custom heading value"); @@ -126,7 +174,7 @@ public void FluentAccordionItem_WhenAllParamsAdded_AndAdditionalAttributes_AndCo public void FluentAccordionItem_WhenAdditionalCSSClass_IsProvided() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Class, "additional-class"); }); @@ -139,7 +187,7 @@ public void FluentAccordionItem_WhenAdditionalCSSClass_IsProvided() public void FluentAccordionItem_WhenAdditionalStyle_IsProvided() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Style, "background-color: grey"); }); @@ -152,7 +200,7 @@ public void FluentAccordionItem_WhenAdditionalStyle_IsProvided() public void FluentAccordionItem_WithHeadingTemplate_IsNull() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.HeadingTemplate, context => { }); }); @@ -165,7 +213,7 @@ public void FluentAccordionItem_WithHeadingTemplate_IsNull() public void FluentAccordionItem_WithHeadingTemplate_IsProvided() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.HeadingTemplate, context => { diff --git a/tests/Core/Accordion/FluentAccordionTests.cs b/tests/Core/Accordion/FluentAccordionTests.cs index 55eb87cf7a..013c5b7a53 100644 --- a/tests/Core/Accordion/FluentAccordionTests.cs +++ b/tests/Core/Accordion/FluentAccordionTests.cs @@ -3,17 +3,23 @@ // ------------------------------------------------------------------------ using Bunit; +using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.FluentUI.AspNetCore.Components.Tests.Accordion; -public class FluentAccordionTests : TestBase +public class FluentAccordionTests : TestContext { + public FluentAccordionTests() + { + Services.AddSingleton(LibraryConfiguration.ForUnitTests); + } + [Fact] public void FluentAccordion_When_ChildContent_IsNull() { // Arrange & Act - var cut = TestContext.RenderComponent(); + var cut = RenderComponent(); // Assert cut.Verify(); @@ -23,7 +29,7 @@ public void FluentAccordion_When_ChildContent_IsNull() public void FluentAccordion_TheDefaultExpandMode_WhenExpandMode_IsNotSpecified() { // Arrange & Act - var cut = TestContext.RenderComponent(); + var cut = RenderComponent(); // Assert cut.Verify(); @@ -35,7 +41,7 @@ public void FluentAccordion_TheDefaultExpandMode_WhenExpandMode_IsNotSpecified() public void FluentAccordion_WhenExpandMode_IsSpecified(AccordionExpandMode accordionExpandMode) { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.ExpandMode, accordionExpandMode); }); @@ -48,7 +54,7 @@ public void FluentAccordion_WhenExpandMode_IsSpecified(AccordionExpandMode accor public void FluentAccordion_WhenAdditionalCSSClass_IsProvided() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Class, "additional-class"); }); @@ -61,7 +67,7 @@ public void FluentAccordion_WhenAdditionalCSSClass_IsProvided() public void FluentAccordion_WhenAdditionalStyle_IsProvided() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.Style, "background-color: grey"); }); @@ -74,7 +80,7 @@ public void FluentAccordion_WhenAdditionalStyle_IsProvided() public void FluentAccordion_WhenAdditionalParameters_AreAdded() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.AddUnmatched("unmatched1", "unmatched1-value"); parameters.AddUnmatched("unmatched2", "unmatched2-value"); @@ -88,7 +94,7 @@ public void FluentAccordion_WhenAdditionalParameters_AreAdded() public void FluentAccordion_WhenExpandedModeIsSingle_AndMultipleItemAreExpanded_ByDefault() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.ExpandMode, AccordionExpandMode.Single); parameters.AddChildContent(itemParams => itemParams.Add(p => p.Expanded, true)); @@ -103,14 +109,14 @@ public void FluentAccordion_WhenExpandedModeIsSingle_AndMultipleItemAreExpanded_ public void FluentAccordion_Dispose() { // Arrange & Act - var cut = TestContext.RenderComponent(parameters => + var cut = RenderComponent(parameters => { parameters.Add(p => p.ExpandMode, AccordionExpandMode.Single); parameters.AddChildContent(itemParams => itemParams.Add(p => p.Expanded, true)); parameters.AddChildContent(itemParams => itemParams.Add(p => p.Expanded, true)); }); - TestContext.DisposeComponents(); + DisposeComponents(); // Assert Assert.True(cut.IsDisposed);