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
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-07 20:45 -0800
1"""Test cases for the display module."""
3import os
4from textwrap import dedent
6from countdown import display
7from countdown.digits import DIGIT_SIZES
10def fake_size(columns, lines):
11 """Create a fake terminal size function for testing."""
13 def get_terminal_size(fallback=(columns, lines)):
14 return os.terminal_size(fallback)
16 return get_terminal_size
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"
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"
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 )
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
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
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"
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"
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
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 )
126def test_char_heights_match_size():
127 """Test that character heights match the expected size."""
128 from countdown.digits import CHARS_BY_SIZE
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 )
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)
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"
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"
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"
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"
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"
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"
180def test_different_sizes_render_correctly(monkeypatch):
181 """Test that different sizes render correctly."""
182 from countdown import timer
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"
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"
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"
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"
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"
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"