Skip to content

Commit ba0fca9

Browse files
author
Daniel Barclay
committed
ManualTicTacToe: Split out ConsoleIO layer; added tests of ColoredConsoleTextIO.
1 parent 527478a commit ba0fca9

File tree

3 files changed

+163
-12
lines changed

3 files changed

+163
-12
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.us.dsb.explore.algs.ttt.manual
22

3-
import com.us.dsb.explore.algs.ttt.manual.ui.{ColoredConsoleTextIO, GameUI}
3+
import com.us.dsb.explore.algs.ttt.manual.ui.{LiveColoredConsoleTextIO, GameUI}
44

55
object ManualTicTacToe extends App {
66

7-
val gameResult = GameUI.runGame(ColoredConsoleTextIO)
7+
val gameResult = GameUI.runGame(LiveColoredConsoleTextIO)
88
println("Game result: " + gameResult.text)
99

1010
}

src/main/scala/com/us/dsb/explore/algs/ttt/manual/ui/TextIO.scala

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,19 @@ package com.us.dsb.explore.algs.ttt.manual.ui
22

33
import com.us.dsb.explore.algs.ttt.manual.ui.GameUI.GameUIResult
44

5-
// ?? revisit names
5+
// Doing ConsoleIO as separate layer to have more simple layers for exploring
6+
// testing, ZIO, etc.
7+
private[ui] trait ConsoleIO {
8+
def println(lineOrLines: String): Unit
9+
def readLine(prompt: String): String
10+
}
11+
object LiveConsoleIO extends ConsoleIO {
12+
override def println(lineOrLines: String): Unit = Predef.println(lineOrLines)
13+
override def readLine(prompt: String): String = scala.io.StdIn.readLine(prompt)
14+
}
15+
// (Expect to have test-double version in tests.)
16+
17+
// ?? revisit names:
618

719
private[ui] trait SegregatedTextIO {
820
private[ui] def printStateText(lineOrLines: String): Unit
@@ -13,18 +25,18 @@ private[ui] trait SegregatedTextIO {
1325
private[ui] def printResult(result: GameUIResult): Unit = printResult(result.text)
1426
}
1527

16-
private[ui] class BaseConsoleTextIO extends SegregatedTextIO {
17-
import scala.io.StdIn.readLine
18-
19-
private[ui] override def printStateText(lineOrLines: String): Unit = println(lineOrLines)
20-
private[ui] override def readPromptedLine(prompt: String): String = readLine(prompt)
21-
private[ui] override def printError(fullLine: String): Unit = println(fullLine)
22-
private[ui] override def printResult(lineOrLines: String): Unit = println(lineOrLines)
28+
private[ui] class BaseConsoleTextIO(cio: ConsoleIO) extends SegregatedTextIO {
29+
private[ui] override def printStateText(lineOrLines: String): Unit = cio.println(lineOrLines)
30+
private[ui] override def readPromptedLine(prompt: String): String = cio.readLine(prompt)
31+
private[ui] override def printError(fullLine: String): Unit = cio.println(fullLine)
32+
private[ui] override def printResult(lineOrLines: String): Unit = cio.println(lineOrLines)
2333
}
2434

25-
object PlainConsoleTextIO extends BaseConsoleTextIO
35+
class PlainConsoleTextIO(x: ConsoleIO) extends BaseConsoleTextIO(x)
36+
object LivePlainConsoleTextIO extends PlainConsoleTextIO(LiveConsoleIO)
37+
// (Expect to have test version in tests.)
2638

27-
object ColoredConsoleTextIO extends BaseConsoleTextIO {
39+
class ColoredConsoleTextIO(x: ConsoleIO) extends BaseConsoleTextIO(x) {
2840
import scala.io.AnsiColor._
2941
private[ui] override def readPromptedLine(prompt: String): String =
3042
super.readPromptedLine(BLUE + prompt + RESET)
@@ -33,3 +45,5 @@ object ColoredConsoleTextIO extends BaseConsoleTextIO {
3345
private[ui] override def printResult(lineOrLines: String): Unit =
3446
super.printResult(BOLD + lineOrLines + RESET)
3547
}
48+
object LiveColoredConsoleTextIO extends ColoredConsoleTextIO(LiveConsoleIO)
49+
// (Expect to have test-double version in tests.)
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package com.us.dsb.explore.algs.ttt.manual.ui
2+
3+
import org.scalatest.LoneElement
4+
import org.scalatest.funspec.AnyFunSpec
5+
import org.scalatest.matchers.should.Matchers._
6+
7+
class TextIOTest extends AnyFunSpec {
8+
9+
it/*describe*/("TextIO?:") {
10+
cancel()
11+
it("println ...") {
12+
}
13+
it("readLine ...") {
14+
}
15+
}
16+
17+
// Crude, manual stub and spy ConsoleIO.
18+
class TextConsoleIO(inputLines: String*) extends ConsoleIO {
19+
private var stringsToRead = inputLines
20+
private var printedStrings: List[String] = Nil;
21+
def getPrintedStrings: List[String] = printedStrings
22+
23+
override def println(lineOrLines: String): Unit = {
24+
printedStrings ::= lineOrLines
25+
}
26+
27+
override def readLine(prompt: String): String = {
28+
printedStrings ::= prompt
29+
30+
val lineRead = stringsToRead.head // .get - unsafe - but crude anyway
31+
stringsToRead = stringsToRead.drop(1)
32+
lineRead
33+
}
34+
}
35+
36+
describe("ColoredConsoleTextIO") {
37+
import org.scalatest.LoneElement._
38+
39+
def getUUT(consoleIODouble: ConsoleIO): SegregatedTextIO = {
40+
// Demo: Try injecting "bad" UUT and see how failing conditions show up:
41+
new ColoredConsoleTextIO(consoleIODouble)
42+
//new PlainConsoleTextIO(consoleIODouble)
43+
}
44+
45+
// Demo: Checking subconditions in multiple leaf-level tests:
46+
describe("printStateText should print given text plainly; output should:") {
47+
// Note: "lazy" avoids executing during ScalaTest's registration phase.
48+
lazy val printedStrings = {
49+
val consoleIODouble = new TextConsoleIO
50+
val uut = getUUT(consoleIODouble)
51+
52+
uut.printStateText("text")
53+
54+
consoleIODouble.getPrintedStrings
55+
}
56+
it("include given text") {
57+
printedStrings.loneElement should include ("text")
58+
}
59+
it("not include color/decoration escape sequences") {
60+
printedStrings.loneElement should not include (scala.io.AnsiColor.RED.take(1))
61+
}
62+
it("include only given text") {
63+
printedStrings.loneElement should be ("text")
64+
}
65+
}
66+
67+
describe("printError should print given text in red; output should:") {
68+
lazy val printedStrings = {
69+
val consoleIODouble = new TextConsoleIO
70+
val uut = getUUT(consoleIODouble)
71+
72+
uut.printError("given text")
73+
74+
consoleIODouble.getPrintedStrings
75+
}
76+
it("include given text") {
77+
printedStrings.loneElement should include ("given text")
78+
}
79+
it("include red escape sequence") {
80+
printedStrings.loneElement should include (scala.io.AnsiColor.RED) // ?? in order too
81+
}
82+
it("include reset escape sequence") {
83+
printedStrings.loneElement should include (scala.io.AnsiColor.RESET) // ?? in order too
84+
}
85+
it("not include only given text") {
86+
printedStrings.loneElement should not be ("given text")
87+
}
88+
}
89+
90+
// Demo: checking subconditions with "should ... and ..." (in one test):
91+
it("printResult should print given text in bold (should ... and ...)") {
92+
val consoleIODouble = new TextConsoleIO
93+
val uut = getUUT(consoleIODouble)
94+
95+
uut.printResult("given text")
96+
val printedStrings = consoleIODouble.getPrintedStrings
97+
98+
// Note: "should ... and ..." reports only _first_condition that failed
99+
// (though error message does seem to include actual value).
100+
printedStrings.loneElement should (
101+
include ("given text") and
102+
include (scala.io.AnsiColor.BOLD) and
103+
include (scala.io.AnsiColor.RESET) and
104+
not be ("given text"))
105+
}
106+
107+
describe("readPromptedLine should print given prompt value and get input") {
108+
lazy val (printedStrings, lineRead) = {
109+
val consoleIODouble = new TextConsoleIO("given input")
110+
val uut = getUUT(consoleIODouble)
111+
112+
val lineRead = uut.readPromptedLine("given text")
113+
114+
(consoleIODouble.getPrintedStrings, lineRead)
115+
}
116+
describe("should print given prompted text in blue; output should:") {
117+
it("include given text") {
118+
printedStrings.loneElement should include("given text")
119+
}
120+
it("include blue escape sequence") {
121+
printedStrings.loneElement should include(scala.io.AnsiColor.BLUE) // ?? in order too
122+
}
123+
it("include reset escape sequence") {
124+
printedStrings.loneElement should include(scala.io.AnsiColor.RESET) // ?? in order too
125+
}
126+
it("not include only given text") {
127+
printedStrings.loneElement should not be ("given text")
128+
}
129+
}
130+
it("should read input line text") {
131+
lineRead should be ("given input")
132+
}
133+
}
134+
135+
}
136+
137+
}

0 commit comments

Comments
 (0)