Coverage for tests/test_display.py: 100%

118 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-07 20:45 -0800

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("shutil.get_terminal_size", fake_size(40, 10)) 

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

22 out, err = capsys.readouterr() 

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

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

25 

26 

27def test_print_full_screen_larger_terminal(capsys, monkeypatch): 

28 monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24)) 

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

30 out, err = capsys.readouterr() 

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

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

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

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

35 

36 

37def test_print_full_screen_multiline_text(capsys, monkeypatch): 

38 monkeypatch.setattr("shutil.get_terminal_size", fake_size(100, 30)) 

39 display.print_full_screen( 

40 dedent( 

41 """\ 

42 ██████ ██████ ██ ████ 

43 ██ ██ ██ ███ ██ ██ 

44 █████ ██████ ██ ████ 

45 ██ ██ ██ ██ ██ ██ 

46 ██████ ██████ ██ ████ 

47 """ 

48 ).splitlines() 

49 ) 

50 out, err = capsys.readouterr() 

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

52 assert out[6:] == ( 

53 "\n\n\n\n\n\n\n\n\n\n\n\n" 

54 " ██████ ██████ ██ ████\n" 

55 " ██ ██ ██ ███ ██ ██\n" 

56 " █████ ██████ ██ ████\n" 

57 " ██ ██ ██ ██ ██ ██\n" 

58 " ██████ ██████ ██ ████" 

59 ) 

60 

61 

62def test_print_full_screen_paused_shows_red_and_message(capsys, monkeypatch): 

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

64 monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24)) 

65 lines = ["00:05"] 

66 display.print_full_screen(lines, paused=True) 

67 out, err = capsys.readouterr() 

68 # Should contain intense magenta color code 

69 assert ( 

70 "\x1b[95m" in out 

71 ), "Should contain intense magenta color code when paused" 

72 # Should contain reset code 

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

74 # Should contain PAUSED message 

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

76 

77 

78def test_print_full_screen_not_paused_no_red_or_message(capsys, monkeypatch): 

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

80 monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24)) 

81 lines = ["00:05"] 

82 display.print_full_screen(lines, paused=False) 

83 out, err = capsys.readouterr() 

84 # Should NOT contain PAUSED message 

85 assert "PAUSED" not in out 

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

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

88 

89 

90def test_print_full_screen_paused_tiny_terminal_no_message(capsys, monkeypatch): 

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

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

93 monkeypatch.setattr("shutil.get_terminal_size", fake_size(20, 3)) 

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

95 display.print_full_screen(lines, paused=True) 

96 out, err = capsys.readouterr() 

97 # Should still show intense magenta color 

98 assert ( 

99 "\x1b[95m" in out 

100 ), "Should contain intense magenta color code when paused" 

101 # Should NOT show PAUSED message (no room) 

102 assert "PAUSED" not in out, "PAUSED message should not appear in tiny terminal" 

103 

104 

105def test_digit_sizes_available(): 

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

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

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

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

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

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

112 

113 

114def test_all_characters_in_each_size(): 

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

116 from countdown.digits import CHARS_BY_SIZE 

117 

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

119 for size in DIGIT_SIZES: 

120 chars = CHARS_BY_SIZE[size] 

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

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

123 ) 

124 

125 

126def test_char_heights_match_size(): 

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

128 from countdown.digits import CHARS_BY_SIZE 

129 

130 for size in DIGIT_SIZES: 

131 chars = CHARS_BY_SIZE[size] 

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

133 height = len(text.splitlines()) 

134 assert height == size, ( 

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

136 ) 

137 

138 

139def test_get_chars_for_terminal_selects_largest_that_fits(monkeypatch): 

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

141 # Size requirements: 16(93w), 7(57w), 5(33w), 3(20w), 1(10w) 

142 

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

144 monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24)) 

145 chars = display.get_chars_for_terminal() 

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

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

148 

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

150 monkeypatch.setattr("shutil.get_terminal_size", fake_size(100, 24)) 

151 chars = display.get_chars_for_terminal() 

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

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

154 

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

156 monkeypatch.setattr("shutil.get_terminal_size", fake_size(60, 20)) 

157 chars = display.get_chars_for_terminal() 

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

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

160 

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

162 monkeypatch.setattr("shutil.get_terminal_size", fake_size(32, 10)) 

163 chars = display.get_chars_for_terminal() 

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

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

166 

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

168 monkeypatch.setattr("shutil.get_terminal_size", fake_size(15, 5)) 

169 chars = display.get_chars_for_terminal() 

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

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

172 

173 # Very small terminal - falls back to smallest 

174 monkeypatch.setattr("shutil.get_terminal_size", fake_size(5, 1)) 

175 chars = display.get_chars_for_terminal() 

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

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

178 

179 

180def test_different_sizes_render_correctly(monkeypatch): 

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

182 from countdown import timer 

183 

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

185 monkeypatch.setattr("shutil.get_terminal_size", fake_size(80, 24)) 

186 chars = display.get_chars_for_terminal() 

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

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

189 

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

191 monkeypatch.setattr("shutil.get_terminal_size", fake_size(32, 10)) 

192 chars = display.get_chars_for_terminal() 

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

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

195 

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

197 monkeypatch.setattr("shutil.get_terminal_size", fake_size(15, 5)) 

198 chars = display.get_chars_for_terminal() 

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

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

201 

202 

203def test_width_constraints_force_smaller_size(monkeypatch): 

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

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

206 monkeypatch.setattr("shutil.get_terminal_size", fake_size(56, 20)) 

207 chars = display.get_chars_for_terminal() 

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

209 assert height == 5, "56x20 terminal too narrow for size 7, should select size 5" 

210 

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

212 monkeypatch.setattr("shutil.get_terminal_size", fake_size(32, 10)) 

213 chars = display.get_chars_for_terminal() 

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

215 assert height == 3, "32x10 terminal too narrow for size 5, should select size 3" 

216 

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

218 monkeypatch.setattr("shutil.get_terminal_size", fake_size(19, 5)) 

219 chars = display.get_chars_for_terminal() 

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

221 assert height == 1, "19x5 terminal too narrow for size 3, should select size 1"