using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.VisualTree;
using Yavsc.Blogspot;
using Yavsc.Api.Client;
using PostIt.ViewModels;
using PostIt.Views;
using PostIt.Views.Blogs;
namespace PostIt.Tests;
///
/// Headless UI tests for the "Save" flow in .
/// The pattern is the one SessionStatusBannerTests
/// established: [AvaloniaFact], a
/// hosting the page (via a because
/// MainPage is a ContentPage), then drive the
/// controls through their public surface and assert on what
/// saw go on the wire.
///
/// The bug we are pinning: the title TextBox is
/// currently {Binding SelectedPost.Title, Mode=TwoWay}.
/// When SelectedPost is null (i.e. the user has not yet
/// clicked an item in the posts list — which is the only state
/// in which a brand-new post can be created), the binding has
/// no target and the user's keystrokes are silently dropped.
/// Clicking "Save" then routes to the VM branch
/// if (SelectedPost is null) { new BlogPostDto { Title = string.Empty, ... } }
/// which the controller rejects with 400 "The Title field is
/// required." This test fails on that branch today and will
/// pass once the VM owns a dedicated Title/Article
/// buffer that the XAML binds to and the Save command consumes.
///
public class MainPageSaveTests
{
[AvaloniaFact]
public async Task Typing_a_title_then_clicking_Save_sends_that_title_in_the_post_body()
{
// Arrange: VM with a recording API client, mounted in a
// headless window via a Frame (MainPage is a ContentPage,
// not a Control, so it needs a navigation host).
var recorder = new CallRecorder();
var api = new RecordingYavscApiClient(recorder);
var blog = new BlogApiClient(api, "http://localhost/");
var viewModel = new BlogsViewModel(blog);
var page = new BlogsPage { DataContext = viewModel };
// MainPage is a ContentPage (a Page, not a Control), so it
// must be hosted in a navigation surface. The production
// MainWindow.axaml uses NavigationPage, and the API is the
// same one App.axaml.cs drives at boot (PushAsync, fire-
// and-forget in prod because the page is the top of the
// stack immediately).
var nav = new NavigationPage();
_ = nav.PushAsync(page);
var window = new Window { Content = nav };
window.Show();
// Act: type a title into the editor's TextBox without
// first selecting a post in the list — the only state in
// which a new post can be created. Then click Save.
var titleBox = window.GetVisualDescendants()
.OfType()
.First(t => t.PlaceholderText == "Title");
const string typed = "Mon premier billet";
titleBox.Text = typed;
var saveButton = page.SaveButton;
saveButton.Command!.Execute(null);
// The Save command is async (RelayCommand over Task) but
// ExecuteAsync would await; the sync Execute enqueues the
// task on the dispatcher. Give the dispatcher a chance to
// run so the awaited CallAsync has actually fired before
// we inspect the recorder.
await Task.Delay(200);
// Assert: the first POST to "blog" carried a BlogPostDto
// whose Title is exactly what the user typed. The bug
// fails this assertion with Title == string.Empty.
Assert.NotEmpty(recorder.Calls);
var (method, path, body) = recorder.Calls[1];
Assert.Equal(HttpMethod.Post, method);
Assert.Equal("blogspot", path);
var sent = Assert.IsType(body);
Assert.Equal(typed, sent.Title);
}
}