diff --git a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml index 88f4f1798e..bfadef81a7 100644 --- a/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml +++ b/examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml @@ -8695,6 +8695,16 @@ + + + Initializes a new instance of the class. + + + + + Gets or sets the list of sub-items to bind to the tree item + + Gets or sets the text of the tree item @@ -8743,6 +8753,20 @@ be selected when it is created. + + + Gets or sets the displayed at the start of tree item, + when the node is collapsed. + If this icon is not set, the will be used. + + + + + Gets or sets the displayed at the start of tree item, + when the node is expanded. + If this icon is not set, the will be used. + + Gets or sets the owning FluentTreeView @@ -8754,6 +8778,23 @@ and if expanded. + + + Gets or sets the list of items to bind to the tree. + + + + + Gets or sets the currently selected tree item. + Only when using the property. + + + + + Called when changes. + Only when using the property. + + Gets or sets whether the tree should render nodes under collapsed items @@ -8768,6 +8809,7 @@ Called when changes. + You cannot update properties. @@ -8779,12 +8821,177 @@ Called whenever changes on an item within the tree. + You cannot update properties. Called whenever changes on an item within the tree. + You cannot update properties. + + + + + Gets or sets the template for rendering tree items. + + + + + Can only be used when the is defined. + Gets or sets whether the tree should use lazy loading when expanding nodes. + If True, the tree will only render the children of a node when it is expanded and will remove them when it is collapsed. + + + + + Search for an item by its id in the tree + + + + + + + + Interface for tree view items + + + + + Gets or sets the unique identifier of the tree item. + + + + + Gets or sets the text of the tree item. + + + + + Gets or sets the sub-items of the tree item. + + + + + Gets or sets the displayed at the start of tree item, + when the node is collapsed. + If this icon is not set, the will be used. + + + + + Gets or sets the displayed at the start of tree item, + when the node is expanded. + If this icon is not set, the will be used. + + + + + When true, the control will be immutable by user interaction. + See disabled HTML attribute for more information. + + + + + Gets or sets a value indicating whether the tree item is expanded. + + + + + Gets or sets the action to be performed when the tree item is expanded or collapsed + + + + + Implementation of + + + + + Returns a that represents a loading state. + + + + + Returns an array with a single that represents a loading state. + + + + + Initializes a new instance of the class. + + + + + Initializes a new instance of the class. + + Text of the tree item + Sub-items of the tree item. + + + + Initializes a new instance of the class. + + Unique identifier of the tree item + Text of the tree item + Sub-items of the tree item. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Class that contains the event arguments for the event. + + + + + + + + Gets the that was expanded or collapsed. + + + + + Gets a value indicating whether the item was expanded or collapsed. diff --git a/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewFlat.razor b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewFlat.razor index 20bca96795..2d382dffba 100644 --- a/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewFlat.razor +++ b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewFlat.razor @@ -1,8 +1,8 @@ - + Daisy Sunflower Rose Petunia Tulip - \ No newline at end of file + diff --git a/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithItems.razor b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithItems.razor new file mode 100644 index 0000000000..90198871f3 --- /dev/null +++ b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithItems.razor @@ -0,0 +1,55 @@ + + + @context.Text + + @(context.Items == null ? "" : $"[{context.Items.Count()}]") + + + + +
+ Total items: @Count +
+
+ Selected item: @SelectedItem?.Text +
+ +@code +{ + private ITreeViewItem? SelectedItem; + + private Icon IconCollapsed = new Icons.Regular.Size20.Folder(); + private Icon IconExpanded = new Icons.Regular.Size20.FolderOpen(); + + private int Count = -1; + private IEnumerable? Items = new List(); + + protected override void OnInitialized() + { + Items = CreateTree(maxLevel: 5, maxItemsPerLevel: 12).Items; + SelectedItem = Items?.ElementAt(3); + } + + // Recursive method to create tree + private TreeViewItem CreateTree(int maxLevel, int maxItemsPerLevel, int level = 0) + { + Count++; + + int nbItems = Random.Shared.Next(maxItemsPerLevel - 3, maxItemsPerLevel); + + var treeItem = new TreeViewItem + { + Text = $"Item {Count}", + Disabled = level >= 2 && Count % 7 == 0, + IconCollapsed = IconCollapsed, + IconExpanded = IconExpanded, + Expanded = level >= 2 && Count % 5 == 0, + Items = level == maxLevel + ? null + : new List(Enumerable.Range(1, nbItems) + .Select(i => CreateTree(maxLevel, maxItemsPerLevel, level + 1))), + }; + + return treeItem; + } +} diff --git a/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithUnlimitedItems.razor b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithUnlimitedItems.razor new file mode 100644 index 0000000000..a5882613d9 --- /dev/null +++ b/examples/Demo/Shared/Pages/TreeView/Examples/TreeViewWithUnlimitedItems.razor @@ -0,0 +1,43 @@ + + +
+ Selected Item: @SelectedItem?.Text +
+ +@code +{ + private ITreeViewItem? SelectedItem; + private IEnumerable? Items = new List(); + + protected override void OnInitialized() + { + Items = GetItems(); + } + + private Task OnExpandedAsync(TreeViewItemExpandedEventArgs e) + { + if (e.Expanded) + { + e.CurrentItem.Items = GetItems(); + } + else + { + // Remove sub-items and add a "Fake" item to simulate the [+] + e.CurrentItem.Items = TreeViewItem.LoadingTreeViewItems; + } + + return Task.CompletedTask; + } + + private IEnumerable GetItems() + { + var nbItems = Random.Shared.Next(3, 9); + + return Enumerable.Range(1, nbItems).Select(i => new TreeViewItem() + { + Text = $"Item {Random.Shared.Next(1, 9999)}", + OnExpandedAsync = OnExpandedAsync, + Items = TreeViewItem.LoadingTreeViewItems, // "Fake" sub-item to simulate the [+] + }).ToArray(); + } +} diff --git a/examples/Demo/Shared/Pages/TreeView/TreeViewPage.razor b/examples/Demo/Shared/Pages/TreeView/TreeViewPage.razor index ce1f3b1648..9a943ce095 100644 --- a/examples/Demo/Shared/Pages/TreeView/TreeViewPage.razor +++ b/examples/Demo/Shared/Pages/TreeView/TreeViewPage.razor @@ -24,6 +24,10 @@ + + + + diff --git a/src/Core/Components/TreeView/FluentTreeItem.razor b/src/Core/Components/TreeView/FluentTreeItem.razor index f628568bcb..39390d0fd7 100644 --- a/src/Core/Components/TreeView/FluentTreeItem.razor +++ b/src/Core/Components/TreeView/FluentTreeItem.razor @@ -1,4 +1,4 @@ -@namespace Microsoft.FluentUI.AspNetCore.Components +@namespace Microsoft.FluentUI.AspNetCore.Components @inherits FluentComponentBase - @if (!string.IsNullOrWhiteSpace(Text)) + + @if (Owner?.ItemTemplate == null && !string.IsNullOrWhiteSpace(Text)) { @Text } + + @if (IconExpanded != null || IconCollapsed != null) + { + + } + @ChildContent + + @if (Owner != null && Items != null) + { + @if (Owner.LazyLoadItems && Items.Any() && !Expanded) + { + @* Lazy loading required a "fake" sub-item to simulate the [+] *@ + @FluentTreeView.LoadingMessage + } + else + { + foreach (var item in Items) + { + @FluentTreeItem.GetFluentTreeItem(Owner, item) + } + } + } + diff --git a/src/Core/Components/TreeView/FluentTreeItem.razor.cs b/src/Core/Components/TreeView/FluentTreeItem.razor.cs index 853f22972d..3c4007b626 100644 --- a/src/Core/Components/TreeView/FluentTreeItem.razor.cs +++ b/src/Core/Components/TreeView/FluentTreeItem.razor.cs @@ -6,6 +6,20 @@ public partial class FluentTreeItem : FluentComponentBase, IDisposable { private bool _disposed; + /// + /// Initializes a new instance of the class. + /// + public FluentTreeItem() + { + Id = Identifier.NewId(); + } + + /// + /// Gets or sets the list of sub-items to bind to the tree item + /// + [Parameter] + public IEnumerable? Items { get; set; } + /// /// Gets or sets the text of the tree item /// @@ -63,11 +77,27 @@ public partial class FluentTreeItem : FluentComponentBase, IDisposable [Parameter] public bool InitiallySelected { get; set; } + /// + /// Gets or sets the displayed at the start of tree item, + /// when the node is collapsed. + /// If this icon is not set, the will be used. + /// + [Parameter] + public Icon? IconCollapsed { get; set; } + + /// + /// Gets or sets the displayed at the start of tree item, + /// when the node is expanded. + /// If this icon is not set, the will be used. + /// + [Parameter] + public Icon? IconExpanded { get; set; } + /// /// Gets or sets the owning FluentTreeView /// [CascadingParameter] - private FluentTreeView Owner { get; set; } = default!; + private FluentTreeView? Owner { get; set; } /// /// Returns if the tree item is collapsed, @@ -75,11 +105,6 @@ public partial class FluentTreeItem : FluentComponentBase, IDisposable /// public bool Collapsed => !Expanded; - public FluentTreeItem() - { - Id = Identifier.NewId(); - } - void IDisposable.Dispose() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method @@ -110,6 +135,7 @@ internal async Task SetSelectedAsync(bool value) } Selected = value; + if (SelectedChanged.HasDelegate) { await SelectedChanged.InvokeAsync(Selected); @@ -119,7 +145,9 @@ internal async Task SetSelectedAsync(bool value) protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); - Owner.Register(this); + + Owner?.Register(this); + if (InitiallyExpanded && !Expanded) { Expanded = true; @@ -128,13 +156,14 @@ protected override async Task OnInitializedAsync() await ExpandedChanged.InvokeAsync(true); } } + if (InitiallySelected) { await SetSelectedAsync(true); } } - private async Task HandleExpandedChangeAsync(TreeChangeEventArgs args) + internal async Task HandleExpandedChangeAsync(TreeChangeEventArgs args) { if (args.AffectedId != Id || args.Expanded is null || args.Expanded == Expanded) { @@ -142,29 +171,59 @@ private async Task HandleExpandedChangeAsync(TreeChangeEventArgs args) } Expanded = args.Expanded.Value; + if (ExpandedChanged.HasDelegate) { await ExpandedChanged.InvokeAsync(Expanded); } - if (Owner is FluentTreeView tree) + if (Owner != null) { - await tree.ItemExpandedChangeAsync(this); + await Owner.ItemExpandedChangeAsync(this); } } - private async Task HandleSelectedChangeAsync(TreeChangeEventArgs args) + internal async Task HandleSelectedChangeAsync(TreeChangeEventArgs args) { if (args.AffectedId != Id || args.Selected is null || args.Selected == Selected) { return; } - await SetSelectedAsync(args.Selected.Value); + if (Owner?.Items == null) + { + await SetSelectedAsync(args.Selected.Value); + } - if (Owner is FluentTreeView tree) + if (Owner != null) { - await tree.ItemSelectedChangeAsync(this); + await Owner.ItemSelectedChangeAsync(this); } } + + internal static RenderFragment GetFluentTreeItem(FluentTreeView owner, ITreeViewItem item) + { + RenderFragment fluentTreeItem = builder => + { + int i = 0; + builder.OpenComponent(i++); + builder.AddAttribute(i++, "Id", item.Id); + builder.AddAttribute(i++, "Items", item.Items); + builder.AddAttribute(i++, "Text", item.Text); + builder.AddAttribute(i++, "Selected", owner.SelectedItem == item); + builder.AddAttribute(i++, "Expanded", item.Expanded); + builder.AddAttribute(i++, "Disabled", item.Disabled); + builder.AddAttribute(i++, "IconCollapsed", item.IconCollapsed); + builder.AddAttribute(i++, "IconExpanded", item.IconExpanded); + + if (owner.ItemTemplate != null) + { + builder.AddAttribute(i++, "ChildContent", owner.ItemTemplate(item)); + } + + builder.CloseComponent(); + }; + + return fluentTreeItem; + } } diff --git a/src/Core/Components/TreeView/FluentTreeView.razor b/src/Core/Components/TreeView/FluentTreeView.razor index a978128a4e..28a0b200d8 100644 --- a/src/Core/Components/TreeView/FluentTreeView.razor +++ b/src/Core/Components/TreeView/FluentTreeView.razor @@ -1,5 +1,8 @@ -@namespace Microsoft.FluentUI.AspNetCore.Components +@namespace Microsoft.FluentUI.AspNetCore.Components @inherits FluentComponentBase +@{ +#pragma warning disable CS0618 // Disable CS0618 warning for obsolete "RenderCollapsedNodes" +} @ChildContent + + @if (Items != null) + { + foreach (var item in Items) + { + @FluentTreeItem.GetFluentTreeItem(this, item) + } + } diff --git a/src/Core/Components/TreeView/FluentTreeView.razor.cs b/src/Core/Components/TreeView/FluentTreeView.razor.cs index 7bc495d2d9..16abfc8d61 100644 --- a/src/Core/Components/TreeView/FluentTreeView.razor.cs +++ b/src/Core/Components/TreeView/FluentTreeView.razor.cs @@ -10,11 +10,34 @@ public partial class FluentTreeView : FluentComponentBase, IDisposable private readonly Debouncer _currentSelectedChangedDebouncer = new(); private bool _disposed; + public static string LoadingMessage = "Loading..."; + + /// + /// Gets or sets the list of items to bind to the tree. + /// + [Parameter] + public IEnumerable? Items { get; set; } + + /// + /// Gets or sets the currently selected tree item. + /// Only when using the property. + /// + [Parameter] + public ITreeViewItem? SelectedItem { get; set; } + + /// + /// Called when changes. + /// Only when using the property. + /// + [Parameter] + public EventCallback SelectedItemChanged { get; set; } + /// /// Gets or sets whether the tree should render nodes under collapsed items /// Defaults to false /// [Parameter] + [Obsolete("Please use the 'LazyLoadItems' parameter instead.")] public bool RenderCollapsedNodes { get; set; } /// @@ -25,6 +48,7 @@ public partial class FluentTreeView : FluentComponentBase, IDisposable /// /// Called when changes. + /// You cannot update properties. /// [Parameter] public EventCallback CurrentSelectedChanged { get; set; } @@ -38,6 +62,7 @@ public partial class FluentTreeView : FluentComponentBase, IDisposable /// /// Called whenever changes on an /// item within the tree. + /// You cannot update properties. /// [Parameter] public EventCallback OnSelectedChange { get; set; } @@ -45,10 +70,25 @@ public partial class FluentTreeView : FluentComponentBase, IDisposable /// /// Called whenever changes on an /// item within the tree. + /// You cannot update properties. /// [Parameter] public EventCallback OnExpandedChange { get; set; } + /// + /// Gets or sets the template for rendering tree items. + /// + [Parameter] + public RenderFragment? ItemTemplate { get; set; } + + /// + /// Can only be used when the is defined. + /// Gets or sets whether the tree should use lazy loading when expanding nodes. + /// If True, the tree will only render the children of a node when it is expanded and will remove them when it is collapsed. + /// + [Parameter] + public bool LazyLoadItems { get; set; } = false; + [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(TreeChangeEventArgs))] public FluentTreeView() { @@ -67,6 +107,23 @@ internal async Task ItemExpandedChangeAsync(FluentTreeItem item) { await OnExpandedChange.InvokeAsync(item); } + + if (Items != null) + { + var currentTreeItem = FindItemById(Items, item.Id); + + if (currentTreeItem != null) + { + currentTreeItem.Expanded = item.Expanded; + + if (currentTreeItem.OnExpandedAsync != null) + { + await currentTreeItem.OnExpandedAsync(new TreeViewItemExpandedEventArgs(currentTreeItem, item.Expanded)); + } + + await InvokeAsync(StateHasChanged); + } + } } internal async Task ItemSelectedChangeAsync(FluentTreeItem item) @@ -89,7 +146,7 @@ internal void Unregister(FluentTreeItem fluentTreeItem) _allItems.Remove(fluentTreeItem.Id!); } - private async Task HandleCurrentSelectedChangeAsync(TreeChangeEventArgs args) + internal async Task HandleCurrentSelectedChangeAsync(TreeChangeEventArgs args) { if (!_allItems.TryGetValue(args.AffectedId!, out FluentTreeItem? treeItem)) { @@ -111,6 +168,16 @@ private async Task HandleCurrentSelectedChangeAsync(TreeChangeEventArgs args) } await CurrentSelectedChanged.InvokeAsync(CurrentSelected); } + + if (Items != null) + { + SelectedItem = args.Selected == true ? FindItemById(Items, args.AffectedId) : null; + + if (SelectedItemChanged.HasDelegate) + { + await SelectedItemChanged.InvokeAsync(SelectedItem); + } + } })); } @@ -130,4 +197,33 @@ protected virtual void Dispose(bool disposing) _disposed = true; } + /// + /// Search for an item by its id in the tree + /// + /// + /// + /// + private ITreeViewItem? FindItemById(IEnumerable? items, string? id) + { + if (items == null) + { + return null; + } + + foreach (var item in items) + { + if (item.Id == id) + { + return item; + } + + var nestedItem = FindItemById(item.Items, id); + if (nestedItem != null) + { + return nestedItem; + } + } + + return null; + } } diff --git a/src/Core/Components/TreeView/ITreeViewItem.cs b/src/Core/Components/TreeView/ITreeViewItem.cs new file mode 100644 index 0000000000..8b885c7484 --- /dev/null +++ b/src/Core/Components/TreeView/ITreeViewItem.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Interface for tree view items +/// +public interface ITreeViewItem +{ + /// + /// Gets or sets the unique identifier of the tree item. + /// + string Id { get; set; } + + /// + /// Gets or sets the text of the tree item. + /// + string Text { get; set; } + + /// + /// Gets or sets the sub-items of the tree item. + /// + IEnumerable? Items { get; set; } + + /// + /// Gets or sets the displayed at the start of tree item, + /// when the node is collapsed. + /// If this icon is not set, the will be used. + /// + Icon? IconCollapsed { get; set; } + + /// + /// Gets or sets the displayed at the start of tree item, + /// when the node is expanded. + /// If this icon is not set, the will be used. + /// + Icon? IconExpanded { get; set; } + + /// + /// When true, the control will be immutable by user interaction. + /// See disabled HTML attribute for more information. + /// + bool Disabled { get; set; } + + /// + /// Gets or sets a value indicating whether the tree item is expanded. + /// + bool Expanded { get; set; } + + /// + /// Gets or sets the action to be performed when the tree item is expanded or collapsed + /// + Func? OnExpandedAsync { get; set; } +} diff --git a/src/Core/Components/TreeView/TreeViewItem.cs b/src/Core/Components/TreeView/TreeViewItem.cs new file mode 100644 index 0000000000..aea8e1be23 --- /dev/null +++ b/src/Core/Components/TreeView/TreeViewItem.cs @@ -0,0 +1,93 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Implementation of +/// +public class TreeViewItem : ITreeViewItem +{ + /// + /// Returns a that represents a loading state. + /// + public static TreeViewItem LoadingTreeViewItem => new TreeViewItem() { Text = FluentTreeView.LoadingMessage, Disabled = true }; + + /// + /// Returns an array with a single that represents a loading state. + /// + public static IEnumerable LoadingTreeViewItems => new[] { new TreeViewItem() { Text = FluentTreeView.LoadingMessage, Disabled = true } }; + + /// + /// Initializes a new instance of the class. + /// + public TreeViewItem() + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// Text of the tree item + /// Sub-items of the tree item. + public TreeViewItem(string text, IEnumerable? items = null) + { + Text = text; + Items = items; + } + + /// + /// Initializes a new instance of the class. + /// + /// Unique identifier of the tree item + /// Text of the tree item + /// Sub-items of the tree item. + public TreeViewItem(string id, string text, IEnumerable? items = null) + { + Id = id; + Text = text; + Items = items; + } + + /// + /// + /// + public string Id { get; set; } = Identifier.NewId(); + + /// + /// + /// + public string Text { get; set; } = string.Empty; + + /// + /// + /// + public IEnumerable? Items { get; set; } + + /// + /// + /// + public Icon? IconCollapsed { get; set; } + + /// + /// + /// + public Icon? IconExpanded { get; set; } + + /// + /// + /// + public bool Disabled { get; set; } = false; + + /// + /// + /// + public bool Expanded { get; set; } = false; + + /// + /// + /// + public Func? OnExpandedAsync { get; set; } +} diff --git a/src/Core/Components/TreeView/TreeViewItemExpandedEventArgs.cs b/src/Core/Components/TreeView/TreeViewItemExpandedEventArgs.cs new file mode 100644 index 0000000000..95ffa88ad2 --- /dev/null +++ b/src/Core/Components/TreeView/TreeViewItemExpandedEventArgs.cs @@ -0,0 +1,28 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components; + +/// +/// Class that contains the event arguments for the event. +/// +public class TreeViewItemExpandedEventArgs +{ + /// + internal TreeViewItemExpandedEventArgs(ITreeViewItem item, bool expanded) + { + CurrentItem = item; + Expanded = expanded; + } + + /// + /// Gets the that was expanded or collapsed. + /// + public ITreeViewItem CurrentItem { get; } + + /// + /// Gets a value indicating whether the item was expanded or collapsed. + /// + public bool Expanded { get; } +} diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Default.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Default.verified.razor.html new file mode 100644 index 0000000000..a275c07371 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Default.verified.razor.html @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Disabled.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Disabled.verified.razor.html new file mode 100644 index 0000000000..ed7c644e9f --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Disabled.verified.razor.html @@ -0,0 +1,27 @@ + + + + + diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Icons.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Icons.verified.razor.html new file mode 100644 index 0000000000..c3a8483734 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Icons.verified.razor.html @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_ItemTemplate.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_ItemTemplate.verified.razor.html new file mode 100644 index 0000000000..13d4b23fb6 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_ItemTemplate.verified.razor.html @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading.verified.razor.html new file mode 100644 index 0000000000..54896a41a4 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading.verified.razor.html @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading_Expanded.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading_Expanded.verified.razor.html new file mode 100644 index 0000000000..8ae152d518 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_LazyLoading_Expanded.verified.razor.html @@ -0,0 +1,19 @@ + + + + Item 1 + + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Selected.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Selected.verified.razor.html new file mode 100644 index 0000000000..a275c07371 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_Selected.verified.razor.html @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem.verified.razor.html new file mode 100644 index 0000000000..71bc4369c5 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem.verified.razor.html @@ -0,0 +1,27 @@ + + + + + \ No newline at end of file diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem_Change.verified.razor.html b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem_Change.verified.razor.html new file mode 100644 index 0000000000..161693c0c7 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.FluentTreeViewItems_SelectedItem_Change.verified.razor.html @@ -0,0 +1,27 @@ + + + + + diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.razor b/tests/Core/TreeView/FluentTreeViewItemsTests.razor new file mode 100644 index 0000000000..83a4e6ae1e --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.razor @@ -0,0 +1,189 @@ +@using Xunit; +@inherits TestContext +@code +{ + [Fact] + public void FluentTreeViewItems_Default() + { + // Arrange && Act + var cut = Render(@); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_LazyLoading() + { + // Arrange && Act + var cut = Render(@); + + // Assert + cut.Verify(); + } + + [Fact] + public async Task FluentTreeViewItems_LazyLoading_Expanded() + { + // Arrange && Act + var cut = Render(@); + + // Act + var first = cut.FindComponent(); + await first.Instance.HandleExpandedChangeAsync(new TreeChangeEventArgs() + { + AffectedId = "id1", + Expanded = true, + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_SelectedItem() + { + // Arrange && Act + var selectedItem = Items.ElementAt(0).Items?.ElementAt(1); // Item 1.2 + var cut = Render(@); + + var item12 = cut.Find("fluent-tree-item[aria-label='Item 1.2']"); + + // Assert + Assert.True(item12.HasAttribute("selected")); + cut.Verify(); + } + + [Fact] + public async Task FluentTreeViewItems_SelectedItem_Change() + { + // Arrange + var selectedItem = Items.ElementAt(0).Items?.ElementAt(1); // Item 1.2 + var cut = Render(@); + + // Act + var tree = cut.FindComponent(); + await tree.Instance.HandleCurrentSelectedChangeAsync(new TreeChangeEventArgs() + { + AffectedId = "id22", + Selected = true, + }); + + tree.Render(); + + // Assert + Assert.Equal("id22", selectedItem?.Id); + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_Icons() + { + Items[0].IconExpanded = IconExpanded; + Items[0].IconCollapsed = IconCollapsed; + + // Arrange && Act + var cut = Render(@); + + // Assert + Assert.Single(cut.FindAll("svg")); + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_Disabled() + { + Items[0].Items!.ElementAt(1).Disabled = true; + Items[1].Disabled = true; + + // Arrange && Act + var cut = Render(@); + + // Assert + Assert.Equal(2, cut.FindAll("fluent-tree-item[disabled]").Count); + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_ItemTemplate() + { + // Arrange && Act + var cut = Render( + @ + + @context.Text + + + ); + + // Assert + cut.Verify(); + } + + [Fact] + public async Task FluentTreeViewItems_OnExpanded() + { + // Arrange + TreeViewItemExpandedEventArgs expandedArgs = default!; + Items[0].OnExpandedAsync = (e) => + { + expandedArgs = e; + return Task.CompletedTask; + }; + + var cut = Render(@); + + // Act + var first = cut.FindComponent(); + await first.Instance.HandleExpandedChangeAsync(new TreeChangeEventArgs() + { + AffectedId = "id1", + Expanded = true, + }); + + // Assert + Assert.Equal("Item 1", expandedArgs.CurrentItem.Text); + Assert.True(expandedArgs.Expanded); + } + + [Fact] + public async Task FluentTreeViewItems_Selected() + { + // Arrange + var cut = Render(@); + + // Act + var first = cut.FindComponent(); + await first.Instance.HandleSelectedChangeAsync(new TreeChangeEventArgs() + { + AffectedId = "id1", + Selected = true, + }); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentTreeViewItems_LoadingObjects() + { + // Arrange && Act + var loadingItem = TreeViewItem.LoadingTreeViewItem; + var loadingItems = TreeViewItem.LoadingTreeViewItems; + var item1 = new TreeViewItem(); + var item2 = new TreeViewItem("Item 1", new[] { new TreeViewItem("Item 1.1") }); + + // Assert + Assert.Equal(FluentTreeView.LoadingMessage, loadingItem.Text); + Assert.True(loadingItem.Disabled); + + Assert.Single(loadingItems); + Assert.Equal(FluentTreeView.LoadingMessage, loadingItems.First().Text); + Assert.True(loadingItems.First().Disabled); + + Assert.Empty(item1.Text); + + Assert.Equal("Item 1", item2.Text); + Assert.Equal("Item 1.1", item2.Items?.First().Text); + } +} diff --git a/tests/Core/TreeView/FluentTreeViewItemsTests.razor.cs b/tests/Core/TreeView/FluentTreeViewItemsTests.razor.cs new file mode 100644 index 0000000000..cd4470cba6 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewItemsTests.razor.cs @@ -0,0 +1,30 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + +namespace Microsoft.FluentUI.AspNetCore.Components.Tests.TreeView; + +using Microsoft.FluentUI.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components.Tests.Extensions; + +public partial class FluentTreeViewItemsTests +{ + private readonly TreeViewItem[] Items = + { + new TreeViewItem("id1", "Item 1", + [ + new TreeViewItem("id11", "Item 1.1"), + new TreeViewItem("id12", "Item 1.2"), + new TreeViewItem("id13", "Item 1.3"), + ]), + new TreeViewItem("id2", "Item 2", + [ + new TreeViewItem("id21", "Item 2.1"), + new TreeViewItem("id22", "Item 2.2"), + new TreeViewItem("id23", "Item 2.3"), + ]), + }; + + private readonly Icon IconCollapsed = SampleIcons.Info; + private readonly Icon IconExpanded = SampleIcons.Warning; +} diff --git a/tests/Core/_ToDo/TreeView/FluentTreeViewTests.FluentTreeView_Default.verified.html b/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_CustomContent.verified.razor.html similarity index 100% rename from tests/Core/_ToDo/TreeView/FluentTreeViewTests.FluentTreeView_Default.verified.html rename to tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_CustomContent.verified.razor.html diff --git a/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_Default.verified.razor.html b/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_Default.verified.razor.html new file mode 100644 index 0000000000..33f1a64145 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_Default.verified.razor.html @@ -0,0 +1,11 @@ + + + + + diff --git a/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_FluentTreeItem_Default.verified.razor.html b/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_FluentTreeItem_Default.verified.razor.html new file mode 100644 index 0000000000..76083189d9 --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewTests.FluentTreeView_FluentTreeItem_Default.verified.razor.html @@ -0,0 +1,4 @@ + + + My text + diff --git a/tests/Core/TreeView/FluentTreeViewTests.razor b/tests/Core/TreeView/FluentTreeViewTests.razor new file mode 100644 index 0000000000..94a26e58cc --- /dev/null +++ b/tests/Core/TreeView/FluentTreeViewTests.razor @@ -0,0 +1,45 @@ +@using Xunit; +@inherits TestContext +@code +{ + [Fact] + public void FluentTreeView_Default() + { + FluentTreeItem? currentSelected = default!; + + // Arrange && Act + var cut = Render( + @ + + + Item 2.1 + Item 2.2 + + ); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentTreeView_CustomContent() + { + FluentTreeItem? currentSelected = default!; + + // Arrange && Act + var cut = Render(@render me); + + // Assert + cut.Verify(); + } + + [Fact] + public void FluentTreeView_FluentTreeItem_Default() + { + // Arrange && Act + var cut = Render(@); + + // Assert + cut.Verify(); + } +} diff --git a/tests/Core/_ToDo/TreeView/FluentTreeItemTests.cs b/tests/Core/_ToDo/TreeView/FluentTreeItemTests.cs deleted file mode 100644 index 8783f52129..0000000000 --- a/tests/Core/_ToDo/TreeView/FluentTreeItemTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Bunit; -using Xunit; - -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.TreeView; -public class FluentTreeItemTests : TestBase -{ - [Fact(Skip = "Need to figure out how to do this test")] - public void FluentTreeItem_Default() - { - //Arrange - var childContent = "render me"; - string text = default!; - bool expanded = default!; - Action expandedChanged = _ => { }; - bool selected = default!; - Action selectedChanged = _ => { }; - bool disabled = default!; - bool initiallyExpanded = default!; - bool initiallySelected = default!; - var cut = TestContext.RenderComponent(parameters => parameters - .Add(p => p.Text, text) - .Add(p => p.Expanded, expanded) - .Add(p => p.ExpandedChanged, expandedChanged) - .Add(p => p.Selected, selected) - .Add(p => p.SelectedChanged, selectedChanged) - .Add(p => p.Disabled, disabled) - .AddChildContent(childContent) - .Add(p => p.InitiallyExpanded, initiallyExpanded) - .Add(p => p.InitiallySelected, initiallySelected) - ); - //Act - - //Assert - cut.Verify(); - } -} - diff --git a/tests/Core/_ToDo/TreeView/FluentTreeViewTests.cs b/tests/Core/_ToDo/TreeView/FluentTreeViewTests.cs deleted file mode 100644 index a6c3eae353..0000000000 --- a/tests/Core/_ToDo/TreeView/FluentTreeViewTests.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Bunit; -using Xunit; - -namespace Microsoft.FluentUI.AspNetCore.Components.Tests.TreeView; -public class FluentTreeViewTests : TestBase -{ - [Fact] - public void FluentTreeView_Default() - { - //Arrange - var childContent = "render me"; - bool renderCollapsedNodes = default!; - FluentTreeItem currentSelected = default!; - Action currentSelectedChanged = _ => { }; - Action onSelectedChange = _ => { }; - Action onExpandedChange = _ => { }; - var cut = TestContext.RenderComponent(parameters => parameters - .Add(p => p.RenderCollapsedNodes, renderCollapsedNodes) - .Add(p => p.CurrentSelected, currentSelected) - .Add(p => p.CurrentSelectedChanged, currentSelectedChanged) - .AddChildContent(childContent) - .Add(p => p.OnSelectedChange, onSelectedChange) - .Add(p => p.OnExpandedChange, onExpandedChange) - ); - //Act - - //Assert - cut.Verify(); - } -} -