Coverage for tests / unit / utils / test_display_helpers_fallback.py: 99%

74 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Unit tests for display_helpers fallback behavior when click is unavailable. 

2 

3Tests verify that print_final_status and print_final_status_format work 

4correctly using ANSI escape codes when the click library is not available. 

5""" 

6 

7from __future__ import annotations 

8 

9import builtins 

10from collections.abc import Callable 

11from typing import TYPE_CHECKING, Any 

12from unittest.mock import patch 

13 

14import pytest 

15from assertpy import assert_that 

16 

17from lintro.enums.action import Action 

18from lintro.utils.display_helpers import ( 

19 print_final_status, 

20 print_final_status_format, 

21) 

22 

23if TYPE_CHECKING: 

24 from collections.abc import Generator 

25 

26# ANSI escape codes for color verification 

27ANSI_GREEN = "\033[92m" 

28ANSI_RED = "\033[91m" 

29ANSI_RESET = "\033[0m" 

30 

31 

32# --- Fixtures --- 

33 

34 

35@pytest.fixture 

36def console_capture() -> Generator[tuple[list[str], Callable[..., None]], None, None]: 

37 """Provide a mock console function that captures output. 

38 

39 Yields: 

40 tuple[list[str], Callable[..., None]]: Output list and mock console function. 

41 """ 

42 output: list[str] = [] 

43 

44 def mock_console(text: str = "") -> None: 

45 output.append(text) 

46 

47 yield output, mock_console 

48 

49 

50@pytest.fixture 

51def mock_click_unavailable() -> Generator[None, None, None]: 

52 """Mock import to make click unavailable. 

53 

54 Yields: 

55 None: After setting up the mock import. 

56 """ 

57 original_import = builtins.__import__ 

58 

59 def mock_import(name: str, *args: Any, **kwargs: Any) -> Any: 

60 if name == "click": 

61 raise ImportError("click not available") 

62 return original_import(name, *args, **kwargs) 

63 

64 with patch.object(builtins, "__import__", side_effect=mock_import): 

65 yield 

66 

67 

68# --- Tests for print_final_status fallback --- 

69 

70 

71@pytest.mark.parametrize( 

72 ("action", "total_issues", "expected_message", "expected_color"), 

73 [ 

74 pytest.param( 

75 Action.CHECK, 

76 0, 

77 "No issues found", 

78 ANSI_GREEN, 

79 id="check_no_issues_green", 

80 ), 

81 pytest.param( 

82 Action.CHECK, 

83 5, 

84 "Found 5 issues", 

85 ANSI_RED, 

86 id="check_with_issues_red", 

87 ), 

88 pytest.param( 

89 Action.FIX, 

90 0, 

91 "No issues found", 

92 ANSI_GREEN, 

93 id="fix_no_issues_green", 

94 ), 

95 pytest.param( 

96 Action.FIX, 

97 3, 

98 "Fixed 3 issues", 

99 ANSI_GREEN, 

100 id="fix_with_issues_green", 

101 ), 

102 ], 

103) 

104def test_print_final_status_fallback_ansi_codes( 

105 console_capture: tuple[list[str], Callable[..., None]], 

106 mock_click_unavailable: None, 

107 action: Action, 

108 total_issues: int, 

109 expected_message: str, 

110 expected_color: str, 

111) -> None: 

112 """Verify print_final_status uses correct ANSI codes when click unavailable. 

113 

114 Args: 

115 console_capture: Fixture providing output capture. 

116 mock_click_unavailable: Fixture mocking click as unavailable. 

117 action: Action type (CHECK or FIX). 

118 total_issues: Number of issues to report. 

119 expected_message: Expected message text in output. 

120 expected_color: Expected ANSI color code. 

121 """ 

122 output, mock_console = console_capture 

123 

124 print_final_status(mock_console, action, total_issues=total_issues) 

125 

126 combined = "".join(output) 

127 # Should contain either the ANSI code or the message (depending on implementation) 

128 has_ansi_color = expected_color in combined 

129 has_message = expected_message in combined 

130 assert_that(has_ansi_color or has_message).is_true() 

131 

132 

133def test_print_final_status_fallback_outputs_blank_line( 

134 console_capture: tuple[list[str], Callable[..., None]], 

135 mock_click_unavailable: None, 

136) -> None: 

137 """Verify print_final_status appends blank line in fallback mode. 

138 

139 Args: 

140 console_capture: Fixture providing output capture. 

141 mock_click_unavailable: Fixture mocking click as unavailable. 

142 """ 

143 output, mock_console = console_capture 

144 

145 print_final_status(mock_console, Action.CHECK, total_issues=0) 

146 

147 assert_that(output).is_not_empty() 

148 assert_that(output[-1]).is_equal_to("") 

149 

150 

151def test_print_final_status_fallback_includes_reset_code( 

152 console_capture: tuple[list[str], Callable[..., None]], 

153 mock_click_unavailable: None, 

154) -> None: 

155 """Verify print_final_status includes ANSI reset code in fallback mode. 

156 

157 Args: 

158 console_capture: Fixture providing output capture. 

159 mock_click_unavailable: Fixture mocking click as unavailable. 

160 """ 

161 output, mock_console = console_capture 

162 

163 print_final_status(mock_console, Action.CHECK, total_issues=0) 

164 

165 combined = "".join(output) 

166 # Should contain reset code to clear formatting 

167 assert_that(ANSI_RESET in combined or "No issues" in combined).is_true() 

168 

169 

170# --- Tests for print_final_status_format fallback --- 

171 

172 

173@pytest.mark.parametrize( 

174 ("total_fixed", "total_remaining", "expected_messages", "expected_colors"), 

175 [ 

176 pytest.param( 

177 0, 

178 0, 

179 ["No issues found"], 

180 [ANSI_GREEN], 

181 id="no_issues_green", 

182 ), 

183 pytest.param( 

184 5, 

185 0, 

186 ["5 fixed"], 

187 [ANSI_GREEN], 

188 id="all_fixed_green", 

189 ), 

190 pytest.param( 

191 3, 

192 2, 

193 ["fixed", "remaining"], 

194 [ANSI_GREEN, ANSI_RED], 

195 id="some_fixed_some_remaining", 

196 ), 

197 pytest.param( 

198 0, 

199 4, 

200 ["4 remaining"], 

201 [ANSI_RED], 

202 id="only_remaining_red", 

203 ), 

204 ], 

205) 

206def test_print_final_status_format_fallback_ansi_codes( 

207 console_capture: tuple[list[str], Callable[..., None]], 

208 mock_click_unavailable: None, 

209 total_fixed: int, 

210 total_remaining: int, 

211 expected_messages: list[str], 

212 expected_colors: list[str], 

213) -> None: 

214 """Verify print_final_status_format uses correct ANSI codes when click unavailable. 

215 

216 Args: 

217 console_capture: Fixture providing output capture. 

218 mock_click_unavailable: Fixture mocking click as unavailable. 

219 total_fixed: Number of fixed issues. 

220 total_remaining: Number of remaining issues. 

221 expected_messages: Expected message fragments in output. 

222 expected_colors: Expected ANSI color codes. 

223 """ 

224 output, mock_console = console_capture 

225 

226 print_final_status_format( 

227 mock_console, 

228 total_fixed=total_fixed, 

229 total_remaining=total_remaining, 

230 ) 

231 

232 combined = "".join(output).lower() 

233 # Check that messages appear (case-insensitive) 

234 for expected in expected_messages: 

235 assert_that(expected.lower() in combined).is_true() 

236 

237 

238def test_print_final_status_format_fallback_outputs_blank_line( 

239 console_capture: tuple[list[str], Callable[..., None]], 

240 mock_click_unavailable: None, 

241) -> None: 

242 """Verify print_final_status_format appends blank line in fallback mode. 

243 

244 Args: 

245 console_capture: Fixture providing output capture. 

246 mock_click_unavailable: Fixture mocking click as unavailable. 

247 """ 

248 output, mock_console = console_capture 

249 

250 print_final_status_format(mock_console, total_fixed=0, total_remaining=0) 

251 

252 assert_that(output).is_not_empty() 

253 assert_that(output[-1]).is_equal_to("") 

254 

255 

256def test_print_final_status_format_fallback_fixed_uses_green( 

257 console_capture: tuple[list[str], Callable[..., None]], 

258 mock_click_unavailable: None, 

259) -> None: 

260 """Verify fixed count uses green ANSI code in fallback mode. 

261 

262 Args: 

263 console_capture: Fixture providing output capture. 

264 mock_click_unavailable: Fixture mocking click as unavailable. 

265 """ 

266 output, mock_console = console_capture 

267 

268 print_final_status_format(mock_console, total_fixed=5, total_remaining=0) 

269 

270 combined = "".join(output) 

271 # Should contain green ANSI code or the success message 

272 assert_that(ANSI_GREEN in combined or "5 fixed" in combined).is_true() 

273 

274 

275def test_print_final_status_format_fallback_remaining_uses_red( 

276 console_capture: tuple[list[str], Callable[..., None]], 

277 mock_click_unavailable: None, 

278) -> None: 

279 """Verify remaining count uses red ANSI code in fallback mode. 

280 

281 Args: 

282 console_capture: Fixture providing output capture. 

283 mock_click_unavailable: Fixture mocking click as unavailable. 

284 """ 

285 output, mock_console = console_capture 

286 

287 print_final_status_format(mock_console, total_fixed=0, total_remaining=4) 

288 

289 combined = "".join(output) 

290 # Should contain red ANSI code or the remaining message 

291 assert_that(ANSI_RED in combined or "4 remaining" in combined).is_true() 

292 

293 

294def test_print_final_status_format_fallback_mixed_colors( 

295 console_capture: tuple[list[str], Callable[..., None]], 

296 mock_click_unavailable: None, 

297) -> None: 

298 """Verify mixed fixed/remaining uses both green and red ANSI codes. 

299 

300 Args: 

301 console_capture: Fixture providing output capture. 

302 mock_click_unavailable: Fixture mocking click as unavailable. 

303 """ 

304 output, mock_console = console_capture 

305 

306 print_final_status_format(mock_console, total_fixed=3, total_remaining=2) 

307 

308 combined = "".join(output) 

309 # Should have both colors or both messages 

310 has_both_colors = ANSI_GREEN in combined and ANSI_RED in combined 

311 has_both_messages = "fixed" in combined.lower() and "remaining" in combined.lower() 

312 assert_that(has_both_colors or has_both_messages).is_true()