using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Omika.App.Views;
using Xunit;
namespace Omika.Tests.Mobile;
///
/// Rangée d'actions de l'écran « Rapport » — dans l'ordre demandé : Annuler,
/// Enregistrer, Créer, Envoyer un email. Sur un téléphone, les quatre boutons
/// doivent rester ATTEIGNABLES et porter le MÊME style que les autres boutons de
/// l'application.
///
///
/// Deux défauts constatés à l'usage, et corrigés ici :
///
///
/// les quatre boutons étaient alignés sur UNE seule ligne, de largeur
/// illimitée : en petit écran les deux derniers sortaient du cadre, deux actions
/// seulement restaient accessibles ;
/// « Annuler » et « Enregistrer » ne portaient que la classe de TAILLE
/// (« compact »), sans aucune variante de couleur : ils conservaient le rendu
/// natif Fluent et ne ressemblaient à aucun autre bouton de l'application.
///
///
///
/// Le bouton d'email, lui, ne doit plus afficher d'icône enveloppe : le libellé
/// seul porte le sens, comme sur tous les autres boutons de l'application.
///
///
///
/// La classe rejoint la collection « ApplicationRuntimeContext » : le localisateur
/// d'Avalonia est global au processus, deux sessions headless ne doivent jamais
/// s'exécuter en parallèle. Les mesures sont lues À L'INTÉRIEUR du fil d'interface
/// (une fenêtre n'a de géométrie qu'une fois disposée).
///
[Collection("ApplicationRuntimeContext")]
public sealed class RapportActionsTests : IDisposable
{
/// Libellés attendus des quatre actions, dans l'ordre de la vue.
private static readonly string[] Libelles =
["Annuler", "Enregistrer", "Créer", "Envoyer un email"];
/// Variantes de couleur réelles de l'application (styles Controls.axaml).
private static readonly string[] Variantes = ["primary", "secondary", "ghost", "danger"];
private readonly HeadlessUnitTestSession _session =
HeadlessUnitTestSession.StartNew(typeof(LayoutTestAppBuilder));
public void Dispose() => _session.Dispose();
private void Executer(Action scenario)
=> _session.Dispatch(
() =>
{
scenario();
return true;
},
CancellationToken.None).GetAwaiter().GetResult();
///
/// Les quatre actions sont présentes et ENTIÈREMENT dans le cadre, à la largeur
/// d'un téléphone (320 px : le plus étroit courant) comme à 420 px. Sans le
/// retour à la ligne, les derniers boutons étaient disposés au-delà du bord
/// droit (rect.Right very largement supérieur à la largeur de l'écran).
///
[Theory]
[InlineData(420)]
[InlineData(360)]
[InlineData(320)]
public void LesQuatreActionsRestentDansLEcran(double largeur)
=> Executer(() =>
{
Window fenetre = OuvrirRapport(largeur);
// Mesure APRÈS disposition : une vue non affichée n'a aucune géométrie.
var boutons = BoutonsDAction(fenetre);
Assert.Equal(Libelles, boutons.Select(b => b.Content as string).ToArray());
foreach (Button bouton in boutons)
{
Rect rect = RectDe(bouton, fenetre);
Assert.True(
rect.Width > 0 && rect.Height > 0,
$"« {bouton.Content} » n'est pas disposé (taille {rect.Width}×{rect.Height}).");
Assert.True(
rect.X >= -0.5,
$"« {bouton.Content} » déborde à gauche : x={Fmt(rect.X)}.");
Assert.True(
rect.Right <= largeur + 0.5,
$"« {bouton.Content} » dépasse l'écran : droite={Fmt(rect.Right)} " +
$"pour une largeur de {Fmt(largeur)} px.");
Assert.True(bouton.IsVisible, $"« {bouton.Content} » n'est pas visible.");
}
fenetre.Close();
});
///
/// Les quatre actions sont dans l'ORDRE DEMANDÉ, lu de gauche à droite et de
/// haut en bas dans la page réellement disposée (Annuler, Enregistrer, Créer,
/// Envoyer un email). Ce test est distinct de la recherche par libellé : il
/// mesure les POSITIONS, donc il détecte un ordre inversé dans la vue.
///
[Fact]
public void LesActionsSontDansLordreDemande()
=> Executer(() =>
{
Window fenetre = OuvrirRapport(420);
string[] ordre = fenetre.GetVisualDescendants()
.OfType