Enhance PLib video library manager with new tabbed navigation for entities and metadata management. Introduce sections for videos, tags, actors, studios, collections, and metadata, each with dedicated search and sorting capabilities. Update UI components to reflect these changes, ensuring a cohesive user experience. Revise repository interfaces to support loading video items with labels and summaries for efficient browsing. Update README.md to document new features and usage instructions.

This commit is contained in:
Leonid Pershin
2026-08-09 08:58:07 +03:00
parent 61d01d1970
commit 5acb42d11d
26 changed files with 1434 additions and 18 deletions
@@ -0,0 +1,84 @@
using NSubstitute;
using PLib.Desktop.Services;
using PLib.Desktop.ViewModels;
using PLib.Domain.Videos;
using Shouldly;
namespace PLib.Tests.ViewModels;
/// <summary>
/// The card carries its labels so the grid can be narrowed to one without a query per card.
/// </summary>
public sealed class VideoCardLabelTests
{
[Fact]
public void Labels_are_only_taken_when_a_caller_says_it_actually_loaded_them()
{
var item = Video();
item.AddLabel(new LibraryLabel("Драма", LabelKind.Tag));
var card = Card(item);
// Apply alone is the scan's path, and the scan loads videos without their labels —
// an empty collection there means "not loaded", not "none".
card.Apply(item);
card.LabelIds.ShouldBeEmpty();
card.ApplyLabels(item.Labels);
card.LabelIds.Count.ShouldBe(1);
}
[Fact]
public void A_card_is_kept_by_the_filter_only_for_labels_it_carries()
{
var mine = new LibraryLabel("Моё", LabelKind.Tag);
var other = new LibraryLabel("Чужое", LabelKind.Studio);
var item = Video();
item.AddLabel(mine);
var card = Card(item);
card.ApplyLabels(item.Labels);
card.HasLabel(mine.Id).ShouldBeTrue();
card.HasLabel(other.Id).ShouldBeFalse();
}
[Fact]
public void Searching_reaches_the_label_names_as_well_as_the_title_and_the_path()
{
var item = Video();
item.AddLabel(new LibraryLabel("Кристофер Нолан", LabelKind.Performer));
var card = Card(item);
card.ApplyLabels(item.Labels);
// Typing a performer's name is a search, not a trip to another tab.
card.Matches("нолан").ShouldBeTrue();
card.Matches("clip").ShouldBeTrue();
card.Matches("чего-то ещё").ShouldBeFalse();
}
[Fact]
public void Reapplying_labels_replaces_them_rather_than_accumulating()
{
var item = Video();
item.AddLabel(new LibraryLabel("Первый", LabelKind.Tag));
var card = Card(item);
card.ApplyLabels(item.Labels);
item.RemoveLabel(item.Labels.Single().Id);
card.ApplyLabels(item.Labels);
// A label removed on the media page has to stop narrowing the grid straight away.
card.LabelIds.ShouldBeEmpty();
card.LabelText.ShouldBeNull();
}
private static VideoItem Video() =>
new(@"C:\videos\clip.mp4", "clip", 1_000, DateTimeOffset.UnixEpoch);
private static VideoCardViewModel Card(VideoItem item) =>
new(item, Substitute.For<ISystemShell>(), _ => { });
}