431 lines
17 KiB
C#
431 lines
17 KiB
C#
using System.Text.Json;
|
|
using Omika.App.Local.Abstractions;
|
|
using Omika.App.Local.Models;
|
|
using Omika.App.ViewModels;
|
|
using Omika.Core.Contracts.Documents;
|
|
|
|
namespace Omika.Tests.Mobile.Local;
|
|
|
|
/// <summary>
|
|
/// Tests de la correction UI « recette » : la sélection UNIQUE ($UN…) doit rendre
|
|
/// les MÊMES cartes que la sélection MULTIPLE ($TAB…) — ligne compacte si non
|
|
/// sélectionnée, développée si sélectionnée — seule la logique de sélection diffère
|
|
/// (max 1 pour UN, plusieurs pour TAB). Vérifie aussi la mutualisation des templates
|
|
/// par TYPE (EstTypePhotos/Contacts/RendezVous/CompteRendus vrais pour UN et TAB),
|
|
/// le résumé utilisateur (jamais CheminComplet) et la notification des états de
|
|
/// miniature (bug du premier affichage).
|
|
/// </summary>
|
|
public class RapportSelectionUiTests
|
|
{
|
|
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
|
|
|
|
// =====================================================================
|
|
// Helpers
|
|
// =====================================================================
|
|
|
|
private static string Serialiser(object valeur) => JsonSerializer.Serialize(valeur, Json);
|
|
|
|
private static RapportChampDto ChampUnique(
|
|
string rubrique,
|
|
string groupeApp,
|
|
string membreMob,
|
|
string listeDonnesBD,
|
|
string listeDonnesChoix)
|
|
=> new()
|
|
{
|
|
Id = "1",
|
|
Nom = "Rubrique",
|
|
Rubrique = rubrique,
|
|
Type = "ChpSimpleChaine",
|
|
GroupeApp = groupeApp,
|
|
MembreMob = membreMob,
|
|
ListeDonnesBD = listeDonnesBD,
|
|
ListeDonnesChoix = listeDonnesChoix
|
|
};
|
|
|
|
private static RapportChampDto Tableau(string rubrique, string groupeApp, string listeDonnesBD)
|
|
=> new()
|
|
{
|
|
Id = "1",
|
|
Nom = "Tableau",
|
|
Rubrique = rubrique,
|
|
Type = "Tableau",
|
|
GroupeApp = groupeApp,
|
|
ListeDonnesBD = listeDonnesBD
|
|
};
|
|
|
|
private static string ContactsJson(params string[] noms)
|
|
{
|
|
var items = noms.Select(n => new
|
|
{
|
|
id = Guid.NewGuid().ToString(),
|
|
nom = n,
|
|
role = "Occupant",
|
|
telFixe = "04 11 22 33 44",
|
|
telMobile = "06 00 00 00 00",
|
|
etage = "1"
|
|
});
|
|
return Serialiser(items);
|
|
}
|
|
|
|
private static string PhotosJson(params (string Nom, string Chemin)[] photos)
|
|
{
|
|
var items = photos.Select(p => new
|
|
{
|
|
guid = Guid.NewGuid().ToString(),
|
|
cheminComplet = p.Chemin,
|
|
nom = p.Nom,
|
|
date = "2026-01-30",
|
|
notes = (string?)null
|
|
});
|
|
return Serialiser(items);
|
|
}
|
|
|
|
private static string RdvJson(params (string Debut, string Fin, string Objet)[] rdvs)
|
|
{
|
|
var items = rdvs.Select(r => new
|
|
{
|
|
id = Guid.NewGuid().ToString(),
|
|
dateDebut = r.Debut,
|
|
dateFin = r.Fin,
|
|
objet = r.Objet,
|
|
note = (string?)null,
|
|
collaborateurs = "Tech 1"
|
|
});
|
|
return Serialiser(items);
|
|
}
|
|
|
|
private static string CrJson(params (string Debut, string Fin, string Collaborateur, string Signataire)[] crs)
|
|
{
|
|
var items = crs.Select(c => new
|
|
{
|
|
id = Guid.NewGuid().ToString(),
|
|
debut = c.Debut,
|
|
fin = c.Fin,
|
|
collaborateur = c.Collaborateur,
|
|
signataire = c.Signataire
|
|
});
|
|
return Serialiser(items);
|
|
}
|
|
|
|
private static TableauOptionEdition PhotoOption(string? guid = null, string? cheminComplet = null)
|
|
{
|
|
using JsonDocument doc = JsonDocument.Parse(Serialiser(
|
|
new { guid, cheminComplet, nom = "photo.jpg", date = "2026-01-30", notes = "Note" }));
|
|
return new TableauOptionEdition(doc.RootElement.Clone(), "photo.jpg", TypeTableau.Photos);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Mutualisation par TYPE (UN et TAB partagent le même template)
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void UnCtsite_EstTypeContacts_True()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCTSITE.NOMPRENOM$", "CONTACT_SITE", "saNom",
|
|
"Jean Dupont", ContactsJson("Jean Dupont", "Marie Martin")));
|
|
|
|
Assert.True(edition.EstTypeContacts);
|
|
Assert.False(edition.EstTypePhotos);
|
|
}
|
|
|
|
[Fact]
|
|
public void TabCtsite_EstTypeContacts_True()
|
|
{
|
|
var edition = new RapportChampEdition(Tableau(
|
|
"$TABCONTACTSITE$", "CONTACT_SITE", ContactsJson("Jean Dupont", "Marie Martin")));
|
|
|
|
Assert.True(edition.EstTypeContacts);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnPhoto_EstTypePhotos_True()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNPHOTO.IMAGE$", "PHOTO_INTER", "cheminComplet",
|
|
"C:\\Documents_Demo\\photo1.jpg", PhotosJson(("photo1.jpg", "C:\\Documents_Demo\\photo1.jpg"))));
|
|
|
|
// UNPHOTO utilise le MÊME template que TABPHOTO (et déclenche le chargement
|
|
// des miniatures, qui n'est plus limité à AChoixMultiple).
|
|
Assert.True(edition.EstTypePhotos);
|
|
}
|
|
|
|
[Fact]
|
|
public void TabPhoto_EstTypePhotos_True()
|
|
{
|
|
var edition = new RapportChampEdition(Tableau(
|
|
"$TABPHOTO$", "PHOTO_INTER", PhotosJson(("photo1.jpg", "C:\\Documents_Demo\\photo1.jpg"))));
|
|
|
|
Assert.True(edition.EstTypePhotos);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnRdv_EstTypeRendezVous_True()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNRDV.OBJET$", "RDV_INTER", "saObjet",
|
|
"Objet", RdvJson(("2026-01-30 10:00:00", "2026-01-30 11:00:00", "Objet"))));
|
|
|
|
Assert.True(edition.EstTypeRendezVous);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnCr_EstTypeCompteRendus_True()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCR.SIGNATAIRE$", "CR_INTER", "saSignataire",
|
|
"Signataire", CrJson(("2026-01-30 08:00:00", "2026-01-30 09:00:00", "Tech", "Signataire"))));
|
|
|
|
Assert.True(edition.EstTypeCompteRendus);
|
|
}
|
|
|
|
[Fact]
|
|
public void Consigne_Unique_SelectionnezUnElement()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCTSITE.NOMPRENOM$", "CONTACT_SITE", "saNom",
|
|
"Jean Dupont", ContactsJson("Jean Dupont")));
|
|
|
|
Assert.Equal("Sélectionnez un élément", edition.Consigne);
|
|
}
|
|
|
|
[Fact]
|
|
public void Consigne_MultiplePhotos_ConsignePhotos()
|
|
{
|
|
var edition = new RapportChampEdition(Tableau(
|
|
"$TABPHOTO$", "PHOTO_INTER", PhotosJson(("photo1.jpg", "C:\\photo1.jpg"))));
|
|
|
|
Assert.Equal("Sélectionnez les photos du rapport", edition.Consigne);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Single vs Multi : une seule carte développée / plusieurs
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void UnCtsite_ChangementSelection_AncienneRepliee()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCTSITE.NOMPRENOM$", "CONTACT_SITE", "saNom",
|
|
"Renaud Oscar", ContactsJson("Renaud Oscar", "Alves Victoire", "Philippe Philippe")));
|
|
|
|
// Pré-sélection = premier élément (une seule carte développée).
|
|
Assert.True(edition.Options[0].EstSelectionne);
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
|
|
// Sélection « Alves Victoire » (index 1) : l'ancienne se replie.
|
|
edition.BasculerOptionCommand.Execute(edition.Options[1]);
|
|
Assert.True(edition.Options[1].EstSelectionne);
|
|
Assert.False(edition.Options[0].EstSelectionne);
|
|
Assert.False(edition.Options[2].EstSelectionne);
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
|
|
// Sélection « Philippe Philippe » (index 2) : Alves se replie.
|
|
edition.BasculerOptionCommand.Execute(edition.Options[2]);
|
|
Assert.True(edition.Options[2].EstSelectionne);
|
|
Assert.False(edition.Options[1].EstSelectionne);
|
|
Assert.False(edition.Options[0].EstSelectionne);
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
}
|
|
|
|
[Fact]
|
|
public void TabCtsite_DeuxSelectionnes_DeuxDeveloppes()
|
|
{
|
|
var edition = new RapportChampEdition(Tableau(
|
|
"$TABCONTACTSITE$", "CONTACT_SITE",
|
|
ContactsJson("Renaud Oscar", "Alves Victoire", "Philippe Philippe")));
|
|
|
|
edition.Options[0].EstSelectionne = true;
|
|
edition.Options[1].EstSelectionne = true;
|
|
|
|
Assert.True(edition.Options[0].EstSelectionne);
|
|
Assert.True(edition.Options[1].EstSelectionne);
|
|
Assert.False(edition.Options[2].EstSelectionne);
|
|
Assert.Equal(2, edition.NombreSelectionnes);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnRdv_UneSeuleCarteDeveloppee()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNRDV.OBJET$", "RDV_INTER", "saObjet",
|
|
"Objet 1",
|
|
RdvJson(
|
|
("2026-01-30 10:00:00", "2026-01-30 11:00:00", "Objet 1"),
|
|
("2026-01-30 12:00:00", "2026-01-30 13:00:00", "Objet 2"),
|
|
("2026-01-30 14:00:00", "2026-01-30 15:00:00", "Objet 3"))));
|
|
|
|
edition.BasculerOptionCommand.Execute(edition.Options[1]);
|
|
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
Assert.True(edition.Options[1].EstSelectionne);
|
|
Assert.False(edition.Options[0].EstSelectionne);
|
|
Assert.False(edition.Options[2].EstSelectionne);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnCr_UneSeuleCarteDeveloppee()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCR.SIGNATAIRE$", "CR_INTER", "saSignataire",
|
|
"Signataire 1",
|
|
CrJson(
|
|
("2026-01-30 08:00:00", "2026-01-30 09:00:00", "Tech 1", "Signataire 1"),
|
|
("2026-01-30 09:00:00", "2026-01-30 10:00:00", "Tech 2", "Signataire 2"),
|
|
("2026-01-30 10:00:00", "2026-01-30 11:00:00", "Tech 3", "Signataire 3"))));
|
|
|
|
edition.BasculerOptionCommand.Execute(edition.Options[2]);
|
|
|
|
Assert.Equal(1, edition.NombreSelectionnes);
|
|
Assert.True(edition.Options[2].EstSelectionne);
|
|
Assert.False(edition.Options[0].EstSelectionne);
|
|
Assert.False(edition.Options[1].EstSelectionne);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Résumé utilisateur (jamais CheminComplet / donnée technique)
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void UnPhoto_Resume_NomPhotoPasChemin()
|
|
{
|
|
string chemin = "C:\\Documents_Demo\\402 ORCHIDEES\\chambre_16020067.jpg";
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNPHOTO.IMAGE$", "PHOTO_INTER", "cheminComplet",
|
|
chemin, PhotosJson(("chambre_16020067.jpg", chemin))));
|
|
|
|
// La donnée TECHNIQUE (maître IMAGE) reste le chemin serveur pour la génération…
|
|
Assert.Equal(chemin, edition.Valeur);
|
|
|
|
// …mais le RÉSUMÉ UI affiche le nom de fichier, jamais le chemin.
|
|
var resume = new RapportRubriqueResume(edition);
|
|
Assert.Equal("chambre_16020067.jpg", resume.Resume);
|
|
Assert.DoesNotContain("C:\\", resume.Resume);
|
|
Assert.DoesNotContain("cheminComplet", resume.Resume);
|
|
}
|
|
|
|
[Fact]
|
|
public void TabPhoto_Resume_CompteSansChemin()
|
|
{
|
|
var edition = new RapportChampEdition(Tableau(
|
|
"$TABPHOTO$", "PHOTO_INTER",
|
|
PhotosJson(
|
|
("chambre_16020067.jpg", "C:\\Documents_Demo\\chambre_16020067.jpg"),
|
|
("peinture_16020986.jpg", "C:\\Documents_Demo\\peinture_16020986.jpg"))));
|
|
|
|
edition.Options[0].EstSelectionne = true;
|
|
edition.Options[1].EstSelectionne = true;
|
|
|
|
var resume = new RapportRubriqueResume(edition);
|
|
Assert.Equal("2 photos sélectionnées", resume.Resume);
|
|
Assert.DoesNotContain("C:\\", resume.Resume);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnCtsite_Resume_NomContact()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNCTSITE.NOMPRENOM$", "CONTACT_SITE", "saNom",
|
|
"Renaud Oscar", ContactsJson("Renaud Oscar", "Alves Victoire")));
|
|
|
|
var resume = new RapportRubriqueResume(edition);
|
|
Assert.Equal("Renaud Oscar", resume.Resume);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnRdv_Resume_FourchetteHoraire()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNRDV.OBJET$", "RDV_INTER", "saObjet",
|
|
"Objet",
|
|
RdvJson(("2026-01-30 10:00:00", "2026-01-30 11:00:00", "Objet"))));
|
|
|
|
var resume = new RapportRubriqueResume(edition);
|
|
Assert.Equal("30/01/2026 10:00 → 30/01/2026 11:00", resume.Resume);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Miniatures : notification des états (bug du premier affichage)
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public void Miniature_Etats_NotifientLeRafraichissementUi()
|
|
{
|
|
TableauOptionEdition option = PhotoOption(Guid.NewGuid().ToString(), null);
|
|
var notifications = new List<string?>();
|
|
option.PropertyChanged += (_, e) => notifications.Add(e.PropertyName);
|
|
|
|
// LOADING : le placeholder doit se masquer (spinner) — la propriété calculée
|
|
// AffichePlaceholderMiniature est re-notifiée (sans quoi l'UI reste figée
|
|
// jusqu'à la navigation suivante).
|
|
option.EstChargementMiniature = true;
|
|
Assert.Contains("AffichePlaceholderMiniature", notifications);
|
|
|
|
// UNAVAILABLE : la propriété calculée est également re-notifiée.
|
|
notifications.Clear();
|
|
option.MiniatureIndisponible = true;
|
|
Assert.Contains("AffichePlaceholderMiniature", notifications);
|
|
|
|
// Fin de chargement (offline) : retour au placeholder.
|
|
notifications.Clear();
|
|
option.EstChargementMiniature = false;
|
|
Assert.Contains("AffichePlaceholderMiniature", notifications);
|
|
Assert.True(option.AffichePlaceholderMiniature);
|
|
Assert.False(option.AfficheMiniature);
|
|
}
|
|
|
|
// =====================================================================
|
|
// Chargement des miniatures pour UNPHOTO (non limité à AChoixMultiple)
|
|
// =====================================================================
|
|
|
|
[Fact]
|
|
public async Task UnPhoto_ChargerMiniaturesAsync_ChargeLesOptions()
|
|
{
|
|
var edition = new RapportChampEdition(ChampUnique(
|
|
"$UNPHOTO.IMAGE$", "PHOTO_INTER", "cheminComplet",
|
|
"C:\\Documents_Demo\\photo1.jpg",
|
|
PhotosJson(
|
|
("photo1.jpg", "C:\\Documents_Demo\\photo1.jpg"),
|
|
("photo2.jpg", "C:\\Documents_Demo\\photo2.jpg"))));
|
|
|
|
var service = new CompteurDocumentService();
|
|
await edition.ChargerMiniaturesAsync(service, CancellationToken.None);
|
|
|
|
Assert.Equal(2, service.EnsureDownloadedCalls);
|
|
Assert.All(edition.Options, o => Assert.True(o.EstPhoto));
|
|
}
|
|
|
|
private sealed class CompteurDocumentService : IDocumentLocalService
|
|
{
|
|
public int EnsureDownloadedCalls { get; private set; }
|
|
|
|
public Task<bool> EnsureDownloadedAsync(Guid documentGuid, CancellationToken ct = default)
|
|
{
|
|
EnsureDownloadedCalls++;
|
|
return Task.FromResult(false);
|
|
}
|
|
|
|
public Task<string?> GetCachedFilePathAsync(Guid documentGuid, CancellationToken ct = default)
|
|
=> Task.FromResult<string?>(null);
|
|
|
|
public Task<IReadOnlyList<InterventionDocumentItem>> GetDocumentsForInterventionAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<bool> OpenAsync(Guid documentGuid, CancellationToken ct = default) => throw new NotImplementedException();
|
|
public Task<IReadOnlyList<DocumentNoteItem>> GetNotesForDocumentAsync(Guid documentGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<DocumentNoteItem> AddNoteAsync(Guid documentGuid, string contenu, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDocumentItem?> AddPhotoAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<AddPhotosResult> AddPhotosAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDocumentItem?> AddPhotoFromCameraAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDocumentItem?> AddDocumentAsync(Guid interventionGuid, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
public Task<InterventionDocumentItem?> AddAnnotatedPhotoAsync(Guid interventionGuid, Guid sourceDocumentGuid, byte[] pngBytes, string fileName, CancellationToken ct = default)
|
|
=> throw new NotImplementedException();
|
|
}
|
|
}
|