using System.Net.Http; using Avalonia; using Avalonia.Controls; using Avalonia.Headless.XUnit; using Avalonia.VisualTree; using CommunityToolkit.Mvvm.Input; using PostIt.ViewModels; using PostIt.Views; using Yavsc.Api.Client; namespace PostIt.Tests; /// /// Headless UI coverage for the "Mes fichiers" page /// (). The user reported that once a file /// is listed, "l'item est inerte au click, et ses boutons restent /// grisés, désactivés" — neither the row nor its action buttons /// (Télécharger / Supprimer) respond to a click. /// /// These tests mount a real under /// the Avalonia headless platform with a stub /// that lists one file, then walk the /// realised visual tree to find the row's action buttons and /// assert the two things that would silently break and reproduce /// the "grayed / inert" symptom: /// /// The Command binding /// ($parent[vm:MyFilesViewModel].DownloadFileCommand) /// resolved to a non-null command — a null command leaves the /// button disabled (grayed) and click-silent. /// The command is armed /// (CanExecute true) for the listed file — an /// AsyncRelayCommand with /// AllowConcurrentExecutions=false (the /// /// default) disables itself while running; a button stuck /// "running" is exactly the grayed-and-inert report. /// Executing the command actually invokes the /// file download / delete on the API client — the row /// "activates" on click instead of being inert. /// /// The OS save picker is absent under headless, so /// FileSaveHelpers.SaveAsync throws or cancels and the /// download command's catch sets an error status — but the /// API download is awaited before the picker, so the call /// is recorded regardless. We do not await the command's /// ExecutionTask to completion, to avoid blocking on the /// (platform-dependent) picker. /// public class MyFilesPageHeadlessTests { private static (MyFilesPage page, MyFilesViewModel vm, StubFsApi api) CreatePage() { var api = new StubFsApi(); var fsClient = new UserFilesApiClient(api, "https://blogs.example/api/v1/"); var vm = new MyFilesViewModel(fsClient); var page = new MyFilesPage { DataContext = vm }; return (page, vm, api); } /// /// Mount the page the way the app does — pushed onto a /// shown in a /// (a realizes its content template via /// the navigation host, cf. RdvPageHeadlessTests) — after /// listing a single file by awaiting /// . Populating /// before the first layout /// pass lets the ItemsControl realise its item templates /// under headless. /// private static async Task<(MyFilesPage page, MyFilesViewModel vm, StubFsApi api)> MountAsync() { var (page, vm, api) = CreatePage(); await vm.InitializeAsync(); var nav = new NavigationPage(); nav.PushAsync(page).GetAwaiter().GetResult(); var window = new Window { Content = nav }; window.Show(); // Let bindings / the ItemsControl item templates apply. await Task.Delay(50); return (page, vm, api); } /// /// Find the first action button in a file row whose content /// matches . The file rows live in an /// ItemsControl DataTemplate (no x:Name), so we /// walk the visual tree. /// private static Button? FindRowButton(MyFilesPage page, string content) => page.GetVisualDescendants() .OfType