Coverage for tests/test_display.py: 100%

124 statements  

« prev     ^ index     » next       coverage.py v7.11.1, created at 2026-03-27 20:23 -0700

1"""Test cases for the display module.""" 

2 

3import os 

4from textwrap import dedent 

5 

6from countdown import display 

7from countdown.digits import DIGIT_SIZES 

8 

9 

10def fake_size(columns, lines): 

11 """Create a fake terminal size function for testing.""" 

12 

13 def get_terminal_size(fallback=(columns, lines)): 

14 return os.terminal_size(fallback) 

15 

16 return get_terminal_size 

17 

18 

19def test_print_full_screen_tiny_terminal(capsys, monkeypatch): 

20 monkeypatch.setattr( 

21 "countdown.display.get_terminal_size", 

22 fake_size(40, 10), 

23 ) 

24 display.print_full_screen(["hello world"]) 

25 out, err = capsys.readouterr() 

26 assert out[:6] == "\x1b[H\x1b[J" 

27 assert out[6:] == "\n\n\n\n hello world" 

28 

29 

30def test_print_full_screen_larger_terminal(capsys, monkeypatch): 

31 monkeypatch.setattr( 

32 "countdown.display.get_terminal_size", 

33 fake_size(80, 24), 

34 ) 

35 display.print_full_screen(["hello world"]) 

36 out, err = capsys.readouterr() 

37 assert out[:6] == "\x1b[H\x1b[J" 

38 # 24 height - 1 line = 23, 23//2 = 11 newlines 

39 # 80 width - 11 chars = 69, 69//2 = 34 spaces 

40 assert out[6:] == "\n" * 11 + " " * 34 + "hello world" 

41 

42 

43def test_print_full_screen_multiline_text(capsys, monkeypatch): 

44 monkeypatch.setattr( 

45 "countdown.display.get_terminal_size", 

46 fake_size(100, 30), 

47 ) 

48 display.print_full_screen( 

49 dedent( 

50 """\ 

51 ██████ ██████ ██ ████ 

52 ██ ██ ██ ███ ██ ██ 

53 █████ ██████ ██ ████ 

54 ██ ██ ██ ██ ██ ██ 

55 ██████ ██████ ██ ████ 

56 """ 

57 ).splitlines() 

58 ) 

59 out, err = capsys.readouterr() 

60 assert out[:6] == "\x1b[H\x1b[J" 

61 assert out[6:] == ( 

62 "\n\n\n\n\n\n\n\n\n\n\n\n" 

63 " ██████ ██████ ██ ████\n" 

64 " ██ ██ ██ ███ ██ ██\n" 

65 " █████ ██████ ██ ████\n" 

66 " ██ ██ ██ ██ ██ ██\n" 

67 " ██████ ██████ ██ ████" 

68 ) 

69 

70 

71def test_print_full_screen_paused_shows_red_and_message(capsys, monkeypatch): 

72 """Test that paused=True shows colored timer and PAUSED message.""" 

73 monkeypatch.setattr( 

74 "countdown.display.get_terminal_size", 

75 fake_size(80, 24), 

76 ) 

77 lines = ["00:05"] 

78 display.print_full_screen(lines, paused=True) 

79 out, err = capsys.readouterr() 

80 # Should contain intense magenta color code 

81 assert "\x1b[95m" in out, ( 

82 "Should contain intense magenta color code when paused" 

83 ) 

84 # Should contain reset code 

85 assert "\033[0m" in out, "Should contain color reset code" 

86 # Should contain PAUSED message 

87 assert "PAUSED - Press any key to resume" in out 

88 

89 

90def test_print_full_screen_not_paused_no_red_or_message(capsys, monkeypatch): 

91 """Test that paused=False shows normal timer without PAUSED message.""" 

92 monkeypatch.setattr( 

93 "countdown.display.get_terminal_size", 

94 fake_size(80, 24), 

95 ) 

96 lines = ["00:05"] 

97 display.print_full_screen(lines, paused=False) 

98 out, err = capsys.readouterr() 

99 # Should NOT contain PAUSED message 

100 assert "PAUSED" not in out 

101 # Red color may or may not be present depending on other features, but 

102 # the important thing is the PAUSED message is not shown 

103 

104 

105def test_print_full_screen_paused_tiny_terminal_no_message(capsys, monkeypatch): 

106 """Test that PAUSED message is hidden in tiny terminals with no room.""" 

107 # Create a 3-line terminal with 3-line timer (no room for PAUSED text) 

108 monkeypatch.setattr("countdown.display.get_terminal_size", fake_size(20, 3)) 

109 lines = ["line1", "line2", "line3"] 

110 display.print_full_screen(lines, paused=True) 

111 out, err = capsys.readouterr() 

112 # Should still show intense magenta color 

113 assert "\x1b[95m" in out, ( 

114 "Should contain intense magenta color code when paused" 

115 ) 

116 # Should NOT show PAUSED message (no room) 

117 assert "PAUSED" not in out, ( 

118 "PAUSED message should not appear in tiny terminal" 

119 ) 

120 

121 

122def test_digit_sizes_available(): 

123 """Test that expected digit sizes are available.""" 

124 assert 16 in DIGIT_SIZES, "Size 16 digits should be available" 

125 assert 7 in DIGIT_SIZES, "Size 7 digits should be available" 

126 assert 5 in DIGIT_SIZES, "Size 5 digits should be available" 

127 assert 3 in DIGIT_SIZES, "Size 3 digits should be available" 

128 assert 1 in DIGIT_SIZES, "Size 1 digits should be available" 

129 

130 

131def test_all_characters_in_each_size(): 

132 """Test that all digit characters exist in each size.""" 

133 from countdown.digits import CHARS_BY_SIZE 

134 

135 expected_chars = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":"} 

136 for size in DIGIT_SIZES: 

137 chars = CHARS_BY_SIZE[size] 

138 assert set(chars.keys()) == expected_chars, ( 

139 f"Size {size} should have all characters" 

140 ) 

141 

142 

143def test_char_heights_match_size(): 

144 """Test that character heights match the expected size.""" 

145 from countdown.digits import CHARS_BY_SIZE 

146 

147 for size in DIGIT_SIZES: 

148 chars = CHARS_BY_SIZE[size] 

149 for char, text in chars.items(): 

150 height = len(text.splitlines()) 

151 assert height == size, ( 

152 f"Character '{char}' in size {size} should have height {size}, got {height}" 

153 ) 

154 

155 

156def test_get_chars_for_terminal_selects_largest_that_fits(monkeypatch): 

157 """Test that get_chars_for_terminal selects the largest size that fits both dimensions.""" 

158 # Size requirements for displaying 00:00: 

159 # 16(93w), 7(57w), 5(33w), 3(20w), 1(10w) 

160 

161 # 80x24 terminal - size 7 fits (57w <= 80, 7h <= 24) 

162 monkeypatch.setattr( 

163 "countdown.display.get_terminal_size", 

164 fake_size(80, 24), 

165 ) 

166 chars = display.get_chars_for_terminal() 

167 height = len(chars["0"].splitlines()) 

168 assert height == 7, "80x24 terminal should select size 7" 

169 

170 # 100x24 terminal - size 16 fits (93w <= 100, 16h <= 24) 

171 monkeypatch.setattr( 

172 "countdown.display.get_terminal_size", 

173 fake_size(100, 24), 

174 ) 

175 chars = display.get_chars_for_terminal() 

176 height = len(chars["0"].splitlines()) 

177 assert height == 16, "100x24 terminal should select size 16" 

178 

179 # 60x20 terminal - size 7 fits (57w <= 60, 7h <= 20) 

180 monkeypatch.setattr( 

181 "countdown.display.get_terminal_size", 

182 fake_size(60, 20), 

183 ) 

184 chars = display.get_chars_for_terminal() 

185 height = len(chars["0"].splitlines()) 

186 assert height == 7, "60x20 terminal should select size 7" 

187 

188 # 32x10 terminal - size 3 fits (20w <= 32, 3h <= 10) 

189 monkeypatch.setattr( 

190 "countdown.display.get_terminal_size", 

191 fake_size(32, 10), 

192 ) 

193 chars = display.get_chars_for_terminal() 

194 height = len(chars["0"].splitlines()) 

195 assert height == 3, "32x10 terminal should select size 3" 

196 

197 # 15x5 terminal - size 1 fits (10w <= 15, 1h <= 5) 

198 monkeypatch.setattr("countdown.display.get_terminal_size", fake_size(15, 5)) 

199 chars = display.get_chars_for_terminal() 

200 height = len(chars["0"].splitlines()) 

201 assert height == 1, "15x5 terminal should select size 1" 

202 

203 # Very small terminal - falls back to smallest 

204 monkeypatch.setattr("countdown.display.get_terminal_size", fake_size(5, 1)) 

205 chars = display.get_chars_for_terminal() 

206 height = len(chars["0"].splitlines()) 

207 assert height == 1, "5x1 terminal should fall back to size 1" 

208 

209 

210def test_different_sizes_render_correctly(monkeypatch): 

211 """Test that different sizes render correctly.""" 

212 from countdown import timer 

213 

214 # Test size 7 rendering (80x24 selects size 7) 

215 monkeypatch.setattr( 

216 "countdown.display.get_terminal_size", 

217 fake_size(80, 24), 

218 ) 

219 chars = display.get_chars_for_terminal() 

220 lines = timer.get_number_lines(0, chars) # 00:00 

221 assert len(lines) == 7, "80x24 terminal should render 7 lines" 

222 

223 # Test size 3 rendering (32x10 selects size 3) 

224 monkeypatch.setattr( 

225 "countdown.display.get_terminal_size", 

226 fake_size(32, 10), 

227 ) 

228 chars = display.get_chars_for_terminal() 

229 lines = timer.get_number_lines(0, chars) # 00:00 

230 assert len(lines) == 3, "32x10 terminal should render 3 lines" 

231 

232 # Test size 1 rendering (15x5 selects size 1) 

233 monkeypatch.setattr("countdown.display.get_terminal_size", fake_size(15, 5)) 

234 chars = display.get_chars_for_terminal() 

235 lines = timer.get_number_lines(0, chars) # 00:00 

236 assert len(lines) == 1, "15x5 terminal should render 1 line" 

237 

238 

239def test_width_constraints_force_smaller_size(monkeypatch): 

240 """Test that narrow terminal widths force selection of smaller digit sizes.""" 

241 # Size 7 requires 57 width - a 56x20 terminal should select size 5 instead 

242 monkeypatch.setattr( 

243 "countdown.display.get_terminal_size", 

244 fake_size(56, 20), 

245 ) 

246 chars = display.get_chars_for_terminal() 

247 height = len(chars["0"].splitlines()) 

248 assert height == 5, ( 

249 "56x20 terminal too narrow for size 7, should select size 5" 

250 ) 

251 

252 # Size 5 requires 33 width - a 32x10 terminal should select size 3 instead 

253 monkeypatch.setattr( 

254 "countdown.display.get_terminal_size", 

255 fake_size(32, 10), 

256 ) 

257 chars = display.get_chars_for_terminal() 

258 height = len(chars["0"].splitlines()) 

259 assert height == 3, ( 

260 "32x10 terminal too narrow for size 5, should select size 3" 

261 ) 

262 

263 # Size 3 requires 20 width - a 19x5 terminal should select size 1 instead 

264 monkeypatch.setattr("countdown.display.get_terminal_size", fake_size(19, 5)) 

265 chars = display.get_chars_for_terminal() 

266 height = len(chars["0"].splitlines()) 

267 assert height == 1, ( 

268 "19x5 terminal too narrow for size 3, should select size 1" 

269 ) 

270 

271 

272def test_three_digit_minutes_force_smaller_chars(monkeypatch): 

273 """Wide minute values should fall back to smaller glyphs if needed.""" 

274 # 60x20 terminal can show size 7 for two-digit minutes 

275 monkeypatch.setattr( 

276 "countdown.display.get_terminal_size", 

277 fake_size(60, 20), 

278 ) 

279 chars = display.get_chars_for_terminal(0) 

280 assert len(chars["0"].splitlines()) == 7 

281 

282 # But 100 minutes needs 70 columns at size 7, so we should drop to size 5 

283 chars = display.get_chars_for_terminal(6000) # 100 minutes 

284 assert len(chars["0"].splitlines()) == 5