Files
mabibli/MaBibli.Tests/RemonteeRoutesTests.cs
T
mathieuandClaude Opus 5 0976da90be Sors du chemin de la consultation d'une revue ce qui la modifie
La fiche d'une revue portait tout à la fois : ses numéros, le formulaire
d'ajout, les champs de la revue et sa suppression. Elle prend la forme
déjà tenue par la fiche d'un livre et par celle d'une série — une route
pour regarder, une route pour changer.

« Ajouter un numéro » et « Modifier la revue » remontent à droite du
titre, dans le bloc d'en-tête partagé avec la fiche d'une série : les
deux actions sont visibles sans descendre au bas d'une collection de
trente numéros.

⚠️ Modifier et retirer un numéro n'existent plus QUE dans
/revues/{id}/edition. En consultation, ces deux boutons se déclenchaient
sous le pouce en faisant défiler la liste — même raison que les flèches
d'ordre d'une série, reléguées sur leur propre écran.

⚠️ Ajouter un numéro, lui, RESTE en consultation, et ce n'est pas une
entorse : le geste ne touche pas à la fiche de la revue, il range un
objet de plus, comme noter un prêt depuis la fiche d'un livre. Le
formulaire se replie derrière son bouton, et s'ouvre de lui-même quand
on arrive du scanner avec un numéro lu sur l'add-on EAN-2 : demander un
clic de plus pour saisir ce qu'on tient en main serait un détour.

Un retrait de numéro se confirme désormais, comme la suppression d'un
livre ou d'une revue : il ne se défait pas, et la liste s'égrène sous le
pouce.

⚠️ Les deux routes partagent le paramètre {id} : le routeur ne
redessine rien en passant de l'une à l'autre, d'où l'abonnement à
LocationChanged — piège déjà rencontré sur la fiche livre. Quitter
l'édition referme au passage ce qui n'a de sens que là : formulaire de
numéro ouvert, retrait ou suppression en attente de confirmation.

/revues/{id}/edition entre dans la table de RemonteeRoutes : elle
retombait jusqu'ici sur le repli de branche, donc sur la liste des
revues au lieu de la fiche qu'on venait de quitter.

Deux tests de service viennent avec, sur ce dont l'écran dépend sans le
recalculer : la revue rendue par la modification d'un numéro revient
déjà rangée (corriger une parution déplace le numéro, les numéros sans
date fermant toujours la liste), et un retrait ne touche qu'au numéro
visé. 605 tests au vert.

Pas de vérification en navigateur : le rendu repose sur la relecture.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:07:51 +02:00

105 lines
3.9 KiB
C#

using MaBibli.Client.Services;
namespace MaBibli.Tests;
/// <summary>
/// La flèche de retour du bandeau remonte d'un cran de route, et ne sort jamais de
/// l'application. Ces cas sont ceux qui ont été écrits noir sur blanc avant de coder.
/// </summary>
public class RemonteeRoutesTests
{
[Theory]
// Fiche livre
[InlineData("/livres/3/edition", "/livres/3")]
[InlineData("/livres/3", "/")]
// Séries
[InlineData("/series/7/edition", "/series/7")]
[InlineData("/series/7/ordre", "/series/7")]
[InlineData("/series/7", "/series")]
[InlineData("/series", "/")]
// Envies
[InlineData("/souhaits/5/edition", "/souhaits")]
[InlineData("/souhaits/revues/5/edition", "/souhaits")]
[InlineData("/souhaits/ordre", "/souhaits")]
[InlineData("/souhaits/ajout", "/souhaits")]
[InlineData("/souhaits", "/")]
// Ajout d'un ouvrage
[InlineData("/ajout", "/")]
[InlineData("/ajout/isbn", "/")]
[InlineData("/ajout/manuel", "/")]
// Revues
[InlineData("/revues/ajout", "/revues")]
[InlineData("/revues/12/edition", "/revues/12")]
[InlineData("/revues/12", "/revues")]
[InlineData("/revues", "/")]
// Auteurs
[InlineData("/auteurs/5/bibliographie", "/auteurs")]
[InlineData("/auteurs", "/")]
// Le reste du menu
[InlineData("/prets", "/")]
[InlineData("/", "/")]
public void Remonte_dun_cran(string chemin, string attendu) =>
Assert.Equal(attendu, RemonteeRoutes.Parent(chemin));
/// <summary>
/// « ajout » n'est pas un identifiant : sans l'ordre de lecture de la table, il serait pris
/// pour la fiche d'une revue.
/// </summary>
[Fact]
public void Ajout_de_revue_nest_pas_une_fiche() =>
Assert.Equal("/revues", RemonteeRoutes.Parent("/revues/ajout"));
/// <summary>La requête d'un filtre ou d'un tri ne change pas la parenté.</summary>
[Theory]
[InlineData("/?auteur=3", "/")]
[InlineData("/livres/3/edition?onglet=pret", "/livres/3")]
[InlineData("/auteurs/5/bibliographie#bas", "/auteurs")]
public void La_requete_et_le_fragment_sont_ignores(string chemin, string attendu) =>
Assert.Equal(attendu, RemonteeRoutes.Parent(chemin));
/// <summary>Le chemin peut arriver relatif à la base, sans barre oblique de tête.</summary>
[Theory]
[InlineData("livres/3/edition", "/livres/3")]
[InlineData("series/7/ordre", "/series/7")]
[InlineData("", "/")]
public void Un_chemin_relatif_est_accepte(string chemin, string attendu) =>
Assert.Equal(attendu, RemonteeRoutes.Parent(chemin));
/// <summary>Une barre oblique finale ne doit pas faire manquer la ligne de table.</summary>
[Fact]
public void La_barre_finale_est_ignoree() =>
Assert.Equal("/livres/3", RemonteeRoutes.Parent("/livres/3/edition/"));
/// <summary>
/// Repli d'une route inconnue : la destination de menu de sa branche, jamais un chemin
/// deviné, et jamais rien d'externe.
/// </summary>
[Theory]
[InlineData("/revues/12/inconnu", "/revues")]
[InlineData("/series/7/inconnu", "/series")]
[InlineData("/inconnu/total", "/")]
[InlineData("/livres", "/")]
public void Une_route_inconnue_retombe_sur_sa_branche(string chemin, string attendu) =>
Assert.Equal(attendu, RemonteeRoutes.Parent(chemin));
/// <summary>
/// ⚠️ Le garde-fou du lot S : quoi qu'on lui donne, le retour reste dans l'application.
/// Une URL absolue ou un chemin protocolaire ne doit jamais ressortir tel quel.
/// </summary>
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
[InlineData("https://exemple.test/livres/3")]
[InlineData("//exemple.test/")]
[InlineData("/livres/abc/edition")]
public void Ne_sort_jamais_de_lapplication(string? chemin)
{
var parent = RemonteeRoutes.Parent(chemin);
Assert.StartsWith("/", parent);
Assert.DoesNotContain("//", parent);
Assert.DoesNotContain(":", parent);
}
}