Premiere commit - Omika

This commit is contained in:
2026-10-23 18:46:50 +02:00
commit 8ea76c1b24
2155 changed files with 984301 additions and 0 deletions
@@ -0,0 +1,184 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Headless;
using Avalonia.VisualTree;
using Omika.App.Controls;
using Omika.App.Local.Abstractions;
using Xunit;
namespace Omika.Tests.Mobile;
/// <summary>
/// Bouton « Envoyer un email » : un email exige le réseau, donc le bouton devient
/// INACTIF hors ligne — dans TOUS les écrans, puisque la règle est portée par le
/// contrôle réutilisable et non répétée sur chaque vue.
///
/// <para>
/// Le bouton reste VISIBLE et à sa place : l'action existe, elle est seulement
/// momentanément impossible, et l'info-bulle le dit. Une détection de connectivité
/// en échec ne bloque jamais l'action (repli « considéré en ligne »).
/// </para>
/// </summary>
/// <remarks>
/// Session Avalonia headless : la classe rejoint la collection
/// « ApplicationRuntimeContext » pour qu'aucune autre session Avalonia ne tourne
/// en parallèle (le localisateur d'Avalonia est global au processus).
/// </remarks>
[Collection("ApplicationRuntimeContext")]
public sealed class EmailButtonTests : IDisposable
{
private readonly HeadlessUnitTestSession _session =
HeadlessUnitTestSession.StartNew(typeof(LayoutTestAppBuilder));
public void Dispose()
{
// Le point d'injection est statique : on le remet à zéro entre les classes.
EmailButton.Connectivite = null;
_session.Dispose();
}
private sealed class FauxReseau : INetworkConnectivity
{
public bool Online { get; set; } = true;
public Task<bool> IsOnlineAsync(CancellationToken cancellationToken = default)
=> Task.FromResult(Online);
}
private sealed class FauxReseauEnPanne : INetworkConnectivity
{
public Task<bool> IsOnlineAsync(CancellationToken cancellationToken = default)
=> throw new InvalidOperationException("capteur réseau indisponible");
}
/// <summary>Bouton réellement rendu à l'intérieur du contrôle.</summary>
private static Button BoutonInterne(EmailButton email)
=> email.GetVisualDescendants().OfType<Button>().Single();
private void Executer(Action scenario)
=> _session.Dispatch(
() =>
{
scenario();
return true;
},
CancellationToken.None).GetAwaiter().GetResult();
[Fact]
public void EnLigne_BoutonActif()
{
Executer(() =>
{
var reseau = new FauxReseau { Online = true };
EmailButton.Connectivite = reseau;
var email = new EmailButton();
Window fenetre = MesureGeometrique.Ouvrir(email, 400);
Button bouton = BoutonInterne(email);
Assert.True(bouton.IsEnabled);
Assert.Equal(EmailButton.InfoBulleDisponible, email.InfoBulle);
fenetre.Close();
});
}
[Fact]
public void HorsLigne_BoutonInactifVisibleEtExplique()
{
Executer(() =>
{
var reseau = new FauxReseau { Online = false };
EmailButton.Connectivite = reseau;
var email = new EmailButton();
Window fenetre = MesureGeometrique.Ouvrir(email, 400);
Button bouton = BoutonInterne(email);
// Inactif : aucun clic ne peut partir.
Assert.False(bouton.IsEnabled);
Assert.False(bouton.IsEffectivelyEnabled);
// Mais toujours VISIBLE et à sa place : l'action ne disparaît pas.
Assert.True(email.IsVisible);
Assert.True(bouton.IsVisible);
Assert.True(bouton.Bounds.Width > 0);
// L'info-bulle explique la raison au lieu d'un bouton muet.
Assert.Equal(EmailButton.InfoBulleHorsLigne, email.InfoBulle);
Assert.Contains("Hors ligne", email.InfoBulle);
fenetre.Close();
});
}
[Fact]
public void RetourDuReseau_BoutonReactiveSansChangerDEcran()
{
Executer(() =>
{
var reseau = new FauxReseau { Online = false };
EmailButton.Connectivite = reseau;
var email = new EmailButton();
Window fenetre = MesureGeometrique.Ouvrir(email, 400);
Assert.False(BoutonInterne(email).IsEnabled);
// Le réseau revient : la veille (ou une synchronisation manuelle)
// réévalue l'état, le bouton redevient actif.
reseau.Online = true;
EmailButton.RafraichirTousAsync().GetAwaiter().GetResult();
Assert.True(BoutonInterne(email).IsEnabled);
Assert.Equal(EmailButton.InfoBulleDisponible, email.InfoBulle);
fenetre.Close();
});
}
/// <summary>
/// Une détection de connectivité en ÉCHEC ne doit jamais bloquer l'envoi : le
/// bouton reste actif. Bloquer sur un capteur muet serait une régression pire
/// que le problème d'origine.
/// </summary>
[Fact]
public void DetectionEnEchec_NeBloquePasLeBouton()
{
Executer(() =>
{
EmailButton.Connectivite = new FauxReseauEnPanne();
var email = new EmailButton();
Window fenetre = MesureGeometrique.Ouvrir(email, 400);
Assert.True(BoutonInterne(email).IsEnabled);
Assert.Equal(EmailButton.InfoBulleDisponible, email.InfoBulle);
fenetre.Close();
});
}
/// <summary>
/// Aucune source de connectivité installée (aperçu de design, tests de mise en
/// page) : le bouton reste actif, comportement d'origine préservé.
/// </summary>
[Fact]
public void SansSourceDeConnectivite_ResteActif()
{
Executer(() =>
{
EmailButton.Connectivite = null;
var email = new EmailButton();
Window fenetre = MesureGeometrique.Ouvrir(email, 400);
Assert.True(BoutonInterne(email).IsEnabled);
fenetre.Close();
});
}
}