|
| 1 | +using Gum.DataTypes; |
| 2 | +using Gum.Forms.Controls.Games; |
| 3 | +using Gum.Wireframe; |
| 4 | +using MonoGameGum.GueDeriving; |
| 5 | +using RenderingLibrary.Graphics; |
| 6 | +using Shouldly; |
| 7 | +using Xunit; |
| 8 | + |
| 9 | +namespace MonoGameGum.Tests.Forms; |
| 10 | + |
| 11 | +public class DialogBoxTests : BaseTestClass |
| 12 | +{ |
| 13 | + sealed class TestDialogBoxVisual : InteractiveGue |
| 14 | + { |
| 15 | + public TextRuntime TextInstance { get; } |
| 16 | + public ContainerRuntime ContinueIndicatorInstance { get; } |
| 17 | + |
| 18 | + public TestDialogBoxVisual(bool paginating = false) : base(new InvisibleRenderable()) |
| 19 | + { |
| 20 | + HasEvents = true; |
| 21 | + Width = 400; |
| 22 | + Height = 100; |
| 23 | + |
| 24 | + TextInstance = new TextRuntime { Name = "TextInstance", Text = string.Empty }; |
| 25 | + if (paginating) |
| 26 | + { |
| 27 | + // Fixed pixel size + TruncateLine triggers DialogBox.ConvertToPages's |
| 28 | + // pagination path. Width 200 / Height ~42 fits roughly 2 lines at the |
| 29 | + // default font's 21px line height. |
| 30 | + TextInstance.Width = 200; |
| 31 | + TextInstance.Height = 42; |
| 32 | + TextInstance.WidthUnits = DimensionUnitType.Absolute; |
| 33 | + TextInstance.HeightUnits = DimensionUnitType.Absolute; |
| 34 | + TextInstance.TextOverflowVerticalMode = TextOverflowVerticalMode.TruncateLine; |
| 35 | + } |
| 36 | + AddChild(TextInstance); |
| 37 | + |
| 38 | + ContinueIndicatorInstance = new ContainerRuntime { Name = "ContinueIndicatorInstance", Visible = false }; |
| 39 | + AddChild(ContinueIndicatorInstance); |
| 40 | + |
| 41 | + FormsControlAsObject = new DialogBox(this); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + static (DialogBox dialogBox, TestDialogBoxVisual visual) CreateDialogBox(bool paginating = false) |
| 46 | + { |
| 47 | + var visual = new TestDialogBoxVisual(paginating); |
| 48 | + return ((DialogBox)visual.FormsControlAsObject, visual); |
| 49 | + } |
| 50 | + |
| 51 | + static void Tick(DialogBox dialogBox, double secondDifference) => |
| 52 | + ((IUpdateEveryFrame)dialogBox).Activity(secondDifference); |
| 53 | + |
| 54 | + [Fact] |
| 55 | + public void Constructor_ShouldWireUpDialogBoxToVisual() |
| 56 | + { |
| 57 | + var (dialogBox, visual) = CreateDialogBox(); |
| 58 | + |
| 59 | + dialogBox.ShouldNotBeNull(); |
| 60 | + dialogBox.Visual.ShouldBe(visual); |
| 61 | + visual.TextInstance.ShouldNotBeNull(); |
| 62 | + visual.ContinueIndicatorInstance.ShouldNotBeNull(); |
| 63 | + visual.ContinueIndicatorInstance.Visible.ShouldBeFalse(); |
| 64 | + } |
| 65 | + |
| 66 | + [Fact] |
| 67 | + public void Show_WithLettersPerSecond_ShouldStartAtZeroLetters() |
| 68 | + { |
| 69 | + var (dialogBox, visual) = CreateDialogBox(); |
| 70 | + dialogBox.LettersPerSecond = 10; |
| 71 | + |
| 72 | + dialogBox.Show("hello"); |
| 73 | + |
| 74 | + visual.TextInstance.MaxLettersToShow.ShouldBe(0); |
| 75 | + visual.ContinueIndicatorInstance.Visible.ShouldBeFalse(); |
| 76 | + } |
| 77 | + |
| 78 | + [Fact] |
| 79 | + public void Show_WithZeroLettersPerSecond_ShouldShowAllLettersImmediately() |
| 80 | + { |
| 81 | + var (dialogBox, visual) = CreateDialogBox(); |
| 82 | + dialogBox.LettersPerSecond = 0; |
| 83 | + |
| 84 | + dialogBox.Show("hello"); |
| 85 | + |
| 86 | + visual.TextInstance.MaxLettersToShow.ShouldBe(5); |
| 87 | + } |
| 88 | + |
| 89 | + [Fact] |
| 90 | + public void Activity_ShouldAdvanceMaxLettersToShow_ProportionalToElapsedTime() |
| 91 | + { |
| 92 | + var (dialogBox, visual) = CreateDialogBox(); |
| 93 | + dialogBox.LettersPerSecond = 10; |
| 94 | + dialogBox.Show("hello world"); |
| 95 | + |
| 96 | + Tick(dialogBox, 0.5); |
| 97 | + |
| 98 | + visual.TextInstance.MaxLettersToShow.ShouldBe(5); |
| 99 | + } |
| 100 | + |
| 101 | + [Fact] |
| 102 | + public void Activity_ShouldFireFinishedTypingPage_WhenAllLettersRevealed() |
| 103 | + { |
| 104 | + var (dialogBox, visual) = CreateDialogBox(); |
| 105 | + dialogBox.LettersPerSecond = 10; |
| 106 | + int finishedFireCount = 0; |
| 107 | + dialogBox.FinishedTypingPage += (_, _) => finishedFireCount++; |
| 108 | + |
| 109 | + dialogBox.Show("hello"); |
| 110 | + Tick(dialogBox, 1.0); |
| 111 | + |
| 112 | + visual.TextInstance.MaxLettersToShow.ShouldBe(5); |
| 113 | + finishedFireCount.ShouldBe(1); |
| 114 | + visual.ContinueIndicatorInstance.Visible.ShouldBeTrue(); |
| 115 | + } |
| 116 | + |
| 117 | + [Fact] |
| 118 | + public void Activity_ShouldNotFireFinishedTypingPageMultipleTimes() |
| 119 | + { |
| 120 | + var (dialogBox, visual) = CreateDialogBox(); |
| 121 | + dialogBox.LettersPerSecond = 10; |
| 122 | + int finishedFireCount = 0; |
| 123 | + dialogBox.FinishedTypingPage += (_, _) => finishedFireCount++; |
| 124 | + |
| 125 | + dialogBox.Show("hello"); |
| 126 | + Tick(dialogBox, 1.0); |
| 127 | + Tick(dialogBox, 1.0); |
| 128 | + Tick(dialogBox, 1.0); |
| 129 | + |
| 130 | + finishedFireCount.ShouldBe(1); |
| 131 | + } |
| 132 | + |
| 133 | + [Fact] |
| 134 | + public void Activity_BeforeShow_ShouldBeNoOp() |
| 135 | + { |
| 136 | + var (dialogBox, visual) = CreateDialogBox(); |
| 137 | + int finishedFireCount = 0; |
| 138 | + dialogBox.FinishedTypingPage += (_, _) => finishedFireCount++; |
| 139 | + |
| 140 | + Tick(dialogBox, 1.0); |
| 141 | + |
| 142 | + finishedFireCount.ShouldBe(0); |
| 143 | + } |
| 144 | + |
| 145 | + [Fact] |
| 146 | + public void Activity_ShouldClampMaxLettersToShow_AtTargetCount() |
| 147 | + { |
| 148 | + var (dialogBox, visual) = CreateDialogBox(); |
| 149 | + dialogBox.LettersPerSecond = 1000; |
| 150 | + dialogBox.Show("hi"); |
| 151 | + |
| 152 | + Tick(dialogBox, 10.0); |
| 153 | + |
| 154 | + visual.TextInstance.MaxLettersToShow.ShouldBe(2); |
| 155 | + } |
| 156 | + |
| 157 | + [Fact] |
| 158 | + public void Activity_OnTwoDialogBoxes_ShouldAdvanceIndependently() |
| 159 | + { |
| 160 | + var (dialogBoxA, visualA) = CreateDialogBox(); |
| 161 | + var (dialogBoxB, visualB) = CreateDialogBox(); |
| 162 | + dialogBoxA.LettersPerSecond = 10; |
| 163 | + dialogBoxB.LettersPerSecond = 10; |
| 164 | + |
| 165 | + dialogBoxA.Show("hello"); |
| 166 | + dialogBoxB.Show("hello"); |
| 167 | + Tick(dialogBoxA, 0.3); |
| 168 | + |
| 169 | + visualA.TextInstance.MaxLettersToShow.ShouldBe(3); |
| 170 | + visualB.TextInstance.MaxLettersToShow.ShouldBe(0); |
| 171 | + } |
| 172 | + |
| 173 | + [Fact] |
| 174 | + public void AnimateSelf_OnVisual_ShouldDispatchToActivity() |
| 175 | + { |
| 176 | + var (dialogBox, visual) = CreateDialogBox(); |
| 177 | + dialogBox.LettersPerSecond = 10; |
| 178 | + dialogBox.Show("hello"); |
| 179 | + |
| 180 | + visual.AnimateSelf(0.5); |
| 181 | + |
| 182 | + visual.TextInstance.MaxLettersToShow.ShouldBe(5); |
| 183 | + } |
| 184 | + |
| 185 | + const string LongText = |
| 186 | + "This is sentence one of a fairly long passage. " + |
| 187 | + "This is sentence two which keeps going on and on. " + |
| 188 | + "Sentence three adds even more text to push past the height. " + |
| 189 | + "Sentence four ensures we definitely overflow the visible area. " + |
| 190 | + "And sentence five is here to be very sure of pagination."; |
| 191 | + |
| 192 | + [Fact] |
| 193 | + public void Show_String_ShouldSplitLongTextAcrossMultiplePages() |
| 194 | + { |
| 195 | + var (dialogBox, _) = CreateDialogBox(paginating: true); |
| 196 | + dialogBox.LettersPerSecond = 0; |
| 197 | + |
| 198 | + dialogBox.Show(LongText); |
| 199 | + |
| 200 | + // First page is shown (popped); remaining pages should still be queued. |
| 201 | + dialogBox.PagesRemaining.ShouldBeGreaterThan(0); |
| 202 | + } |
| 203 | + |
| 204 | + [Fact] |
| 205 | + public void Show_Enumerable_ShouldSplitEachLongEntryAcrossMultiplePages() |
| 206 | + { |
| 207 | + var (dialogBoxA, _) = CreateDialogBox(paginating: true); |
| 208 | + var (dialogBoxB, _) = CreateDialogBox(paginating: true); |
| 209 | + dialogBoxA.LettersPerSecond = 0; |
| 210 | + dialogBoxB.LettersPerSecond = 0; |
| 211 | + |
| 212 | + // Two long entries should produce strictly more pages than one long entry — |
| 213 | + // confirming each entry runs through ConvertToPages, not just appended verbatim. |
| 214 | + dialogBoxA.Show(new[] { LongText }); |
| 215 | + dialogBoxB.Show(new[] { LongText, LongText }); |
| 216 | + |
| 217 | + dialogBoxB.PagesRemaining.ShouldBeGreaterThan(dialogBoxA.PagesRemaining); |
| 218 | + } |
| 219 | + |
| 220 | + [Fact] |
| 221 | + public void Show_String_AndShow_SingleEntryArray_ShouldProduceSamePageCount() |
| 222 | + { |
| 223 | + var (dialogBoxA, _) = CreateDialogBox(paginating: true); |
| 224 | + var (dialogBoxB, _) = CreateDialogBox(paginating: true); |
| 225 | + dialogBoxA.LettersPerSecond = 0; |
| 226 | + dialogBoxB.LettersPerSecond = 0; |
| 227 | + |
| 228 | + dialogBoxA.Show(LongText); |
| 229 | + dialogBoxB.Show(new[] { LongText }); |
| 230 | + |
| 231 | + dialogBoxA.PagesRemaining.ShouldBe(dialogBoxB.PagesRemaining); |
| 232 | + } |
| 233 | + |
| 234 | + [Fact] |
| 235 | + public void Show_Enumerable_TwoIdenticalLongEntries_ShouldProduceTwiceTheSplitPagesOfOne() |
| 236 | + { |
| 237 | + var (dialogBoxA, _) = CreateDialogBox(paginating: true); |
| 238 | + var (dialogBoxB, _) = CreateDialogBox(paginating: true); |
| 239 | + dialogBoxA.LettersPerSecond = 0; |
| 240 | + dialogBoxB.LettersPerSecond = 0; |
| 241 | + |
| 242 | + dialogBoxA.Show(new[] { LongText }); |
| 243 | + dialogBoxB.Show(new[] { LongText, LongText }); |
| 244 | + |
| 245 | + // PagesRemaining = pages queued AFTER ShowNextPage popped one, |
| 246 | + // so the two-entry case should have (2 * (A.PagesRemaining + 1)) - 1 |
| 247 | + // = 2 * A.PagesRemaining + 1 pages remaining. |
| 248 | + dialogBoxB.PagesRemaining.ShouldBe(2 * dialogBoxA.PagesRemaining + 1); |
| 249 | + } |
| 250 | + |
| 251 | + [Fact] |
| 252 | + public void Show_Enumerable_ShouldKeepShortEntriesAsLiteralPages() |
| 253 | + { |
| 254 | + var (dialogBox, _) = CreateDialogBox(paginating: true); |
| 255 | + dialogBox.LettersPerSecond = 0; |
| 256 | + |
| 257 | + dialogBox.Show(new[] { "one", "two", "three" }); |
| 258 | + |
| 259 | + // Three short entries -> three pages, first popped -> two remaining. |
| 260 | + dialogBox.PagesRemaining.ShouldBe(2); |
| 261 | + } |
| 262 | +} |
0 commit comments