Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Headless;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Markup.Xaml.Templates;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using Omika.App.Controls;
|
||||
using Omika.App.ViewModels;
|
||||
using Omika.Core.Contracts.Documents;
|
||||
using Xunit;
|
||||
|
||||
namespace Omika.Tests.Mobile;
|
||||
|
||||
/// <summary>
|
||||
/// Champ TEXTE d'une rubrique dynamique (création de RAPPORT et composition
|
||||
/// d'EMAIL) : la saisie doit être TOUJOURS multiligne.
|
||||
///
|
||||
/// <para>
|
||||
/// Un champ sur une seule ligne masquait la fin d'une valeur longue : le texte
|
||||
/// défilait horizontalement au lieu de revenir à la ligne, et l'utilisateur ne
|
||||
/// voyait pas ce qu'il avait écrit. Le type RTF et la longueur de la valeur ne
|
||||
/// décident donc plus du contrôle affiché.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Les deux écrans partagent le MÊME gabarit
|
||||
/// (<c>avares://Omika.App/Views/ChampEditionTemplates.axaml</c>, clé
|
||||
/// <c>ChampEditionDetailTemplate</c>) : ces tests portent donc sur le rendu réel
|
||||
/// du gabarit commun, pas sur une copie.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// (un champ n'a de géométrie qu'une fois disposé).
|
||||
/// </remarks>
|
||||
[Collection("ApplicationRuntimeContext")]
|
||||
public sealed class ChampTexteMultiligneTests : IDisposable
|
||||
{
|
||||
/// <summary>Gabarit partagé entre l'écran Rapport et l'écran de composition d'email.</summary>
|
||||
private const string GabaritPartage = "avares://Omika.App/Views/ChampEditionTemplates.axaml";
|
||||
|
||||
private const string CleGabarit = "ChampEditionDetailTemplate";
|
||||
|
||||
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();
|
||||
|
||||
/// <summary>
|
||||
/// Champ modifiable, valeur courte (« REF ») : le contrôle affiché est un champ
|
||||
/// de saisie MULTILIGNE — retour à la ligne accepté et enroulement actif.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChampTexteModifiable_ValeurCourte_ResteMultiligne()
|
||||
=> Executer(() =>
|
||||
{
|
||||
Window fenetre = Ouvrir(Texte(modifiable: true, valeur: "REF"));
|
||||
try
|
||||
{
|
||||
DictationTextField dictee = ChampDeSaisie(fenetre);
|
||||
TextBox saisie = dictee.FindControl<TextBox>("Saisie")!;
|
||||
|
||||
Assert.True(dictee.Multiline, "Le champ texte n'est pas en mode multiligne.");
|
||||
|
||||
// Deux marqueurs du multiligne : le retour à la ligne est accepté
|
||||
// (touche Entrée) et le texte revient à la ligne tout seul.
|
||||
Assert.True(saisie.AcceptsReturn, "Le champ n'accepte pas le retour à la ligne.");
|
||||
Assert.Equal(TextWrapping.Wrap, saisie.TextWrapping);
|
||||
|
||||
// Et le champ est réellement plus haut qu'une ligne : la place des
|
||||
// lignes suivantes existe dès l'ouverture.
|
||||
Assert.True(
|
||||
saisie.Bounds.Height >= 90,
|
||||
$"Le champ ne fait que {saisie.Bounds.Height:0.##} px de haut.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
fenetre.Close();
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Champ modifiable dont la valeur contient déjà un retour à la ligne : même
|
||||
/// contrôle multiligne (aucun basculement de gabarit selon le contenu).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChampTexteModifiable_ValeurSurPlusieursLignes_ResteMultiligne()
|
||||
=> Executer(() =>
|
||||
{
|
||||
Window fenetre = Ouvrir(Texte(modifiable: true, valeur: "ligne 1\nligne 2"));
|
||||
try
|
||||
{
|
||||
TextBox saisie = ChampDeSaisie(fenetre).FindControl<TextBox>("Saisie")!;
|
||||
|
||||
Assert.True(saisie.AcceptsReturn);
|
||||
Assert.Equal(TextWrapping.Wrap, saisie.TextWrapping);
|
||||
Assert.Equal("ligne 1\nligne 2", saisie.Text);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fenetre.Close();
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Un seul contrôle de saisie est construit pour un champ texte : la vue ne
|
||||
/// peut pas basculer d'un champ simple vers un champ long selon la valeur.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChampTexte_UnSeulControleDeSaisie()
|
||||
=> Executer(() =>
|
||||
{
|
||||
Window fenetre = Ouvrir(Texte(modifiable: true, valeur: "REF"));
|
||||
try
|
||||
{
|
||||
DictationTextField[] champs = fenetre.GetVisualDescendants()
|
||||
.OfType<DictationTextField>()
|
||||
.ToArray();
|
||||
|
||||
// Un seul champ de saisie est DESSINÉ (les autres branches du
|
||||
// gabarit restent masquées).
|
||||
DictationTextField dessine = Assert.Single(champs, c => c.IsVisible);
|
||||
Assert.True(dessine.Multiline);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fenetre.Close();
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Champ NON modifiable : aucun contrôle de saisie, un texte qui revient à la
|
||||
/// ligne (la lecture suit la même largeur d'écran que la saisie).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ChampTexteNonModifiable_TexteQuiRevientALaLigne()
|
||||
=> Executer(() =>
|
||||
{
|
||||
Window fenetre = Ouvrir(Texte(modifiable: false, valeur: "REF"));
|
||||
try
|
||||
{
|
||||
Assert.DoesNotContain(
|
||||
fenetre.GetVisualDescendants().OfType<DictationTextField>(),
|
||||
c => c.IsVisible);
|
||||
|
||||
TextBlock texte = fenetre.GetVisualDescendants()
|
||||
.OfType<TextBlock>()
|
||||
.Single(t => t.IsVisible && t.Text == "REF");
|
||||
|
||||
Assert.Equal(TextWrapping.Wrap, texte.TextWrapping);
|
||||
}
|
||||
finally
|
||||
{
|
||||
fenetre.Close();
|
||||
}
|
||||
});
|
||||
|
||||
// =====================================================================
|
||||
// Aides
|
||||
// =====================================================================
|
||||
|
||||
/// <summary>Rubrique texte de test (type chaîne du modèle de rapport).</summary>
|
||||
private static RapportChampEdition Texte(bool modifiable, string valeur)
|
||||
=> new(new RapportChampDto
|
||||
{
|
||||
Id = "1",
|
||||
Rubrique = "$REFINTERNE$",
|
||||
GroupeApp = "INTER",
|
||||
MembreMob = "IN_RefInterne",
|
||||
Type = "ChpSimpleChaine",
|
||||
ListeDonnesBD = valeur,
|
||||
Modifiable = modifiable
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Construit le gabarit PARTAGÉ (Rapport + Email) pour cette rubrique et
|
||||
/// l'affiche dans une fenêtre de largeur téléphone.
|
||||
/// </summary>
|
||||
private static Window Ouvrir(RapportChampEdition edition)
|
||||
{
|
||||
var dictionnaire = (ResourceDictionary)AvaloniaXamlLoader.Load(new Uri(GabaritPartage));
|
||||
var gabarit = (DataTemplate)dictionnaire[CleGabarit];
|
||||
|
||||
Control contenu = gabarit.Build(edition)!;
|
||||
|
||||
// Le contexte est posé explicitement : sans lui, aucun binding du gabarit
|
||||
// n'est évalué (les contrôleurs resteraient tous visibles, sans valeur),
|
||||
// et le test ne prouverait rien.
|
||||
contenu.DataContext = edition;
|
||||
|
||||
var fenetre = new Window
|
||||
{
|
||||
Width = 400,
|
||||
Height = 900,
|
||||
RequestedThemeVariant = Avalonia.Styling.ThemeVariant.Light,
|
||||
Content = contenu
|
||||
};
|
||||
|
||||
fenetre.Show();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
Dispatcher.UIThread.RunJobs();
|
||||
return fenetre;
|
||||
}
|
||||
|
||||
/// <summary>Le champ de saisie dicté réellement construit par le gabarit.</summary>
|
||||
private static DictationTextField ChampDeSaisie(Window fenetre)
|
||||
=> fenetre.GetVisualDescendants().OfType<DictationTextField>().Single();
|
||||
}
|
||||
Reference in New Issue
Block a user