Replie les blocs de série au catalogue, cycle par cycle

Le catalogue ne rangeait un livre que sous sa série directe : il n'y avait
qu'un seul niveau, donc rien à replier « à chaque niveau ». Le regroupement
devient un arbre — un cycle coiffe ses séries, comme sur /series — et chaque
nœud porte sa bascule.

Tout reste déplié par défaut ; un bloc replié annonce ce qu'il contient, et
l'état de repli appartient à l'écran, sans quoi une frappe dans la recherche
le perdrait.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-22 01:34:04 +02:00
co-authored by Claude Opus 5
parent 7d2f3dc5d9
commit e85867014c
6 changed files with 411 additions and 48 deletions
+113
View File
@@ -23,10 +23,17 @@ public class GroupementCatalogueTests
};
private static SerieDto Serie(int id, string titre, params (int Position, int LivreId)[] tomes) =>
SousSerie(id, titre, null, 0, tomes);
private static SerieDto SousSerie(
int id, string titre, int? parenteId, int position,
params (int Position, int LivreId)[] tomes) =>
new()
{
Id = id,
Titre = titre,
SerieParenteId = parenteId,
Position = position,
Elements =
[
.. tomes.Select(t => new ElementSerieDto
@@ -133,4 +140,110 @@ public class GroupementCatalogueTests
Assert.Equal(["L'Éclat de cristal"], Assert.Single(entrees).Livres.Select(l => l.Titre));
}
/// <summary>
/// ⚠️ Le cas du 2026-08-22 : un cycle est une série de séries, et le catalogue doit le
/// montrer — sans quoi « replier chaque niveau » n'aurait qu'un seul niveau à replier.
/// </summary>
[Fact]
public void Un_cycle_coiffe_les_series_qu_il_contient()
{
List<LivreDto> catalogue = [Livre(1, "Terre natale"), Livre(2, "L'Éclat de cristal")];
List<SerieDto> series =
[
Serie(1, "La Légende de Drizzt"),
SousSerie(3, "La Trilogie des Elfes noirs", 1, 1, (0, 2)),
SousSerie(2, "L'Elfe noir", 1, 0, (0, 1)),
];
var cycle = Assert.Single(GroupementCatalogue.Grouper(catalogue, series));
Assert.Equal("La Légende de Drizzt", cycle.Serie!.Titre);
Assert.Empty(cycle.Livres);
Assert.Equal(2, cycle.NombreLivres);
// Les sous-séries suivent leur ordre de LECTURE dans le cycle, pas l'alphabet.
Assert.Collection(
cycle.SousEntrees,
e =>
{
Assert.Equal("L'Elfe noir", e.Serie!.Titre);
Assert.Equal(["Terre natale"], e.Livres.Select(l => l.Titre));
},
e =>
{
Assert.Equal("La Trilogie des Elfes noirs", e.Serie!.Titre);
Assert.Equal(["L'Éclat de cristal"], e.Livres.Select(l => l.Titre));
});
}
/// <summary>
/// Le bloc du cycle se pose là où tombe son premier tome, comme celui d'une série simple :
/// l'ordre général du catalogue ne bouge pas.
/// </summary>
[Fact]
public void Le_cycle_prend_la_place_du_premier_tome_de_sa_descendance()
{
List<LivreDto> catalogue =
[
Livre(1, "Dracula"),
Livre(2, "Terre natale"),
Livre(3, "Dune"),
Livre(4, "L'Éclat de cristal"),
];
List<SerieDto> series =
[
Serie(1, "Drizzt"),
SousSerie(2, "L'Elfe noir", 1, 0, (0, 2)),
SousSerie(3, "Les Elfes noirs", 1, 1, (0, 4)),
];
Assert.Collection(
GroupementCatalogue.Grouper(catalogue, series),
e => Assert.Equal("Dracula", Assert.Single(e.Livres).Titre),
e => Assert.Equal("Drizzt", e.Serie!.Titre),
e => Assert.Equal("Dune", Assert.Single(e.Livres).Titre));
}
/// <summary>
/// ⚠️ Un niveau qui ne porte rien n'existe pas : ce serait une indentation qui ne se
/// replierait sur rien, et un titre de série dont aucun tome n'est au catalogue.
/// </summary>
[Fact]
public void Les_series_sans_tome_visible_ne_font_pas_de_niveau()
{
List<SerieDto> series =
[
Serie(1, "Cycle"),
SousSerie(2, "Vide", 1, 0, (0, 99)),
SousSerie(3, "Garnie", 1, 1, (0, 5)),
];
var cycle = Assert.Single(GroupementCatalogue.Grouper([Livre(5, "Un tome")], series));
Assert.Equal("Garnie", Assert.Single(cycle.SousEntrees).Serie!.Titre);
}
/// <summary>
/// Une parenté qui boucle en base ne doit pas faire tourner le regroupement sans fin :
/// même garde-fou que la remontée du serveur, bornée par le nombre de séries.
/// </summary>
[Fact]
public void Une_parente_qui_boucle_ne_bloque_pas_le_catalogue()
{
List<SerieDto> series =
[
SousSerie(1, "A", 2, 0, (0, 1)),
SousSerie(2, "B", 1, 0),
];
var entrees = GroupementCatalogue.Grouper([Livre(1, "Un livre")], series);
Assert.Equal([1], entrees.SelectMany(EntreesEtDescendance).SelectMany(e => e.Livres).Select(l => l.Id));
}
private static IEnumerable<EntreeCatalogue> EntreesEtDescendance(EntreeCatalogue entree) =>
[entree, .. entree.SousEntrees.SelectMany(EntreesEtDescendance)];
}