summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop.Tests/P31StoryUiTests.fs
blob: 562794dc60b8ef5c17f507d0a74f085daf6832e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
namespace LivingVillage.Desktop.Tests

open System
open Microsoft.VisualStudio.TestTools.UnitTesting
open LivingVillage.Kernel
open LivingVillage.Kernel.Sim
open LivingVillage.Desktop

module private P31Harness =

    let atNpc (id: int) (world: World) : World =
        let npc = world.Npcs.[id]
        { world with Avatar = { world.Avatar with Pos = npc.Pos } }

    let nearWorld () : World = Sim.initialWorldN 42UL 4 |> atNpc 0

    let npcIndex (NpcId id) : int = id

[<TestClass>]
type P31StoryUiTests () =

    // ---- 任务面板:剧情线名 + 进度段名 ----

    [<TestMethod>]
    member _.TaskPanelShowsTheStorylineNameAndStage () =
        let world = Sim.initialWorldN 42UL 4
        let state = { Occupation.stateOf Occupation.Fisher with StoryStage = 2 }
        let view = { M5Interaction.initial with Task = Some state }
        let _, taskView = M5Interaction.apply ShowTaskPanel world view
        let lines = M5Interaction.panelLines world taskView
        Assert.IsTrue(lines |> List.contains "剧情线:水位与收成,走访中", $"lines={lines}")

    [<TestMethod>]
    member _.TaskPanelShowsConcludedStageAndNoOccupationStaysStoryFree () =
        let world = Sim.initialWorldN 42UL 4
        let concluded = { Occupation.stateOf Occupation.Scholar with StoryStage = 3 }
        let view = { M5Interaction.initial with Task = Some concluded }
        let _, taskView = M5Interaction.apply ShowTaskPanel world view
        Assert.IsTrue(
            M5Interaction.panelLines world taskView
            |> List.contains "剧情线:庙前的争论,已收束")
        let _, noneView = M5Interaction.apply ShowTaskPanel world M5Interaction.initial
        Assert.IsFalse(
            M5Interaction.panelLines world noneView
            |> List.exists (fun line -> line.Contains("剧情线")))

    // ---- 对话接线:回应偏差进记忆 + 剧情线推进 + 年鉴写入 ----

    [<TestMethod>]
    member _.DialogueThroughM5AppliesBiasAndAdvancesTheStory () =
        let world = { P31Harness.nearWorld () with Tick = 5L * Sim.ticksPerDay }
        let state = Occupation.stateOf Occupation.Fisher
        let view = { M5Interaction.initial with Task = Some state; Seed = 42UL }
        let personality = world.Npcs.[0].Mind.Personality
        let openedWorld, openedView = M5Interaction.apply Interact world view
        match openedView.Menu with
        | None -> Assert.Fail("dialogue menu should open next to a villager")
        | Some menu ->
            let finalWorld, finalView = M5Interaction.apply Intent1 openedWorld openedView
            // 剧情线推进一段(tick 第 5 日 ≥ 开线日)
            let task = finalView.Task.Value
            Assert.AreEqual<int>(1, task.StoryStage)
            // 年鉴写入剧情条目
            Assert.IsTrue(
                finalWorld.Annals
                |> List.exists (fun entry ->
                    match entry.Kind with
                    | StoryAnnal -> entry.Summary.Contains("接了件事")
                    | _ -> false))
            // 回应偏差可观测:偏差后的回应与纯函数口径一致
            let biased = Occupation.biasResponse (Some Occupation.Fisher) SmallTalk personality
            let unbiased = Sim.responseFor SmallTalk personality
            if biased <> unbiased then
                Assert.IsTrue(
                    finalWorld.Annals
                    |> List.exists (fun entry ->
                        match entry.Kind with
                        | DialogueAnnal outcome -> outcome.Response = biased
                        | _ -> false),
                    "the biased response must be the one recorded")

    [<TestMethod>]
    member _.NoOccupationDialogueKeepsTheHistoricResponsePath () =
        let world = { P31Harness.nearWorld () with Tick = 5L * Sim.ticksPerDay }
        let view = M5Interaction.initial
        let openedWorld, openedView = M5Interaction.apply Interact world view
        match openedView.Menu with
        | None -> Assert.Fail("dialogue menu should open next to a villager")
        | Some menu ->
            let personality = openedWorld.Npcs.[P31Harness.npcIndex menu.Target].Mind.Personality
            let finalWorld, _ = M5Interaction.apply Intent1 openedWorld openedView
            let expected = Sim.responseFor SmallTalk personality
            Assert.IsTrue(
                finalWorld.Npcs.[0].Mind.Memory
                |> List.exists (fun memory ->
                    match memory.Kind with
                    | Dialogue (_, SmallTalk, response) -> response = expected
                    | _ -> false))
            // 无职业不产生剧情条目,剧情线状态不存在
            Assert.IsFalse(
                finalWorld.Annals
                |> List.exists (fun entry ->
                    match entry.Kind with
                    | StoryAnnal -> true
                    | _ -> false))