185 lines
5.9 KiB
C#
185 lines
5.9 KiB
C#
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();
|
|
});
|
|
}
|
|
}
|