Auteurs cherchables et triables, filtre de prêt, rang dans les exports, couvertures d'envies
Auteurs - Recherche et tri (par nom, ou du plus présent au moins présent), en mémoire : la liste est déjà chargée en entier — c'est elle qui sert l'instantané hors-ligne — et la filtrer côté serveur aurait rendu la page inutilisable sans réseau. - Trois colonnes dont une seule est élastique : compteur à gauche sur une largeur figée, nom au milieu, actions à droite. Les compteurs et les boutons s'alignent alors d'une ligne à l'autre au lieu de suivre la longueur du nom. Catalogue - Filtre « À la maison / Prêtés », posé dans les deux implémentations (serveur et hors-ligne) et dans le test qui les confronte, avec un cas de prêt CLOS — c'est exactement là que les deux auraient pu diverger en silence. - La ligne disparaît tant que rien n'est prêté, comme celles des formats et des types. Envies - ⚠️ Les couvertures manquaient parce que seul l'écran « ajouter une envie » remplissait CoverUrl : la bibliographie et les tomes manquants transmettent un ISBN mais aucune image. Le repli est posé à la LECTURE, donc il vaut aussi pour les envies déjà enregistrées, sans migration. - Les exports .txt et .csv portent le rang d'envie. Le .txt en a le plus besoin : son groupement par auteur détruit précisément cet ordre. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,8 +46,10 @@ public class ExportSouhaitsTests
|
||||
Assert.Single(lignes, l => l == "Bernard Werber");
|
||||
|
||||
var debutWerber = Array.IndexOf(lignes, "Bernard Werber");
|
||||
Assert.Equal(" - La prophétie des abeilles", lignes[debutWerber + 1]);
|
||||
Assert.Equal(" - Les fourmis", lignes[debutWerber + 2]);
|
||||
// Le crochet porte le rang d'envie, que le groupement par auteur ferait sinon
|
||||
// disparaître : la liste s'emporte pour savoir par quoi commencer.
|
||||
Assert.Equal(" - [1] La prophétie des abeilles", lignes[debutWerber + 1]);
|
||||
Assert.Equal(" - [3] Les fourmis", lignes[debutWerber + 2]);
|
||||
|
||||
// Werber avant Zola : les auteurs sont classés alphabétiquement.
|
||||
Assert.True(debutWerber < Array.IndexOf(lignes, "Émile Zola"));
|
||||
@@ -75,7 +77,7 @@ public class ExportSouhaitsTests
|
||||
|
||||
// ISBN découpé : c'est un numéro qu'on épelle à un libraire. La valeur passée est nue
|
||||
// (« 2253004227 »), les tirets n'existent qu'à la sortie.
|
||||
Assert.Contains(" - Germinal (Le Livre de poche, 1885, ISBN 2-253-00422-7)", texte);
|
||||
Assert.Contains(" - [1] Germinal (Le Livre de poche, 1885, ISBN 2-253-00422-7)", texte);
|
||||
Assert.Contains(" en poche seulement", texte);
|
||||
}
|
||||
|
||||
@@ -109,8 +111,8 @@ public class ExportSouhaitsTests
|
||||
|
||||
var lignes = csv.Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
Assert.Equal("Titre;Auteur;Éditeur;Année;ISBN;Note;Ajouté le", lignes[0]);
|
||||
Assert.Equal("Germinal;Émile Zola;Le Livre de poche;1885;;;2026-08-18", lignes[1]);
|
||||
Assert.Equal("Rang;Titre;Auteur;Éditeur;Année;ISBN;Note;Ajouté le", lignes[0]);
|
||||
Assert.Equal("1;Germinal;Émile Zola;Le Livre de poche;1885;;;2026-08-18", lignes[1]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -125,11 +127,35 @@ public class ExportSouhaitsTests
|
||||
|
||||
var ligne = csv.Split("\r\n")[1];
|
||||
|
||||
Assert.StartsWith("\"Titre; piégé\";", ligne);
|
||||
Assert.StartsWith("1;\"Titre; piégé\";", ligne);
|
||||
Assert.Contains("\"il a dit \"\"oui\"\"\"", ligne);
|
||||
|
||||
// Sept colonnes une fois l'échappement respecté, pas huit.
|
||||
Assert.Equal(7, ColonnesCsv(ligne).Count);
|
||||
// Huit colonnes une fois l'échappement respecté, pas neuf.
|
||||
Assert.Equal(8, ColonnesCsv(ligne).Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Le rang imprimé est la <b>position dans la liste reçue</b>, qui vient déjà triée par
|
||||
/// <c>Rang</c>. Le texte la regroupe par auteur, ce qui casse l'ordre à l'affichage : le
|
||||
/// crochet est alors la seule trace de « par quoi commencer ».
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Le_rang_suit_lordre_recu_et_non_lalphabet()
|
||||
{
|
||||
IReadOnlyList<SouhaitDto> liste =
|
||||
[
|
||||
Souhait(7, "Zorro", "Émile Zola"),
|
||||
Souhait(3, "Abécédaire", "Émile Zola"),
|
||||
];
|
||||
|
||||
var texte = ExportSouhaits.Texte(liste, null, Jour);
|
||||
Assert.Contains(" - [1] Zorro", texte);
|
||||
Assert.Contains(" - [2] Abécédaire", texte);
|
||||
|
||||
// Le CSV, lui, garde l'ordre : le rang y est croissant ligne après ligne.
|
||||
var lignes = ExportSouhaits.Csv(liste).Split("\r\n", StringSplitOptions.RemoveEmptyEntries);
|
||||
Assert.StartsWith("1;Zorro;", lignes[1]);
|
||||
Assert.StartsWith("2;Abécédaire;", lignes[2]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -32,7 +32,8 @@ public class FiltreLivresLocalTests
|
||||
|
||||
private static Livre Livre(
|
||||
int id, string titre, Auteur? auteur, Format format, Statut? statut,
|
||||
TypeDocument type = TypeDocument.NonPrecise)
|
||||
TypeDocument type = TypeDocument.NonPrecise,
|
||||
string? preteA = null, bool pretClos = false)
|
||||
{
|
||||
var livre = new Livre
|
||||
{
|
||||
@@ -50,6 +51,20 @@ public class FiltreLivresLocalTests
|
||||
livre.Statuts.Add(new StatutLecture { LivreId = id, Utilisateur = Lecteur, Statut = valeur });
|
||||
}
|
||||
|
||||
if (preteA is not null)
|
||||
{
|
||||
// ⚠️ Un prêt CLOS ne doit pas compter comme « dehors ». C'est le point où les deux
|
||||
// implémentations pourraient diverger sans qu'on s'en aperçoive : le serveur regarde
|
||||
// DateRetour, le client un PreteA que le serveur a déjà résolu.
|
||||
livre.Prets.Add(new Pret
|
||||
{
|
||||
LivreId = id,
|
||||
Emprunteur = preteA,
|
||||
DatePret = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc),
|
||||
DateRetour = pretClos ? new DateTime(2026, 2, 1, 0, 0, 0, DateTimeKind.Utc) : null,
|
||||
});
|
||||
}
|
||||
|
||||
return livre;
|
||||
}
|
||||
|
||||
@@ -57,8 +72,9 @@ public class FiltreLivresLocalTests
|
||||
[
|
||||
Livre(1, "Germinal", Zola, Format.Physique, Statut.Lu, TypeDocument.Roman),
|
||||
Livre(2, "La Bête humaine", Zola, Format.Numerique, Statut.ALire, TypeDocument.BandeDessinee),
|
||||
Livre(3, "Le Horla", Maupassant, Format.Physique, Statut.EnCours, TypeDocument.Roman),
|
||||
Livre(4, "Bel-Ami", Maupassant, Format.Numerique, null),
|
||||
Livre(3, "Le Horla", Maupassant, Format.Physique, Statut.EnCours, TypeDocument.Roman,
|
||||
preteA: "Marie"),
|
||||
Livre(4, "Bel-Ami", Maupassant, Format.Numerique, null, preteA: "Paul", pretClos: true),
|
||||
Livre(5, "Œuvres complètes", Zola, Format.Physique, Statut.ALire),
|
||||
Livre(6, "L'Éducation sentimentale", null, Format.Physique, null),
|
||||
];
|
||||
@@ -76,6 +92,9 @@ public class FiltreLivresLocalTests
|
||||
TypeDocument = l.TypeDocument,
|
||||
DateAjout = DateTime.UtcNow,
|
||||
Statut = l.Statuts.FirstOrDefault(s => s.Utilisateur == Lecteur)?.Statut,
|
||||
|
||||
// Ce que l'API expose : l'emprunteur du prêt EN COURS, rien de l'historique clos.
|
||||
PreteA = l.Prets.FirstOrDefault(p => p.DateRetour is null)?.Emprunteur,
|
||||
Auteurs = l.Auteurs
|
||||
.Select(la => new AuteurDto { Id = la.AuteurId, Nom = la.Auteur!.Nom })
|
||||
.ToList(),
|
||||
@@ -104,6 +123,10 @@ public class FiltreLivresLocalTests
|
||||
new CritereLivres { AuteurId = 1 },
|
||||
new CritereLivres { AuteurId = 2 },
|
||||
new CritereLivres { AuteurId = 99 },
|
||||
new CritereLivres { Prete = true },
|
||||
new CritereLivres { Prete = false },
|
||||
new CritereLivres { Prete = false, Format = Format.Physique },
|
||||
new CritereLivres { Prete = true, Recherche = "horla" },
|
||||
new CritereLivres { Recherche = "zola", Format = Format.Physique, Statut = Statut.ALire },
|
||||
new CritereLivres
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user