using AcDream.App.UI.Layout; namespace AcDream.App.Tests.UI.Layout; /// /// Pure logic tests for / — /// no DAT, no widgets, no runtime. Campaign OP slice OP3 ships the model /// against an EMPTY page (the Gameplay tab has no options at all — research /// doc 2026-08-10-options-panel-structure.md §6); this file also /// exercises a synthetic 2-option page to prove Apply/Reset/Defaults' /// per-row semantics before OP4-6 wire real DAT-backed rows into the same /// model. /// public sealed class OptionPageModelTests { // ── Empty page (the Gameplay tab's own shape) ─────────────────────────── [Fact] public void EmptyPage_ChangedIsAlwaysFalse() { var page = new OptionPage(); Assert.False(page.Changed); } [Fact] public void EmptyPage_ApplyResetDefaults_AreNoOps() { var page = new OptionPage(); page.Apply(); page.Reset(); page.Defaults(); Assert.Empty(page.Rows); Assert.False(page.Changed); } [Fact] public void EmptyPage_OnShownAndOnHidden_DoNotThrow() { var page = new OptionPage(); page.OnShown(); page.OnHidden(); } [Fact] public void EmptyPage_Apply_StillInvokesAfterApply() { // Retail's PlayerOptionPage::SaveCurrentValues flushes the batched // module regardless of whether THIS page's own rows changed // anything — the module's dirty flag is global, not per-page. var page = new OptionPage(); int flushCount = 0; page.AfterApply = () => flushCount++; page.Apply(); Assert.Equal(1, flushCount); } [Fact] public void EmptyPage_OnShown_InvokesAfterApply() { var page = new OptionPage(); int flushCount = 0; page.AfterApply = () => flushCount++; page.OnShown(); Assert.Equal(1, flushCount); } [Fact] public void EmptyPage_ResetAndDefaults_DoNotInvokeAfterApply() { var page = new OptionPage(); int flushCount = 0; page.AfterApply = () => flushCount++; page.Reset(); page.Defaults(); Assert.Equal(0, flushCount); } // ── BoolOptionRow leaf semantics (UIOption_Checkbox port) ─────────────── [Fact] public void BoolOptionRow_SetCurrentValue_AppliesLiveImmediately_WithoutCommitting() { var applied = new List(); var row = new BoolOptionRow(initial: false, defaultValue: false, apply: v => applied.Add(v)); row.SetCurrentValue(true); Assert.True(row.Current); Assert.False(row.Saved); // NOT committed — SetCurrentValue never touches Saved. Assert.True(row.Changed); Assert.Equal([true], applied); } [Fact] public void BoolOptionRow_SaveCurrentValue_CommitsBaseline() { var row = new BoolOptionRow(initial: false, defaultValue: false); row.SetCurrentValue(true); row.SaveCurrentValue(); Assert.True(row.Saved); Assert.False(row.Changed); } [Fact] public void BoolOptionRow_RestoreSavedValue_RevertsAndReapplies() { var applied = new List(); var row = new BoolOptionRow(initial: false, defaultValue: false, apply: v => applied.Add(v)); row.SetCurrentValue(true); applied.Clear(); row.RestoreSavedValue(); Assert.False(row.Current); Assert.False(row.Changed); Assert.Equal([false], applied); } [Fact] public void BoolOptionRow_RestoreDefaultValue_AppliesLiveWithoutCommitting() { var applied = new List(); var row = new BoolOptionRow(initial: false, defaultValue: true, apply: v => applied.Add(v)); row.RestoreDefaultValue(); Assert.True(row.Current); Assert.False(row.Saved); // uncommitted — Changed re-arms Apply/Reset. Assert.True(row.Changed); Assert.Equal([true], applied); } // ── Synthetic 2-option page ────────────────────────────────────────────── private static (OptionPage Page, BoolOptionRow A, BoolOptionRow B) MakeTwoOptionPage() { var page = new OptionPage(); var a = new BoolOptionRow(initial: true, defaultValue: true); var b = new BoolOptionRow(initial: false, defaultValue: true); page.Register(a); page.Register(b); return (page, a, b); } [Fact] public void TwoOptionPage_Changed_TrueWhenAnyRowChanged() { var (page, a, _) = MakeTwoOptionPage(); Assert.False(page.Changed); a.SetCurrentValue(false); Assert.True(page.Changed); } [Fact] public void TwoOptionPage_Apply_CommitsEveryRowUnconditionally() { var (page, a, b) = MakeTwoOptionPage(); a.SetCurrentValue(false); // a changed // b left at its initial (unchanged) value. page.Apply(); Assert.False(page.Changed); Assert.True(a.Saved == a.Current); Assert.True(b.Saved == b.Current); } [Fact] public void TwoOptionPage_Reset_RevertsOnlyChangedRows() { var (page, a, b) = MakeTwoOptionPage(); a.SetCurrentValue(false); // a: true -> false, changed // b stays at its initial false (unchanged, Saved == Current == false). page.Reset(); Assert.True(a.Current); // reverted to its original saved baseline Assert.False(b.Current); // untouched — was never Changed Assert.False(page.Changed); } [Fact] public void TwoOptionPage_Defaults_RestoresEveryRow_WithoutCommitting() { var (page, a, b) = MakeTwoOptionPage(); a.SetCurrentValue(false); page.Apply(); // commit a's change so Changed starts false page.Defaults(); Assert.True(a.Current); // default is true Assert.True(b.Current); // default is true (was false) // Defaults never commits — b's baseline (Saved) is still its old // committed value (false), so Changed re-arms Apply/Reset. Assert.True(page.Changed); } [Fact] public void TwoOptionPage_OnHidden_RevertsUncommittedEdits() { var (page, a, _) = MakeTwoOptionPage(); a.SetCurrentValue(false); page.OnHidden(); Assert.True(a.Current); Assert.False(page.Changed); } [Fact] public void TwoOptionPage_OnShown_AppliesAndCommits() { var (page, a, _) = MakeTwoOptionPage(); a.SetCurrentValue(false); page.OnShown(); Assert.False(page.Changed); Assert.False(a.Saved != a.Current); } }