using System; using System.IO; using System.Text.Json; using System.Threading.Tasks; using PostIt.Controls; using PostIt.ViewModels; using Xunit; namespace PostIt.Tests; /// /// Tests for : the contract /// between the page's view model and the . /// The view (XAML + code-behind rendering) is not tested here — the /// control is render-agnostic, and the rendering is plain Polyline /// reconstruction that we'll exercise manually in PostIt.Desktop. /// public class SignaturePageViewModelTests { [Fact] public void Default_constructor_uses_default_dimensions() { var vm = new SignaturePageViewModel(); Assert.Equal(SignaturePageViewModel.DefaultWidth, vm.Width); Assert.Equal(SignaturePageViewModel.DefaultHeight, vm.Height); } [Fact] public void Constructor_rejects_non_positive_dimensions() { Assert.Throws( () => new SignaturePageViewModel(0, 100)); Assert.Throws( () => new SignaturePageViewModel(100, 0)); Assert.Throws( () => new SignaturePageViewModel(-1, 100)); } [Fact] public void Attach_then_Detach_is_idempotent() { var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); vm.Detach(); // Second detach is a no-op: must not throw. vm.Detach(); } [Fact] public void Attach_rejects_null() { var vm = new SignaturePageViewModel(); Assert.Throws(() => vm.Attach(null!)); } [Fact] public void StrokeCompleted_updates_status_and_counts() { var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); // Drive the control via the test hooks so we don't depend // on Avalonia pointer events. pad.AppendPointForTest(1_000, 1_000); pad.AppendPointForTest(2_000, 2_000); pad.SealStrokeForTest(); Assert.Equal(1, vm.StrokeCount); Assert.Equal(2, vm.PointCount); Assert.Contains("1 trait", vm.StatusMessage); } [Fact] public void Clear_resets_counts_and_buffer() { var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); pad.AppendPointForTest(1, 1); pad.SealStrokeForTest(); Assert.Equal(1, vm.StrokeCount); vm.Clear(); Assert.Equal(0, vm.StrokeCount); Assert.Equal(0, vm.PointCount); Assert.Empty(pad.Strokes); Assert.Contains("Effacé", vm.StatusMessage); } [Fact] public async Task CaptureAsync_on_empty_buffer_reports_and_writes_nothing() { var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); await vm.CaptureAsync(); Assert.Contains("Rien", vm.StatusMessage); Assert.Null(vm.LastCapturedPath); } [Fact] public async Task CaptureAsync_writes_a_yavsc_signature_v1_file() { // The VM uses Environment.SpecialFolder.LocalApplicationData, // which we cannot redirect per-call without a constructor // seam. We test the produced file's structure rather than // its text formatting, because System.Text.Json's pretty- // printer is not part of the contract we're locking down. var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); pad.AppendPointForTest(1_000, 2_000); pad.AppendPointForTest(3_000, 4_000); pad.SealStrokeForTest(); await vm.CaptureAsync(); Assert.NotNull(vm.LastCapturedPath); Assert.True(File.Exists(vm.LastCapturedPath!), $"file missing: {vm.LastCapturedPath}"); using var doc = JsonDocument.Parse(File.ReadAllText(vm.LastCapturedPath!)); var root = doc.RootElement; Assert.Equal("yavsc.signature/v1", root.GetProperty("format").GetString()); Assert.Equal(10_000, root.GetProperty("coordinateMax").GetInt32()); Assert.Equal(1, root.GetProperty("strokeCount").GetInt32()); var strokes = root.GetProperty("strokes"); Assert.Equal(JsonValueKind.Array, strokes.ValueKind); // [k=2, x0, y0, x1, y1] Assert.Equal(5, strokes.GetArrayLength()); Assert.Equal(2, strokes[0].GetInt32()); // k (2 points) Assert.Equal(1_000, strokes[1].GetInt32()); // x0 Assert.Equal(2_000, strokes[2].GetInt32()); // y0 Assert.Equal(3_000, strokes[3].GetInt32()); // x1 Assert.Equal(4_000, strokes[4].GetInt32()); // y1 } [Fact] public async Task CaptureAsync_creates_directory_if_missing() { var vm = new SignaturePageViewModel(); var pad = new SignaturePadControl(); vm.Attach(pad); pad.AppendPointForTest(1, 1); pad.SealStrokeForTest(); // The directory must exist after the call (CreateDirectory // in the VM handles this). await vm.CaptureAsync(); var dir = Path.GetDirectoryName(vm.LastCapturedPath!); Assert.NotNull(dir); Assert.True(Directory.Exists(dir), $"directory missing: {dir}"); } }