using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Themes.Fluent;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Omika.App.Local.Models;
using Omika.App.Navigation;
using Omika.App.Services;
using Omika.App.ViewModels;
using Omika.App.Views;
using Omika.Core.Constants;
using Xunit;
namespace Omika.Tests.Mobile;
///
/// Régression « écran étroit » : dans un en-tête de fiche, les pastilles ne
/// doivent JAMAIS se chevaucher — y compris quand elles passent à la ligne.
/// Un WrapPanel n'ajoute aucun espacement, ni entre ses éléments, ni entre ses
/// lignes : sans marge dédiée, la pastille qui passe à la ligne venait se coller
/// à celle du dessus (constaté à 320 px : ligne 1 de y=109 à y=129, ligne 2
/// reprenant exactement à y=129, avec en plus un décalage de 8 px).
/// Ces tests mesurent la géométrie RÉELLE après disposition de la vue.
///
///
/// Ces tests mesurent une disposition réelle : ils rejoignent la collection
/// « ApplicationRuntimeContext » pour qu'aucune autre session Avalonia headless
/// ne tourne en parallèle (le localisateur d'Avalonia est global au processus).
///
[Collection("ApplicationRuntimeContext")]
public sealed class PastillesEnTeteTests : IDisposable
{
/// Largeur d'un petit téléphone (Android compact).
private const double LargeurEcranEtroit = 320;
private readonly HeadlessUnitTestSession _session =
HeadlessUnitTestSession.StartNew(typeof(LayoutTestAppBuilder));
public void Dispose() => _session.Dispose();
[Fact]
public void FicheClient_PastillesJamaisCollantes_MemeAEcranEtroit()
{
// La surcharge générique de Dispatch est ATTENDUE (contrairement à
// Dispatch(Action), qui rend la main avant l'exécution de l'action).
_session.Dispatch(
() =>
{
var permissions = new PermissionService();
permissions.SetRights(MobilePermissionKeys.All.ToDictionary(k => k, _ => true));
var vueModele = new ClientDetailViewModel(null!, new NavigationService(), permissions)
{
Client = new ClientDetail
{
ClientGuid = Guid.NewGuid(),
Nom = "REPETTO IMMOBILIER",
Code = "CLI-00123",
Type = "Client final — particulier",
Statut = "Prospect",
EstArchive = false
}
};
Window fenetre = MesureGeometrique.Ouvrir(
new ClientDetailView { DataContext = vueModele },
LargeurEcranEtroit);
MesureGeometrique.VerifierPastillesEspacees(fenetre, LargeurEcranEtroit);
fenetre.Close();
return true;
},
CancellationToken.None).GetAwaiter().GetResult();
}
[Fact]
public void FicheSite_PastillesJamaisCollantes_MemeAEcranEtroit()
{
_session.Dispatch(
() =>
{
var permissions = new PermissionService();
permissions.SetRights(MobilePermissionKeys.All.ToDictionary(k => k, _ => true));
var vueModele = new SiteDetailViewModel(null!, new NavigationService(), permissions, null!)
{
Site = new SiteDetail
{
SiteGuid = Guid.NewGuid(),
Nom = "Siège social Marseille",
Code = "SIT-0001",
Type = "Siège social",
EstArchive = false
}
};
Window fenetre = MesureGeometrique.Ouvrir(
new SiteDetailView { DataContext = vueModele },
LargeurEcranEtroit);
MesureGeometrique.VerifierPastillesEspacees(fenetre, LargeurEcranEtroit);
fenetre.Close();
return true;
},
CancellationToken.None).GetAwaiter().GetResult();
}
}
///
/// La carte de site de la liste des sites expose le même fonctionnement que les
/// cartes Client et Dossier : « Ouvrir » (et l'étoile d'épinglage), en plus des
/// informations du site (type, nom, code, client, ville, code postal).
///
[Collection("ApplicationRuntimeContext")]
public sealed class CarteSiteListeTests : IDisposable
{
private readonly HeadlessUnitTestSession _session =
HeadlessUnitTestSession.StartNew(typeof(LayoutTestAppBuilder));
public void Dispose() => _session.Dispose();
[Fact]
public void ListeDesSites_ChaqueCarte_ExposeOuvrirEtEpinglage()
{
_session.Dispatch(
() =>
{
var vueModele = new SitesViewModel(null!, null!, new NavigationService())
{
HasSearched = true,
Sites = new ObservableCollection
{
new()
{
SiteGuid = Guid.NewGuid(),
Nom = "Siège social Marseille",
Code = "SIT-0001",
ClientNom = "REPETTO IMMOBILIER",
Ville = "Marseille",
CodePostal = "13001",
Type = "Siège",
IsPinned = true
},
new()
{
SiteGuid = Guid.NewGuid(),
Nom = "Entrepôt Nord",
Code = "SIT-0002",
ClientNom = "REPETTO IMMOBILIER",
Ville = "Lyon",
CodePostal = "69003",
IsPinned = false
}
}
};
Window fenetre = MesureGeometrique.Ouvrir(
new SitesView { DataContext = vueModele },
420);
Control[] descendants = fenetre.GetVisualDescendants().OfType().ToArray();
// 1. Un bouton « Ouvrir » par site, câblé sur la commande d'ouverture.
Button[] boutonsOuvrir = descendants
.OfType