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 @@ await _currentSelectedChangedDebouncer.DebounceAsync(50, () => InvokeAsync(async
}
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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
\ 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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
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 @@
+
+
+
+ Item 1
+
+
+
+ Loading...
+
+
+ Item 2
+ Loading...
+
+
\ 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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
\ 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 @@
+
+
+
+ Item 1
+ Loading...
+
+
+ Item 2
+ Loading...
+
+
\ 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
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+ Loading...
+
+
\ 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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
\ 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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
\ 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 @@
+
+
+
+ Item 1
+
+ Item 1.1
+
+
+ Item 1.2
+
+
+ Item 1.3
+
+
+
+ Item 2
+
+ Item 2.1
+
+
+ Item 2.2
+
+
+ Item 2.3
+
+
+
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 @@
+
+
+
+ Item 1
+
+
+ Item 2
+ Item 2.1
+ Item 2.2
+
+
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();
- }
-}
-