diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-22 00:35:03 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-22 00:35:03 +0800 |
| commit | 1cef3b1544ff8574c7b56bc1a84e3be0846a9b22 (patch) | |
| tree | 5aa705bf3e9cfe82e5e3637cdc699bc52f93c559 /src/LivingVillage.Desktop.Tests | |
| parent | 1f1c502ba750c4c55ac2aca28eacafcd0c3ae932 (diff) | |
| parent | 81a84f9f86d59387a349caa339148c7a7a6d1e72 (diff) | |
| download | living-village-1cef3b1544ff8574c7b56bc1a84e3be0846a9b22.tar.gz | |
merge: P16 启动画面/开始界面/HUD 精美化(独立复验全绿 Desktop 106/Kernel 89)
Diffstat (limited to 'src/LivingVillage.Desktop.Tests')
4 files changed, 160 insertions, 9 deletions
diff --git a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj index a8aac24..60d9995 100644 --- a/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj +++ b/src/LivingVillage.Desktop.Tests/LivingVillage.Desktop.Tests.fsproj @@ -9,6 +9,7 @@ <Compile Include="DesktopTests.fs" /> <Compile Include="M6aTests.fs" /> <Compile Include="M6bTests.fs" /> + <Compile Include="P16UiTests.fs" /> <Compile Include="PrototypeTests.fs" /> <Compile Include="M3BatchOutputTests.fs" /> <Compile Include="SampleTests.fs" /> diff --git a/src/LivingVillage.Desktop.Tests/M6bTests.fs b/src/LivingVillage.Desktop.Tests/M6bTests.fs index 5aea9bd..1b2ef70 100644 --- a/src/LivingVillage.Desktop.Tests/M6bTests.fs +++ b/src/LivingVillage.Desktop.Tests/M6bTests.fs @@ -13,8 +13,9 @@ type M6bMenuTests () = Assert.AreEqual<MenuPage>(MainMenu, state.Page) Assert.AreEqual<MenuItem list>( - [ NewGameItem; LoadItem; SettingsItem; ControlsItem; ExitItem ], + [ NewGameItem; LoadItem; ExitItem ], MenuState.items state) + Assert.AreEqual<string list>([ "开始"; "读档"; "退出" ], MenuState.items state |> List.map MenuState.itemLabel) Assert.AreEqual<int>(0, state.Selected) [<TestMethod>] @@ -32,7 +33,7 @@ type M6bMenuTests () = [<TestMethod>] member _.SettingsCanSelectEveryExistingSimulationMode () = let initial = MenuState.create SimulationControl.initial false - let settings, _ = MenuState.update Confirm (MenuState.move Down (MenuState.move Down initial)) + let settings = MenuState.openSettings initial let oneX, oneXCommand = MenuState.update Confirm settings let twoX, twoXCommand = MenuState.update Confirm (MenuState.move Down settings) let fiveX, fiveXCommand = MenuState.update Confirm (MenuState.move Down (MenuState.move Down settings)) diff --git a/src/LivingVillage.Desktop.Tests/P16UiTests.fs b/src/LivingVillage.Desktop.Tests/P16UiTests.fs new file mode 100644 index 0000000..b071dfa --- /dev/null +++ b/src/LivingVillage.Desktop.Tests/P16UiTests.fs @@ -0,0 +1,149 @@ +namespace LivingVillage.Desktop.Tests + +open Microsoft.VisualStudio.TestTools.UnitTesting +open LivingVillage.Kernel +open LivingVillage.Desktop + +// ---- P16 启动画面 / 开始界面 ---- + +[<TestClass>] +type P16LaunchTests () = + + [<TestMethod>] + member _.LaunchRevealAndVeilAreDeterministicAndComplete () = + Assert.AreEqual<float32>(0.0f, LaunchScreen.revealProgress 0L) + Assert.AreEqual<float32>(1.0f, LaunchScreen.revealProgress LaunchScreen.fadeInFrames) + Assert.AreEqual<float32>(1.0f, LaunchScreen.revealProgress 1000L) + Assert.AreEqual<float32>(1.0f, LaunchScreen.veilAlpha 0L) + Assert.AreEqual<float32>(0.0f, LaunchScreen.veilAlpha LaunchScreen.durationFrames) + Assert.IsTrue(LaunchScreen.veilAlpha 30L > LaunchScreen.veilAlpha 90L, "veil must decrease monotonically") + Assert.IsFalse(LaunchScreen.isComplete (LaunchScreen.durationFrames - 1L)) + Assert.IsTrue(LaunchScreen.isComplete LaunchScreen.durationFrames) + + [<TestMethod>] + member _.LaunchSkipIsEscOrSpaceAndTextUsesTheAtlasPath () = + Assert.IsTrue(LaunchScreen.skipRequested true false) + Assert.IsTrue(LaunchScreen.skipRequested false true) + Assert.IsFalse(LaunchScreen.skipRequested false false) + Assert.IsTrue(PixelText.isRenderable LaunchScreen.title) + Assert.IsTrue(PixelText.isRenderable LaunchScreen.skipHint) + Assert.IsTrue(PixelText.isRenderable LaunchScreen.subtitle) + Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains LaunchScreen.skipHint) + +[<TestClass>] +type P16StartMenuTests () = + + [<TestMethod>] + member _.StartMenuIsExactlyThreeItemsWithChineseLabels () = + let state = MenuState.create SimulationControl.initial false + + Assert.AreEqual<int>(3, MenuState.items state |> List.length) + Assert.AreEqual<string list>([ "开始"; "读档"; "退出" ], MenuState.items state |> List.map MenuState.itemLabel) + + [<TestMethod>] + member _.StartMenuNavigationSelectsEachItemAndWraps () = + let state = MenuState.create SimulationControl.initial false + let down1 = MenuState.move Down state + let down2 = MenuState.move Down down1 + let wrap = MenuState.move Down down2 + + Assert.AreEqual<MenuItem>(NewGameItem, MenuState.selectedItem state) + Assert.AreEqual<MenuItem>(LoadItem, MenuState.selectedItem down1) + Assert.AreEqual<MenuItem>(ExitItem, MenuState.selectedItem down2) + Assert.AreEqual<MenuItem>(NewGameItem, MenuState.selectedItem wrap) + + Assert.AreEqual<MenuCommand>(StartNewGame, MenuState.update Confirm state |> snd) + Assert.AreEqual<MenuCommand>(LoadGame, MenuState.update Confirm down1 |> snd) + Assert.AreEqual<MenuCommand>(ExitGame, MenuState.update Confirm down2 |> snd) + + [<TestMethod>] + member _.ReturningToMenuOffersContinueAsTheFirstOfThreeItems () = + let playing = MenuState.create SimulationControl.initial true |> MenuState.enterGame + let main = MenuState.openMain playing + + Assert.AreEqual<MenuItem list>([ ContinueItem; LoadItem; ExitItem ], MenuState.items main) + Assert.AreEqual<string list>([ "继续"; "读档"; "退出" ], MenuState.items main |> List.map MenuState.itemLabel) + Assert.AreEqual<MenuCommand>(ContinueGame, MenuState.update Confirm main |> snd) + +// ---- P16 HUD 顶部窄信息条 ---- + +[<TestClass>] +type P16HudTests () = + + let world = Sim.initialWorldN 42UL 4 + + [<TestMethod>] + member _.HudBarIsANarrowStripNotAFullscreenBlock () = + Assert.IsTrue(HudLayout.barHeight <= 40, "HUD bar must stay a narrow strip") + Assert.IsTrue(HudLayout.barHeight * 4 < 720, "HUD bar must not dominate the viewport") + + [<TestMethod>] + member _.HudSegmentsAreDeterministicAndUseRealState () = + let control = SimulationControl.initial + let segments = HudLayout.segments world control + Assert.AreEqual<string list>([ "时段"; "时间"; "倍速"; "体力"; "钱币" ], segments |> List.map (fun segment -> segment.Label)) + Assert.AreEqual<HudLayout.Segment list>(segments, HudLayout.segments world control) + let energy = segments |> List.find (fun segment -> segment.Label = "体力") + Assert.AreEqual<string>(sprintf "%.0f" world.Avatar.Mind.Needs.Energy, energy.Value) + let money = segments |> List.find (fun segment -> segment.Label = "钱币") + Assert.AreEqual<string>(sprintf "%.0f" world.Avatar.Mind.Needs.Money, money.Value) + + [<TestMethod>] + member _.HudLayoutPlacesSegmentsDeterministicallyWithinWidth () = + let placed = HudLayout.layout (HudLayout.segments world SimulationControl.initial) + Assert.AreEqual<int>(5, placed.Length) + let (firstX, _, _) = List.head placed + Assert.AreEqual<int>(HudLayout.paddingX, firstX) + placed + |> List.pairwise + |> List.iter (fun ((x1, w1, _), (x2, _, _)) -> + Assert.IsTrue(x2 >= x1 + w1, sprintf "segments overlap: %d+%d vs %d" x1 w1 x2)) + let (lastX, lastWidth, _) = List.last placed + Assert.IsTrue(lastX + lastWidth <= 1280, "last segment must fit inside a 1280 viewport") + + [<TestMethod>] + member _.HudClockAndPeriodArePureFunctions () = + let ticksPerHour = Sim.ticksPerDay / 24L + Assert.AreEqual<string>("12:00", M6Presentation.clockTimeLabel (12L * ticksPerHour)) + Assert.AreEqual<string>("00:00", M6Presentation.clockTimeLabel 0L) + Assert.AreEqual<string>("06:30", M6Presentation.clockTimeLabel (6L * ticksPerHour + ticksPerHour / 2L)) + Assert.AreEqual<string>("白天", M6Presentation.timePeriodLabel (12L * ticksPerHour)) + Assert.AreEqual<string>("夜晚", M6Presentation.timePeriodLabel 0L) + Assert.AreEqual<string>("清晨", M6Presentation.timePeriodLabel (6L * ticksPerHour)) + Assert.AreEqual<string>("黄昏", M6Presentation.timePeriodLabel (19L * ticksPerHour)) + + [<TestMethod>] + member _.HudNightPaletteLeansCoolAndLabelsAreAtlasBacked () = + let day = HudLayout.palette Day + let night = HudLayout.palette Night + Assert.IsTrue(night.Accent.B > night.Accent.R, "night accent should lean cool/blue (winter-night tint)") + Assert.IsTrue(day.Accent.R > day.Accent.B, "day accent should lean warm") + for label in [ "时段"; "时间"; "倍速"; "体力"; "钱币" ] do + Assert.IsTrue(PixelText.isRenderable label, sprintf "HUD label %s must render" label) + Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "时段") + Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "体力") + Assert.IsTrue(ChineseText.requiredUiLabels |> List.contains "钱币") + +// ---- P16-FIX 菜单截图帧表 ---- + +[<TestClass>] +type P16MenuShotTests () = + + [<TestMethod>] + member _.MenuShotFrameTableIsOrderedAndNamesAreDistinct () = + Assert.IsTrue(MenuShotScript.splashMidFrame > 0L) + Assert.IsTrue(LaunchScreen.revealProgress MenuShotScript.splashMidFrame > 0.0f) + Assert.IsTrue(LaunchScreen.revealProgress MenuShotScript.splashMidFrame < 1.0f) + Assert.IsTrue(MenuShotScript.splashMidFrame < MenuShotScript.initialFrame) + Assert.IsTrue(MenuShotScript.initialFrame < MenuShotScript.downFrame) + Assert.IsTrue(MenuShotScript.downFrame < MenuShotScript.loadFrame) + Assert.IsTrue(MenuShotScript.loadFrame < MenuShotScript.exitFrame) + Assert.IsTrue(MenuShotScript.initialFrame >= 120L, "initial shot must wait for the 2s entrance to settle") + let names = [ MenuShotScript.splashMidName; MenuShotScript.initialName; MenuShotScript.loadName ] + Assert.AreEqual<int>(names.Length, names |> List.distinct |> List.length) + + [<TestMethod>] + member _.MenuShotSelectsStartThenLoadOnFreshState () = + let state = MenuState.create SimulationControl.initial false + Assert.AreEqual<MenuItem>(NewGameItem, MenuState.selectedItem state) + Assert.AreEqual<MenuItem>(LoadItem, MenuState.selectedItem (MenuState.move Down state)) diff --git a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs index 658e2e1..88f6016 100644 --- a/src/LivingVillage.Desktop.Tests/PrototypeTests.fs +++ b/src/LivingVillage.Desktop.Tests/PrototypeTests.fs @@ -119,10 +119,11 @@ type PrototypeTests () = let controlLabels = MenuState.controlRows |> List.map (fun row -> row.Action) let settingsLabels = MenuState.settings |> List.map MenuState.settingsLabel - Assert.IsTrue(itemLabels |> List.contains "开始新游戏") - Assert.IsTrue(itemLabels |> List.contains "读取存档") - Assert.IsTrue(itemLabels |> List.contains "设置") + Assert.IsTrue(itemLabels |> List.contains "开始") + Assert.IsTrue(itemLabels |> List.contains "读档") Assert.IsTrue(itemLabels |> List.contains "退出") + Assert.AreEqual<string>("设置", MenuState.itemLabel SettingsItem) + Assert.AreEqual<string>("操作说明", MenuState.itemLabel ControlsItem) Assert.AreEqual<string list>( [ "移动" "互动" @@ -791,9 +792,8 @@ type PrototypeTests () = Assert.IsTrue(rect.Y >= 0 && rect.Y + rect.Height <= height, sprintf "y overflow %A %A" (anchorX, anchorY) rect) // Never cover the bottom-left HUD band (camera 0: band starts at height - 160). Assert.IsTrue(rect.Y + rect.Height <= height - FloatingPrompt.hudBottomBandHeight + 8, sprintf "bottom HUD overlap %A %A" (anchorX, anchorY) rect) - // Never cover the top-right status strip. - if rect.X + rect.Width >= width - FloatingPrompt.hudTopRightWidth then - Assert.IsTrue(rect.Y >= FloatingPrompt.hudTopRightHeight, sprintf "top-right HUD overlap %A %A" (anchorX, anchorY) rect) + // Never cover the full-width top status bar. + Assert.IsTrue(rect.Y >= FloatingPrompt.hudTopBarHeight, sprintf "top HUD overlap %A %A" (anchorX, anchorY) rect) [<TestMethod>] member _.FloatingPromptMeasureMatchesPixelTextAdvances () = @@ -945,7 +945,7 @@ type PrototypeTests () = [<TestMethod>] member _.CjkGlyphAtlasHasAStableCharacterTableAndGeometry () = - Assert.AreEqual<int>(228, characters.Length) + Assert.AreEqual<int>(238, characters.Length) CollectionAssert.AreEqual(characters, characters |> Array.sort) Assert.AreEqual<int>(characters.Length, characters |> Array.distinct |> Array.length) Assert.AreEqual<int>(16, columns) |
