diff --git a/src/PostIt.Tests/BlogApiTestFakes.cs b/src/PostIt.Tests/BlogApiTestFakes.cs deleted file mode 100644 index 9ec10f89..00000000 --- a/src/PostIt.Tests/BlogApiTestFakes.cs +++ /dev/null @@ -1,65 +0,0 @@ -using PostIt.Models; -using PostIt.Services; -using PostIt.ViewModels; - -namespace PostIt.Tests; - -/// Per-call ledger shared between the test and the -/// recording fake, so the assertion can inspect what the VM -/// actually sent on the wire without coupling to the fake's -/// internals. -internal sealed class CallRecorder -{ - public (HttpMethod method, string path, object? body) FirstCall => - Calls[0]; - public List<(HttpMethod method, string path, object? body)> Calls { get; } = new(); -} - -/// Test fake that records every CallAsync invocation -/// and answers them with a canned sequence: the first call gets -/// a server-issued BlogPost (Id=42), the second call gets a -/// single-element list containing that post. Used by the ViewModel -/// tests and the headless UI test to capture exactly what the -/// Save button posts to the server. -internal sealed class RecordingYavscApiClient : YavscApiClient -{ - private readonly CallRecorder _recorder; - public RecordingYavscApiClient(CallRecorder recorder) - : base( - new Settings - { - Authentication = new AuthenticationSettings - { - Authority = "https://stub.invalid", - ClientId = "stub", - Scopes = new[] { "openid" }, - }, - }, - new TokenStore(System.IO.Path.GetTempFileName())) - { - _recorder = recorder; - } - - public override Task CallAsync(HttpMethod method, string path, object? body = null, CancellationToken ct = default) - { - _recorder.Calls.Add((method, path, body)); - // BlogPost? boxes to BlogPost at runtime, so we test the - // non-nullable type — typeof(BlogPost?) is a C# error - // (CS8639: "typeof cannot be used on a nullable reference - // type"). - if (typeof(T) == typeof(BlogPost)) - return Task.FromResult((T)(object)new BlogPost - { - Id = 42, - Title = "Mon premier billet", - AuthorId = "tester", - Article = "Contenu du billet de test.", - }); - if (typeof(T) == typeof(List)) - return Task.FromResult((T)(object)new List - { - new() { Id = 42, Title = "Mon premier billet" } - }); - return Task.FromResult(default(T)!); - } -} diff --git a/src/PostIt.Tests/MainPageSaveTests.cs b/src/PostIt.Tests/MainPageSaveTests.cs deleted file mode 100644 index c76115d7..00000000 --- a/src/PostIt.Tests/MainPageSaveTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Headless.XUnit; -using Avalonia.VisualTree; -using PostIt.Models; -using PostIt.Services; -using PostIt.ViewModels; -using PostIt.Views; -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 BlogPost { 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); - var viewModel = new MainPageViewModel(blog); - - var page = new MainPage { 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 = window.GetVisualDescendants() - .OfType