Page « À propos » (/a-propos) : manuel annoncé à venir, contact, site de l'auteur, version publiée avec sa date de build, et licence AGPL v3 avec le lien vers le dépôt — l'AGPL attend que les utilisateurs d'un service en réseau puissent en obtenir la source, ce lien n'est donc pas un ornement. L'application ne connaissait pas sa version : elle vivait dans le manifeste du paquet et dans les tags git, jamais dans le binaire. `build/publier-release.sh` pose désormais `-p:Version` et `-p:MaBibliDateBuild` ; l'API rend les deux par `GET /api/version`. ⚠️ L'horodatage est le TÉMOIN de l'injection, et rien ne le calcule côté MSBuild. Sans lui, la version lue serait le « 1.0.0 » que le SDK pose par défaut : il se lirait comme une vraie version alors qu'il ne désigne rien, et c'est exactement la valeur qu'on ira chercher pour diagnostiquer un appareil au cache dépareillé. Mieux vaut ne rien annoncer — un binaire compilé à la main se déclare « version de développement », ce qui est vrai. Autres décisions : - l'entrée du menu est DÉTACHÉE des six destinations, par un filet au-dessus sur téléphone et à gauche en rangée sur PC : « À propos » est une annexe, pas une septième destination. Ses règles vivent en feuille GLOBALE, comme tout le menu — une règle scopée n'atteint pas ce que rend un NavLink ; - le « mailto: » reste un lien même hors-ligne : il ne charge aucune page et passe la main au client de messagerie, qui sait mettre un message en attente. Le site et le dépôt, eux, basculent en boutons désactivés portant leur motif, comme les liens d'export des envies ; - la version est un sixième instantané hors-ligne : on la lit justement quand quelque chose ne va pas, et un appareil qu'on soupçonne est souvent celui qui n'a plus de réseau. L'écran dit alors que c'est la dernière version vue du serveur. Le point de rupture de 40 rem reste identique dans les deux feuilles, et /a-propos est inscrite dans la table de remontée des routes. Vérifié en exécution, l'API lancée : sans injection `{"numero":null,"publiee":false}`, avec `-p:Version=0.4.1 -p:MaBibliDateBuild=…` `{"numero":"0.4.1","publiee":true}`. Le rendu des écrans n'a PAS été vérifié en navigateur. 618 tests au vert (605 avant). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
107 lines
4.0 KiB
C#
107 lines
4.0 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", "/")]
|
|
// L'annexe du menu
|
|
[InlineData("/a-propos", "/")]
|
|
[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);
|
|
}
|
|
}
|