namespace LivingVillage.Desktop.Tests open Microsoft.VisualStudio.TestTools.UnitTesting open LivingVillage.Kernel open LivingVillage.Desktop // ---- P16 启动画面 / 开始界面 ---- [] type P16LaunchTests () = [] member _.LaunchRevealAndVeilAreDeterministicAndComplete () = Assert.AreEqual(0.0f, LaunchScreen.revealProgress 0L) Assert.AreEqual(1.0f, LaunchScreen.revealProgress LaunchScreen.fadeInFrames) Assert.AreEqual(1.0f, LaunchScreen.revealProgress 1000L) Assert.AreEqual(1.0f, LaunchScreen.veilAlpha 0L) Assert.AreEqual(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) [] 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) [] type P16StartMenuTests () = [] member _.StartMenuIsExactlyThreeItemsWithChineseLabels () = let state = MenuState.create SimulationControl.initial false Assert.AreEqual(3, MenuState.items state |> List.length) Assert.AreEqual([ "开始"; "读档"; "退出" ], MenuState.items state |> List.map MenuState.itemLabel) [] 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(NewGameItem, MenuState.selectedItem state) Assert.AreEqual(LoadItem, MenuState.selectedItem down1) Assert.AreEqual(ExitItem, MenuState.selectedItem down2) Assert.AreEqual(NewGameItem, MenuState.selectedItem wrap) Assert.AreEqual(StartNewGame, MenuState.update Confirm state |> snd) Assert.AreEqual(LoadGame, MenuState.update Confirm down1 |> snd) Assert.AreEqual(ExitGame, MenuState.update Confirm down2 |> snd) [] member _.ReturningToMenuOffersContinueAsTheFirstOfThreeItems () = let playing = MenuState.create SimulationControl.initial true |> MenuState.enterGame let main = MenuState.openMain playing Assert.AreEqual([ ContinueItem; LoadItem; ExitItem ], MenuState.items main) Assert.AreEqual([ "继续"; "读档"; "退出" ], MenuState.items main |> List.map MenuState.itemLabel) Assert.AreEqual(ContinueGame, MenuState.update Confirm main |> snd) // ---- P16 HUD 顶部窄信息条 ---- [] type P16HudTests () = let world = Sim.initialWorldN 42UL 4 [] 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") [] member _.HudSegmentsAreDeterministicAndUseRealState () = let control = SimulationControl.initial let segments = HudLayout.segments world control Assert.AreEqual([ "时段"; "时间"; "倍速"; "体力"; "钱币" ], segments |> List.map (fun segment -> segment.Label)) Assert.AreEqual(segments, HudLayout.segments world control) let energy = segments |> List.find (fun segment -> segment.Label = "体力") Assert.AreEqual(sprintf "%.0f" world.Avatar.Mind.Needs.Energy, energy.Value) let money = segments |> List.find (fun segment -> segment.Label = "钱币") Assert.AreEqual(sprintf "%.0f" world.Avatar.Mind.Needs.Money, money.Value) [] member _.HudLayoutPlacesSegmentsDeterministicallyWithinWidth () = let placed = HudLayout.layout (HudLayout.segments world SimulationControl.initial) Assert.AreEqual(5, placed.Length) let (firstX, _, _) = List.head placed Assert.AreEqual(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") [] member _.HudClockAndPeriodArePureFunctions () = let ticksPerHour = Sim.ticksPerDay / 24L Assert.AreEqual("12:00", M6Presentation.clockTimeLabel (12L * ticksPerHour)) Assert.AreEqual("00:00", M6Presentation.clockTimeLabel 0L) Assert.AreEqual("06:30", M6Presentation.clockTimeLabel (6L * ticksPerHour + ticksPerHour / 2L)) Assert.AreEqual("白天", M6Presentation.timePeriodLabel (12L * ticksPerHour)) Assert.AreEqual("夜晚", M6Presentation.timePeriodLabel 0L) Assert.AreEqual("清晨", M6Presentation.timePeriodLabel (6L * ticksPerHour)) Assert.AreEqual("黄昏", M6Presentation.timePeriodLabel (19L * ticksPerHour)) // P18: 黄昏 covers the golden hour through 20:59; 21:00 onwards is 夜晚. Assert.AreEqual("黄昏", M6Presentation.timePeriodLabel (20L * ticksPerHour)) Assert.AreEqual("黄昏", M6Presentation.timePeriodLabel (int64 20 * ticksPerHour + ticksPerHour * 3L / 4L)) Assert.AreEqual("夜晚", M6Presentation.timePeriodLabel (21L * ticksPerHour)) [] 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 菜单截图帧表 ---- [] type P16MenuShotTests () = [] 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(names.Length, names |> List.distinct |> List.length) [] member _.MenuShotSelectsStartThenLoadOnFreshState () = let state = MenuState.create SimulationControl.initial false Assert.AreEqual(NewGameItem, MenuState.selectedItem state) Assert.AreEqual(LoadItem, MenuState.selectedItem (MenuState.move Down state))