feat(m9/T23b): folder reorder menu items + root context menu
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
using System.Security.Claims;
|
||||
using ZB.MOM.WW.ScadaBridge.Security;
|
||||
using Bunit;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
||||
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Shared;
|
||||
using ZB.MOM.WW.ScadaBridge.TemplateEngine;
|
||||
using ZB.MOM.WW.ScadaBridge.TemplateEngine.Services;
|
||||
@@ -176,6 +178,149 @@ public class TemplatesPageTests : BunitContext
|
||||
Assert.Contains("BetaDevice", cut.Markup);
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// M9-T23b: folder sibling reorder menu items + root context menu
|
||||
// ========================================================================
|
||||
|
||||
// Seeds two root sibling folders (Alpha @ SortOrder 0, Beta @ SortOrder 1) and
|
||||
// wires the repo so TemplateFolderService.ReorderFolderAsync runs end-to-end.
|
||||
private (TemplateFolder alpha, TemplateFolder beta) SeedTwoRootSiblings()
|
||||
{
|
||||
var alpha = new TemplateFolder("Alpha") { Id = 1, ParentFolderId = null, SortOrder = 0 };
|
||||
var beta = new TemplateFolder("Beta") { Id = 2, ParentFolderId = null, SortOrder = 1 };
|
||||
var all = new List<TemplateFolder> { alpha, beta };
|
||||
|
||||
_repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template>()));
|
||||
_repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(_ => Task.FromResult<IReadOnlyList<TemplateFolder>>(all));
|
||||
_repo.GetFolderByIdAsync(1, Arg.Any<CancellationToken>()).Returns(Task.FromResult<TemplateFolder?>(alpha));
|
||||
_repo.GetFolderByIdAsync(2, Arg.Any<CancellationToken>()).Returns(Task.FromResult<TemplateFolder?>(beta));
|
||||
|
||||
return (alpha, beta);
|
||||
}
|
||||
|
||||
// Right-clicks the folder row whose label contains the given text, opening the
|
||||
// TreeView context menu, then returns the rendered context-menu container.
|
||||
private static AngleSharp.Dom.IElement OpenFolderContextMenu(IRenderedComponent<TemplatesPage> cut, string folderLabel)
|
||||
{
|
||||
var row = cut.FindAll("li[role='treeitem']")
|
||||
.First(li => li.TextContent.Contains(folderLabel))
|
||||
.QuerySelector(".tv-row")!;
|
||||
row.ContextMenu();
|
||||
return cut.Find(".dropdown-menu.show");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FolderContextMenu_ExposesMoveUpAndMoveDown()
|
||||
{
|
||||
SeedTwoRootSiblings();
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
var menu = OpenFolderContextMenu(cut, "Beta");
|
||||
var labels = menu.QuerySelectorAll("button.dropdown-item").Select(b => b.TextContent.Trim()).ToList();
|
||||
|
||||
Assert.Contains("Move up", labels);
|
||||
Assert.Contains("Move down", labels);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FolderContextMenu_MoveDown_DispatchesReorderDown_AndReloads()
|
||||
{
|
||||
var (alpha, beta) = SeedTwoRootSiblings();
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
// Move Alpha (first sibling) down -> swaps SortOrder with Beta (the next sibling).
|
||||
var menu = OpenFolderContextMenu(cut, "Alpha");
|
||||
menu.QuerySelectorAll("button.dropdown-item")
|
||||
.First(b => b.TextContent.Trim() == "Move down")
|
||||
.Click();
|
||||
|
||||
// Reorder was dispatched the same way other folder commands are — via
|
||||
// TemplateFolderService, which resolves + persists both swapped siblings.
|
||||
_repo.Received().GetFolderByIdAsync(1, Arg.Any<CancellationToken>());
|
||||
_repo.Received().UpdateFolderAsync(alpha, Arg.Any<CancellationToken>());
|
||||
_repo.Received().UpdateFolderAsync(beta, Arg.Any<CancellationToken>());
|
||||
_repo.Received().SaveChangesAsync(Arg.Any<CancellationToken>());
|
||||
|
||||
// Down on Alpha swapped sort orders: Alpha now after Beta.
|
||||
Assert.Equal(1, alpha.SortOrder);
|
||||
Assert.Equal(0, beta.SortOrder);
|
||||
|
||||
// Tree reloaded after the mutation: LoadTemplatesAsync re-fetched (initial
|
||||
// load + post-reorder reload). GetAllTemplatesAsync is only touched by the
|
||||
// page's load path, never by ReorderFolderAsync, so 2 calls == one reload.
|
||||
_repo.Received(2).GetAllTemplatesAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FolderContextMenu_MoveUp_DispatchesReorderUp_AndReloads()
|
||||
{
|
||||
var (alpha, beta) = SeedTwoRootSiblings();
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
// Move Beta (second sibling) up -> swaps SortOrder with Alpha (the previous sibling).
|
||||
var menu = OpenFolderContextMenu(cut, "Beta");
|
||||
menu.QuerySelectorAll("button.dropdown-item")
|
||||
.First(b => b.TextContent.Trim() == "Move up")
|
||||
.Click();
|
||||
|
||||
_repo.Received().GetFolderByIdAsync(2, Arg.Any<CancellationToken>());
|
||||
_repo.Received().UpdateFolderAsync(alpha, Arg.Any<CancellationToken>());
|
||||
_repo.Received().UpdateFolderAsync(beta, Arg.Any<CancellationToken>());
|
||||
|
||||
// Up on Beta swapped sort orders: Beta now before Alpha.
|
||||
Assert.Equal(0, beta.SortOrder);
|
||||
Assert.Equal(1, alpha.SortOrder);
|
||||
|
||||
// Tree reloaded after the mutation (one reload == GetAllTemplatesAsync twice).
|
||||
_repo.Received(2).GetAllTemplatesAsync(Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RootContextMenu_OffersNewFolderAndNewTemplate()
|
||||
{
|
||||
_repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template>()));
|
||||
_repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder>()));
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
// Right-click the root tree zone to open the root context menu.
|
||||
cut.Find(".tv-root-zone").ContextMenu();
|
||||
var menu = cut.Find(".tv-root-menu");
|
||||
var labels = menu.QuerySelectorAll("button.dropdown-item").Select(b => b.TextContent.Trim()).ToList();
|
||||
|
||||
Assert.Contains("New Folder", labels);
|
||||
Assert.Contains("New Template", labels);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RootContextMenu_NewTemplate_NavigatesToRootCreate()
|
||||
{
|
||||
_repo.GetAllTemplatesAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<Template>>(new List<Template>()));
|
||||
_repo.GetAllFoldersAsync(Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<TemplateFolder>>(new List<TemplateFolder>()));
|
||||
|
||||
var nav = Services.GetRequiredService<NavigationManager>();
|
||||
|
||||
var cut = Render<TemplatesPage>();
|
||||
|
||||
cut.Find(".tv-root-zone").ContextMenu();
|
||||
cut.Find(".tv-root-menu")
|
||||
.QuerySelectorAll("button.dropdown-item")
|
||||
.First(b => b.TextContent.Trim() == "New Template")
|
||||
.Click();
|
||||
|
||||
// Root-level New Template navigates with no folderId query — a root create.
|
||||
Assert.EndsWith("/design/templates/create", nav.Uri);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal sealed class TestAuthStateProvider : AuthenticationStateProvider
|
||||
|
||||
Reference in New Issue
Block a user