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