1 // -*- Mode: C++; tab-width:2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 // vi:tw=80:et:ts=2:sts=2
3 //
4 // -----------------------------------------------------------------------
5 //
6 // This file is part of RLVM, a RealLive virtual machine clone.
7 //
8 // -----------------------------------------------------------------------
9 //
10 // Copyright (C) 2007 Elliot Glaysher
11 //
12 // This program is free software; you can redistribute it and/or modify
13 // it under the terms of the GNU General Public License as published by
14 // the Free Software Foundation; either version 3 of the License, or
15 // (at your option) any later version.
16 //
17 // This program is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 // GNU General Public License for more details.
21 //
22 // You should have received a copy of the GNU General Public License
23 // along with this program; if not, write to the Free Software
24 // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
25 //
26 // -----------------------------------------------------------------------
27
28 #include "gtest/gtest.h"
29 #include "gmock/gmock.h"
30
31 #include "libreallive/archive.h"
32 #include "long_operations/textout_long_operation.h"
33 #include "machine/rlmachine.h"
34 #include "systems/base/text_page.h"
35 #include "test_system/mock_surface.h"
36 #include "test_system/mock_text_window.h"
37 #include "test_system/test_system.h"
38
39 #include "test_utils.h"
40
41 #include <string>
42 #include <memory>
43
44 using namespace std;
45
46 using ::testing::_;
47
48 // Increments everytime you ask for ticks to simulate time going by in the real
49 // world. Neccessary because textout measures ticks so it knows how fast to
50 // print out characters.
51 class IncrementingTickCounter : public EventSystemMockHandler {
52 public:
IncrementingTickCounter()53 IncrementingTickCounter() : ticks(0) {}
GetTicks() const54 virtual unsigned int GetTicks() const { return ticks++; }
55
56 private:
57 mutable unsigned int ticks;
58 };
59
60 class TextSystemTest : public FullSystemTest {
61 protected:
TextSystemTest()62 TextSystemTest() {
63 event_mock.reset(new IncrementingTickCounter);
64 dynamic_cast<TestEventSystem&>(system.event()).SetMockHandler(event_mock);
65
66 system.text().set_active_window(0);
67 }
68
GetTextSystem()69 TestTextSystem& GetTextSystem() {
70 return dynamic_cast<TestTextSystem&>(system.text());
71 }
72
GetGraphicsSystem()73 TestGraphicsSystem& GetGraphicsSystem() {
74 return dynamic_cast<TestGraphicsSystem&>(system.graphics());
75 }
76
GetTextWindow(int twn)77 MockTextWindow& GetTextWindow(int twn) {
78 return dynamic_cast<MockTextWindow&>(*system.text().GetTextWindow(twn));
79 }
80
GetCurrentPage()81 TextPage& GetCurrentPage() { return system.text().GetCurrentPage(); }
82
WriteString(const std::string & text,bool nowait)83 void WriteString(const std::string& text, bool nowait) {
84 unique_ptr<TextoutLongOperation> tolo(
85 new TextoutLongOperation(rlmachine, text));
86
87 if (nowait)
88 tolo->set_no_wait();
89
90 while (!(*tolo)(rlmachine))
91 ;
92 }
93
SnapshotAndClear()94 void SnapshotAndClear() {
95 TextSystem& text = rlmachine.system().text();
96 text.Snapshot();
97 text.GetTextWindow(0)->ClearWin();
98 text.NewPageOnWindow(0);
99 }
100
101 std::shared_ptr<EventSystemMockHandler> event_mock;
102 };
103
TEST_F(TextSystemTest,NormalTextDisplay)104 TEST_F(TextSystemTest, NormalTextDisplay) {
105 WriteString("A text string.", false);
106 EXPECT_EQ("A text string.", GetTextWindow(0).current_contents());
107 }
108
TEST_F(TextSystemTest,NormalTextWithNoWait)109 TEST_F(TextSystemTest, NormalTextWithNoWait) {
110 WriteString("A text string.", true);
111 EXPECT_EQ("A text string.", GetTextWindow(0).current_contents());
112 }
113
TEST_F(TextSystemTest,PrintEmptyString)114 TEST_F(TextSystemTest, PrintEmptyString) {
115 WriteString("", false);
116 EXPECT_EQ("", GetTextWindow(0).current_contents());
117 }
118
TEST_F(TextSystemTest,BackLogFunctionality)119 TEST_F(TextSystemTest, BackLogFunctionality) {
120 TextSystem& text = rlmachine.system().text();
121
122 WriteString("Page one.", true);
123 SnapshotAndClear();
124 WriteString("Page two.", true);
125 SnapshotAndClear();
126 WriteString("Page three.", true);
127 SnapshotAndClear();
128 WriteString("Page four.", true);
129
130 EXPECT_EQ("Page four.", GetTextWindow(0).current_contents())
131 << "We're on the final page!";
132 EXPECT_FALSE(text.IsReadingBacklog()) << "We're not reading the backlog.";
133
134 // Reply our way back to the front
135 text.BackPage();
136 EXPECT_EQ("Page three.", GetTextWindow(0).current_contents())
137 << "We're on the 3rd page!";
138 EXPECT_TRUE(text.IsReadingBacklog()) << "We're reading the backlog.";
139
140 text.BackPage();
141 EXPECT_EQ("Page two.", GetTextWindow(0).current_contents())
142 << "We're on the 2nd page!";
143 EXPECT_TRUE(text.IsReadingBacklog()) << "We're reading the backlog.";
144
145 text.BackPage();
146 EXPECT_EQ("Page one.", GetTextWindow(0).current_contents())
147 << "We're on the 1st page!";
148 EXPECT_TRUE(text.IsReadingBacklog()) << "We're reading the backlog.";
149
150 // Trying to go back past the first page doesn't do anything.
151 text.BackPage();
152 EXPECT_EQ("Page one.", GetTextWindow(0).current_contents())
153 << "We're still on the 1st page!";
154 EXPECT_TRUE(text.IsReadingBacklog()) << "We're reading the backlog.";
155
156 text.ForwardPage();
157 EXPECT_EQ("Page two.", GetTextWindow(0).current_contents())
158 << "We're back to the 2nd page!";
159 EXPECT_TRUE(text.IsReadingBacklog()) << "We're reading the backlog.";
160
161 text.StopReadingBacklog();
162 EXPECT_EQ("Page four.", GetTextWindow(0).current_contents())
163 << "We're back to the current page!";
164 EXPECT_FALSE(text.IsReadingBacklog())
165 << "We're no longer reading the backlog.";
166 }
167
168 // -----------------------------------------------------------------------
169
170 // Tests that the TextPage::name construct repeats correctly.
TEST_F(TextSystemTest,RepeatsTextPageName)171 TEST_F(TextSystemTest, RepeatsTextPageName) {
172 TestTextSystem& sys = GetTextSystem();
173 MockTextWindow& win = GetTextWindow(0);
174 EXPECT_CALL(win, SetName("Bob", "")).Times(1);
175 GetCurrentPage().Name("Bob", "");
176 SnapshotAndClear();
177 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
178
179 // Replay it:
180 EXPECT_CALL(win, SetName("Bob", _)).Times(1);
181 GetTextSystem().BackPage();
182 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
183 }
184
185 // -----------------------------------------------------------------------
186
187 // Tests that the TextPgae::hardBreak construct repeats correctly.
TEST_F(TextSystemTest,TextPageHardBreakRepeats)188 TEST_F(TextSystemTest, TextPageHardBreakRepeats) {
189 TestTextSystem& sys = GetTextSystem();
190 MockTextWindow& win = GetTextWindow(0);
191 EXPECT_CALL(win, HardBrake()).Times(1);
192 GetCurrentPage().HardBrake();
193 SnapshotAndClear();
194 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
195
196 // Replay it:
197 EXPECT_CALL(win, HardBrake()).Times(1);
198 GetTextSystem().BackPage();
199 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
200 }
201
202 // -----------------------------------------------------------------------
203
204 // Tests that the TextPage::ResetIndentation construct repeats correctly.
TEST_F(TextSystemTest,TextPageResetIndentationRepeats)205 TEST_F(TextSystemTest, TextPageResetIndentationRepeats) {
206 TestTextSystem& sys = GetTextSystem();
207 MockTextWindow& win = GetTextWindow(0);
208 WriteString("test", true);
209
210 EXPECT_CALL(win, ResetIndentation()).Times(1);
211 GetCurrentPage().ResetIndentation();
212 SnapshotAndClear();
213 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
214
215 // Replay it:
216 EXPECT_CALL(win, ResetIndentation()).Times(1);
217 GetTextSystem().BackPage();
218 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
219 }
220
221 // -----------------------------------------------------------------------
222
223 // Tests that the TextPage::fontColor construct repeats correctly.
TEST_F(TextSystemTest,TextPageFontColorRepeats)224 TEST_F(TextSystemTest, TextPageFontColorRepeats) {
225 TestTextSystem& sys = GetTextSystem();
226 MockTextWindow& win = GetTextWindow(0);
227
228 EXPECT_CALL(win, SetFontColor(_)).Times(1);
229 GetCurrentPage().FontColour(0);
230 SnapshotAndClear();
231 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
232
233 // The scrollback shouldn't be colored.
234 EXPECT_CALL(win, SetFontColor(_)).Times(0);
235 GetTextSystem().BackPage();
236 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
237 }
238
239 // -----------------------------------------------------------------------
240
241 // Tests that the ruby constructs repeat correctly.
TEST_F(TextSystemTest,RubyRepeats)242 TEST_F(TextSystemTest, RubyRepeats) {
243 TestTextSystem& sys = GetTextSystem();
244 MockTextWindow& win = GetTextWindow(0);
245
246 EXPECT_CALL(win, MarkRubyBegin()).Times(1);
247 EXPECT_CALL(win, DisplayRubyText("ruby")).Times(1);
248 GetCurrentPage().MarkRubyBegin();
249 WriteString("With Ruby", true);
250 GetCurrentPage().DisplayRubyText("ruby");
251 SnapshotAndClear();
252 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
253
254 // Replay it:
255 EXPECT_CALL(win, MarkRubyBegin()).Times(1);
256 EXPECT_CALL(win, DisplayRubyText("ruby")).Times(1);
257 GetTextSystem().BackPage();
258 ASSERT_TRUE(::testing::Mock::VerifyAndClearExpectations(&win));
259 }
260
261 // -----------------------------------------------------------------------
262
TEST_F(TextSystemTest,RenderGlyphOntoOneLine)263 TEST_F(TextSystemTest, RenderGlyphOntoOneLine) {
264 TestTextSystem& sys = GetTextSystem();
265 std::shared_ptr<Surface> text_surface =
266 sys.RenderText("One", 20, 0, 0, RGBColour::White(), NULL, 3);
267 // Ensure that when the number of characters equals the max number of
268 // characters, we only use one line.
269 EXPECT_EQ(20, text_surface->GetSize().height());
270 }
271
TEST_F(TextSystemTest,RenderGlyphNoRestriction)272 TEST_F(TextSystemTest, RenderGlyphNoRestriction) {
273 TestTextSystem& sys = GetTextSystem();
274 std::shared_ptr<Surface> text_surface =
275 sys.RenderText("A Very Long String That Goes On And On",
276 20,
277 0,
278 0,
279 RGBColour::White(),
280 NULL,
281 0);
282 // Ensure that when the number of characters equals the max number of
283 // characters, we only use one line.
284 EXPECT_EQ(20, text_surface->GetSize().height());
285 }
286
TEST_F(TextSystemTest,RenderGlyphOntoTwoLines)287 TEST_F(TextSystemTest, RenderGlyphOntoTwoLines) {
288 TestTextSystem& sys = GetTextSystem();
289 std::shared_ptr<Surface> text_surface =
290 sys.RenderText("OneTwo", 20, 0, 0, RGBColour::White(), NULL, 3);
291 EXPECT_EQ(40, text_surface->GetSize().height());
292
293 // Tests the location of rendered glyphs.
294 struct {
295 const char* str;
296 int xpos;
297 int ypos;
298 } test_data[] = {{"O", 0, 0},
299 {"n", 20, 0},
300 {"e", 40, 0},
301 {"T", 0, 20},
302 {"w", 20, 20},
303 {"o", 40, 20}};
304
305 std::vector<std::tuple<std::string, int, int>> data = sys.glyphs();
306 ASSERT_EQ(6, data.size());
307 for (int i = 0; i < data.size(); ++i) {
308 EXPECT_EQ(test_data[i].str, get<0>(data[i]));
309 EXPECT_EQ(test_data[i].xpos, get<1>(data[i]));
310 EXPECT_EQ(test_data[i].ypos, get<2>(data[i]));
311 }
312 }
313
TEST_F(TextSystemTest,DontCrashWithNoEmojiFile)314 TEST_F(TextSystemTest, DontCrashWithNoEmojiFile) {
315 TestTextSystem& sys = GetTextSystem();
316 std::shared_ptr<Surface> text_surface =
317 sys.RenderText("One#A00Two", 20, 0, 0, RGBColour::White(), NULL, -1);
318 EXPECT_EQ(20, text_surface->GetSize().height());
319 }
320
TEST_F(TextSystemTest,TestEmoji)321 TEST_F(TextSystemTest, TestEmoji) {
322 // Set up emoji printing:
323 system.gameexe().SetStringAt("E_MOJI.004", "emoji_file");
324
325 // Inject a fake surface of (24*5, 24). We will expect this to blit to the
326 // text surface.
327 std::shared_ptr<MockSurface> mock(
328 MockSurface::Create("emoji_file", Size(24 * 5, 24)));
329 GetGraphicsSystem().InjectSurface("emoji_file", mock);
330
331 // Our mock surface should render its icon between the Es.
332 EXPECT_CALL(*mock,
333 BlitToSurface(_,
334 Rect(24 * 2, 0, Size(24, 24)),
335 Rect(20, 0, Size(24, 24)),
336 255,
337 false));
338
339 TestTextSystem& sys = GetTextSystem();
340 std::shared_ptr<Surface> text_surface =
341 sys.RenderText("E#A02E", 20, 0, 0, RGBColour::White(), NULL, -1);
342 EXPECT_EQ(20, text_surface->GetSize().height());
343
344 // Tests the location of rendered glyphs.
345 struct {
346 const char* str;
347 int xpos;
348 int ypos;
349 } test_data[] = {{"E", 0, 0}, {"E", 40, 0}, };
350
351 std::vector<std::tuple<std::string, int, int>> data = sys.glyphs();
352 ASSERT_EQ(2, data.size());
353 for (int i = 0; i < data.size(); ++i) {
354 EXPECT_EQ(test_data[i].str, get<0>(data[i]));
355 EXPECT_EQ(test_data[i].xpos, get<1>(data[i]));
356 EXPECT_EQ(test_data[i].ypos, get<2>(data[i]));
357 }
358 }
359
360 // If we return an empty surface, we crash. Make sure passing an empty string
361 // doesn't return an empty surface.
TEST_F(TextSystemTest,TestEmptyString)362 TEST_F(TextSystemTest, TestEmptyString) {
363 TestTextSystem& sys = GetTextSystem();
364 std::shared_ptr<Surface> text_surface =
365 sys.RenderText("", 20, 0, 0, RGBColour::White(), NULL, -1);
366 EXPECT_GT(text_surface->GetSize().width(), 0);
367 EXPECT_GT(text_surface->GetSize().height(), 0);
368 }
369