using MaBibli.Client.Services; using MaBibli.Shared.Dtos; namespace MaBibli.Tests; /// /// Regroupement des tomes d'une série au catalogue (2026-08-22). /// /// /// L'écran n'est pas éprouvable ici, mais la règle l'est : ces tests portent sur ce qui a été /// décidé — la place du bloc, l'ordre des tomes à l'intérieur, et le fait que rien ne /// disparaisse ni ne se duplique. /// public class GroupementCatalogueTests { private static LivreDto Livre(int id, string titre) => new() { Id = id, Titre = titre, Format = MaBibli.Shared.Entites.Format.Physique, DateAjout = DateTime.UtcNow, }; private static SerieDto Serie(int id, string titre, params (int Position, int LivreId)[] tomes) => new() { Id = id, Titre = titre, Elements = [ .. tomes.Select(t => new ElementSerieDto { Id = t.LivreId, Position = t.Position, Titre = $"tome {t.Position}", LivreId = t.LivreId, }), ], }; /// /// ⚠️ Le cas qui a tranché la décision : le bloc se pose à la place du PREMIER tome, pour /// que l'ordre général du catalogue ne bouge pas. /// [Fact] public void Le_bloc_prend_la_place_de_son_premier_tome() { List catalogue = [ Livre(1, "Dracula"), Livre(2, "L'Éclat de cristal"), Livre(3, "Dune"), Livre(4, "Terre natale"), ]; var entrees = GroupementCatalogue.Grouper( catalogue, [Serie(9, "La Légende de Drizzt", (1, 2), (0, 4))]); Assert.Collection( entrees, e => Assert.Equal("Dracula", Assert.Single(e.Livres).Titre), e => { Assert.Equal("La Légende de Drizzt", e.Serie!.Titre); // À l'intérieur, l'ordre est celui de LECTURE, pas celui du catalogue. Assert.Equal(["Terre natale", "L'Éclat de cristal"], e.Livres.Select(l => l.Titre)); }, e => Assert.Equal("Dune", Assert.Single(e.Livres).Titre)); } /// Aucun livre ne disparaît, aucun n'apparaît deux fois : c'est la règle du catalogue. [Fact] public void Le_regroupement_ne_perd_ni_ne_duplique_aucun_livre() { List catalogue = [Livre(1, "A"), Livre(2, "B"), Livre(3, "C")]; // Le livre 2 est rattaché à DEUX séries — le modèle l'autorise. var entrees = GroupementCatalogue.Grouper( catalogue, [Serie(1, "Zeta", (0, 2)), Serie(2, "Alpha", (0, 2), (1, 3))]); Assert.Equal([1, 2, 3], entrees.SelectMany(e => e.Livres).Select(l => l.Id).Order()); } /// /// ⚠️ La série retenue est la première par ordre alphabétique, et non celle que l'API a /// rendue en premier : sans cela, le bloc changerait de nom d'un chargement à l'autre. /// [Fact] public void Un_livre_de_deux_series_est_range_sous_la_premiere_alphabetiquement() { var entrees = GroupementCatalogue.Grouper( [Livre(2, "B")], [Serie(1, "Zeta", (0, 2)), Serie(2, "Alpha", (0, 2))]); Assert.Equal("Alpha", Assert.Single(entrees).Serie!.Titre); } /// Sans série connue — hors-ligne sans instantané, ou groupement défait — la liste est plate. [Fact] public void Sans_series_la_liste_reste_celle_du_catalogue() { List catalogue = [Livre(1, "A"), Livre(2, "B")]; var entrees = GroupementCatalogue.Grouper(catalogue, null); Assert.All(entrees, e => Assert.Null(e.Serie)); Assert.Equal(["A", "B"], entrees.Select(e => e.Livres[0].Titre)); } /// /// Un seul tome possédé forme quand même un bloc : c'est ce qui dit à quoi ce livre /// appartient, et ce qui mène à la série où figurent les tomes manquants. /// [Fact] public void Un_seul_tome_possede_forme_quand_meme_un_bloc() { var entrees = GroupementCatalogue.Grouper( [Livre(4, "Terre natale")], [Serie(9, "L'Elfe noir", (0, 4), (1, 5))]); Assert.Equal("L'Elfe noir", Assert.Single(entrees).Serie!.Titre); } /// /// Les tomes filtrés restent absents : le regroupement rassemble ce que la liste contient, /// il ne va pas rechercher ce qu'un filtre a écarté. /// [Fact] public void Un_filtre_ne_ramene_pas_les_tomes_ecartes() { var entrees = GroupementCatalogue.Grouper( [Livre(5, "L'Éclat de cristal")], [Serie(9, "Drizzt", (0, 4), (1, 5))]); Assert.Equal(["L'Éclat de cristal"], Assert.Single(entrees).Livres.Select(l => l.Titre)); } }