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);
}
}