feat(ui): Listen und Ordner in einer gemischten Sidebar-Reihenfolge
Die oberste Sidebar-Ebene ist jetzt eine gemischte TopLevel-Collection, so dass eine Liste auch oberhalb eines Ordners stehen kann. DropAsync deckt alle vier Quelle/Ziel-Kombinationen ab, ReorderSidebarAsync numeriert die gesamte sichtbare Reihenfolge ueber lists und list_folders hinweg.
This commit is contained in:
@@ -102,6 +102,36 @@ public sealed class ListFolderTests : IDisposable
|
||||
Assert.Null((await ctx.Lists.AsNoTracking().FirstAsync(l => l.Id == "l1")).FolderId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReorderSidebarAsync_numbers_folders_and_lists_in_one_sequence()
|
||||
{
|
||||
await using var ctx = Open();
|
||||
var repo = new ListRepository(ctx);
|
||||
await repo.AddFolderAsync(NewFolder("f1"));
|
||||
await repo.AddAsync(new ListEntity { Id = "inside", Name = "Inside", CreatedAt = DateTime.UtcNow, FolderId = "f1" });
|
||||
await repo.AddAsync(new ListEntity { Id = "top", Name = "Top", CreatedAt = DateTime.UtcNow });
|
||||
|
||||
// Sidebar: the top-level list above the folder, whose one child follows the folder.
|
||||
await repo.ReorderSidebarAsync([("top", false), ("f1", true), ("inside", false)]);
|
||||
|
||||
Assert.Equal(0, (await repo.GetByIdAsync("top"))!.SortOrder);
|
||||
Assert.Equal(1, (await repo.GetFoldersAsync()).Single().SortOrder);
|
||||
Assert.Equal(2, (await repo.GetByIdAsync("inside"))!.SortOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddAsync_appends_past_the_folders_too()
|
||||
{
|
||||
await using var ctx = Open();
|
||||
var repo = new ListRepository(ctx);
|
||||
await repo.AddFolderAsync(NewFolder("f1"));
|
||||
await repo.AddFolderAsync(NewFolder("f2"));
|
||||
await repo.AddAsync(new ListEntity { Id = "l1", Name = "Fresh", CreatedAt = DateTime.UtcNow });
|
||||
|
||||
// 2, not 0 — a new list must not jump above the existing folders on the next load.
|
||||
Assert.Equal(2, (await repo.GetByIdAsync("l1"))!.SortOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddFolderAsync_appends_in_creation_order()
|
||||
{
|
||||
|
||||
@@ -48,11 +48,14 @@ public class ListsIslandFolderTests : IDisposable
|
||||
public override bool IsConnected => true;
|
||||
}
|
||||
|
||||
private async Task SeedAsync(string? folderId = null, params (string Id, string Name, int Sort)[] lists)
|
||||
private async Task SeedAsync(string? folderId = null, params (string Id, string Name, int Sort)[] lists) =>
|
||||
await SeedAsync(folderId, 0, lists);
|
||||
|
||||
private async Task SeedAsync(string? folderId, int folderSort, params (string Id, string Name, int Sort)[] lists)
|
||||
{
|
||||
await using var db = NewContext();
|
||||
if (folderId is not null)
|
||||
db.ListFolders.Add(new ListFolderEntity { Id = folderId, Name = $"Folder {folderId}", CreatedAt = DateTime.UtcNow });
|
||||
db.ListFolders.Add(new ListFolderEntity { Id = folderId, Name = $"Folder {folderId}", SortOrder = folderSort, CreatedAt = DateTime.UtcNow });
|
||||
foreach (var (id, name, sort) in lists)
|
||||
db.Lists.Add(new ListEntity { Id = id, Name = name, SortOrder = sort, CreatedAt = DateTime.UtcNow, FolderId = folderId });
|
||||
await db.SaveChangesAsync();
|
||||
@@ -164,9 +167,74 @@ public class ListsIslandFolderTests : IDisposable
|
||||
await using var db = NewContext();
|
||||
var stored = await db.Lists.AsNoTracking().ToListAsync();
|
||||
Assert.Equal("f1", stored.Single(l => l.Id == "mover").FolderId);
|
||||
// sort_order is written as one global sequence in sidebar order: folder contents first.
|
||||
Assert.Equal(0, stored.Single(l => l.Id == "mover").SortOrder);
|
||||
Assert.Equal(1, stored.Single(l => l.Id == "inside").SortOrder);
|
||||
// sort_order is one sequence over the whole sidebar, folders included: f1 takes 0, its
|
||||
// two children 1 and 2.
|
||||
Assert.Equal(0, (await db.ListFolders.AsNoTracking().FirstAsync(f => f.Id == "f1")).SortOrder);
|
||||
Assert.Equal(1, stored.Single(l => l.Id == "mover").SortOrder);
|
||||
Assert.Equal(2, stored.Single(l => l.Id == "inside").SortOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DropAsync_reorders_folders_among_each_other_and_persists_it()
|
||||
{
|
||||
await SeedAsync("f1", folderSort: 0);
|
||||
await SeedAsync("f2", folderSort: 1);
|
||||
|
||||
var vm = NewVm();
|
||||
await vm.LoadAsync();
|
||||
var (first, second) = (vm.Folders[0], vm.Folders[1]);
|
||||
|
||||
await vm.DropAsync(second, first, placeBelow: false);
|
||||
|
||||
Assert.Equal([second, first], vm.Folders);
|
||||
await using var db = NewContext();
|
||||
var stored = await db.ListFolders.AsNoTracking().ToListAsync();
|
||||
Assert.Equal(0, stored.Single(f => f.Id == second.Id).SortOrder);
|
||||
Assert.Equal(1, stored.Single(f => f.Id == first.Id).SortOrder);
|
||||
|
||||
// And it survives a reload — the load path merges folders and lists by sort_order.
|
||||
var reloaded = NewVm();
|
||||
await reloaded.LoadAsync();
|
||||
Assert.Equal([second.Id, first.Id], reloaded.Folders.Select(f => f.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DropAsync_puts_a_list_above_a_folder_at_the_top_level()
|
||||
{
|
||||
await SeedAsync("f1", ("inside", "Inside", 1));
|
||||
await SeedAsync(null, ("mover", "Mover", 2));
|
||||
|
||||
var vm = NewVm();
|
||||
await vm.LoadAsync();
|
||||
var folder = vm.Folders[0];
|
||||
var mover = vm.RootLists.Single();
|
||||
|
||||
await vm.DropAsync(mover, folder, placeBelow: false);
|
||||
|
||||
Assert.Equal(new object[] { mover, folder }, vm.TopLevel);
|
||||
Assert.Null(mover.FolderId);
|
||||
|
||||
var reloaded = NewVm();
|
||||
await reloaded.LoadAsync();
|
||||
Assert.Equal(["user:mover", "f1"], reloaded.TopLevel.Select(i =>
|
||||
i is ListNavItemViewModel l ? l.Id : ((ListFolderNavItemViewModel)i).Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DropAsync_never_nests_a_folder_in_a_folder()
|
||||
{
|
||||
await SeedAsync("f1", 0, ("inside", "Inside", 1));
|
||||
await SeedAsync("f2", folderSort: 2);
|
||||
|
||||
var vm = NewVm();
|
||||
await vm.LoadAsync();
|
||||
var inside = vm.Folders[0].Lists.Single();
|
||||
var f2 = vm.Folders[1];
|
||||
|
||||
await vm.DropAsync(f2, inside, placeBelow: true);
|
||||
|
||||
Assert.Equal(2, vm.TopLevel.Count);
|
||||
Assert.Single(vm.Folders[0].Lists);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user