Coverage for tests / unit / tools / tsc / test_options.py: 99%

90 statements  

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

1"""Unit tests for tsc plugin options and command building.""" 

2 

3from __future__ import annotations 

4 

5from unittest.mock import patch 

6 

7import pytest 

8from assertpy import assert_that 

9 

10from lintro.enums.tool_type import ToolType 

11from lintro.tools.definitions.tsc import ( 

12 TSC_DEFAULT_PRIORITY, 

13 TSC_DEFAULT_TIMEOUT, 

14 TSC_FILE_PATTERNS, 

15 TscPlugin, 

16) 

17 

18# ============================================================================= 

19# Tests for TscPlugin definition 

20# ============================================================================= 

21 

22 

23def test_definition_name(tsc_plugin: TscPlugin) -> None: 

24 """Plugin name is 'tsc'. 

25 

26 Args: 

27 tsc_plugin: The TscPlugin instance to test. 

28 """ 

29 assert_that(tsc_plugin.definition.name).is_equal_to("tsc") 

30 

31 

32def test_definition_description(tsc_plugin: TscPlugin) -> None: 

33 """Plugin has a description containing TypeScript. 

34 

35 Args: 

36 tsc_plugin: The TscPlugin instance to test. 

37 """ 

38 assert_that(tsc_plugin.definition.description).contains("TypeScript") 

39 

40 

41def test_definition_can_fix(tsc_plugin: TscPlugin) -> None: 

42 """Plugin cannot fix issues. 

43 

44 Args: 

45 tsc_plugin: The TscPlugin instance to test. 

46 """ 

47 assert_that(tsc_plugin.definition.can_fix).is_false() 

48 

49 

50def test_definition_tool_type(tsc_plugin: TscPlugin) -> None: 

51 """Plugin is a linter and type checker. 

52 

53 Args: 

54 tsc_plugin: The TscPlugin instance to test. 

55 """ 

56 tool_type = tsc_plugin.definition.tool_type 

57 assert_that(ToolType.LINTER in tool_type).is_true() 

58 assert_that(ToolType.TYPE_CHECKER in tool_type).is_true() 

59 

60 

61def test_definition_file_patterns(tsc_plugin: TscPlugin) -> None: 

62 """Plugin handles TypeScript files. 

63 

64 Args: 

65 tsc_plugin: The TscPlugin instance to test. 

66 """ 

67 patterns = tsc_plugin.definition.file_patterns 

68 assert_that(patterns).is_equal_to(TSC_FILE_PATTERNS) 

69 assert_that(patterns).contains("*.ts", "*.tsx", "*.mts", "*.cts") 

70 

71 

72def test_definition_native_configs(tsc_plugin: TscPlugin) -> None: 

73 """Plugin recognizes tsconfig.json. 

74 

75 Args: 

76 tsc_plugin: The TscPlugin instance to test. 

77 """ 

78 assert_that(tsc_plugin.definition.native_configs).contains("tsconfig.json") 

79 

80 

81@pytest.mark.parametrize( 

82 ("option_name", "expected_value"), 

83 [ 

84 ("timeout", TSC_DEFAULT_TIMEOUT), 

85 ("project", None), 

86 ("strict", None), 

87 ("skip_lib_check", True), 

88 ("use_project_files", False), 

89 ], 

90 ids=[ 

91 "timeout_equals_default", 

92 "project_is_none", 

93 "strict_is_none", 

94 "skip_lib_check_is_true", 

95 "use_project_files_is_false", 

96 ], 

97) 

98def test_default_options_values( 

99 tsc_plugin: TscPlugin, 

100 option_name: str, 

101 expected_value: object, 

102) -> None: 

103 """Default options have correct values. 

104 

105 Args: 

106 tsc_plugin: The TscPlugin instance to test. 

107 option_name: The name of the option to check. 

108 expected_value: The expected value for the option. 

109 """ 

110 assert_that(tsc_plugin.definition.default_options).contains_key(option_name) 

111 assert_that(tsc_plugin.definition.default_options[option_name]).is_equal_to( 

112 expected_value, 

113 ) 

114 

115 

116def test_definition_priority(tsc_plugin: TscPlugin) -> None: 

117 """Plugin has correct priority. 

118 

119 Args: 

120 tsc_plugin: The TscPlugin instance to test. 

121 """ 

122 assert_that(tsc_plugin.definition.priority).is_equal_to(TSC_DEFAULT_PRIORITY) 

123 

124 

125# ============================================================================= 

126# Tests for TscPlugin.set_options method 

127# ============================================================================= 

128 

129 

130@pytest.mark.parametrize( 

131 ("option_name", "option_value"), 

132 [ 

133 ("project", "tsconfig.json"), 

134 ("project", "tsconfig.build.json"), 

135 ("strict", True), 

136 ("strict", False), 

137 ("skip_lib_check", True), 

138 ("skip_lib_check", False), 

139 ("use_project_files", True), 

140 ("use_project_files", False), 

141 ], 

142 ids=[ 

143 "project_default", 

144 "project_custom", 

145 "strict_true", 

146 "strict_false", 

147 "skip_lib_check_true", 

148 "skip_lib_check_false", 

149 "use_project_files_true", 

150 "use_project_files_false", 

151 ], 

152) 

153def test_set_options_valid( 

154 tsc_plugin: TscPlugin, 

155 option_name: str, 

156 option_value: object, 

157) -> None: 

158 """Set valid options correctly. 

159 

160 Args: 

161 tsc_plugin: The TscPlugin instance to test. 

162 option_name: The name of the option to set. 

163 option_value: The value to set for the option. 

164 """ 

165 tsc_plugin.set_options( 

166 **{option_name: option_value}, # type: ignore[arg-type] # Dynamic kwargs 

167 ) 

168 assert_that(tsc_plugin.options.get(option_name)).is_equal_to(option_value) 

169 

170 

171@pytest.mark.parametrize( 

172 ("option_name", "invalid_value", "error_match"), 

173 [ 

174 ("project", 123, "project must be a string path"), 

175 ("strict", "yes", "strict must be a boolean"), 

176 ("skip_lib_check", "yes", "skip_lib_check must be a boolean"), 

177 ("use_project_files", "yes", "use_project_files must be a boolean"), 

178 ], 

179 ids=[ 

180 "invalid_project_type", 

181 "invalid_strict_type", 

182 "invalid_skip_lib_check_type", 

183 "invalid_use_project_files_type", 

184 ], 

185) 

186def test_set_options_invalid_type( 

187 tsc_plugin: TscPlugin, 

188 option_name: str, 

189 invalid_value: object, 

190 error_match: str, 

191) -> None: 

192 """Raise ValueError for invalid option types. 

193 

194 Args: 

195 tsc_plugin: The TscPlugin instance to test. 

196 option_name: The name of the option being tested. 

197 invalid_value: An invalid value for the option. 

198 error_match: Pattern expected in the error message. 

199 """ 

200 with pytest.raises(ValueError, match=error_match): 

201 tsc_plugin.set_options( 

202 **{option_name: invalid_value}, # type: ignore[arg-type] # Intentional wrong type 

203 ) 

204 

205 

206# ============================================================================= 

207# Tests for TscPlugin._get_tsc_command method 

208# ============================================================================= 

209 

210 

211def test_get_tsc_command_with_tsc_available(tsc_plugin: TscPlugin) -> None: 

212 """Return direct tsc command when available. 

213 

214 Args: 

215 tsc_plugin: The TscPlugin instance to test. 

216 """ 

217 with patch("shutil.which", return_value="/usr/bin/tsc"): 

218 cmd = tsc_plugin._get_tsc_command() 

219 

220 assert_that(cmd).is_equal_to(["tsc"]) 

221 

222 

223def test_get_tsc_command_with_bunx_fallback(tsc_plugin: TscPlugin) -> None: 

224 """Fall back to bunx when tsc not directly available. 

225 

226 Args: 

227 tsc_plugin: The TscPlugin instance to test. 

228 """ 

229 

230 def which_side_effect(cmd: str) -> str | None: 

231 if cmd == "tsc": 

232 return None 

233 if cmd == "bunx": 

234 return "/usr/bin/bunx" 

235 return None 

236 

237 with patch("shutil.which", side_effect=which_side_effect): 

238 cmd = tsc_plugin._get_tsc_command() 

239 

240 assert_that(cmd).is_equal_to(["bunx", "tsc"]) 

241 

242 

243def test_get_tsc_command_with_npx_fallback(tsc_plugin: TscPlugin) -> None: 

244 """Fall back to npx when tsc and bunx not available. 

245 

246 Args: 

247 tsc_plugin: The TscPlugin instance to test. 

248 """ 

249 

250 def which_side_effect(cmd: str) -> str | None: 

251 if cmd == "npx": 

252 return "/usr/bin/npx" 

253 return None 

254 

255 with patch("shutil.which", side_effect=which_side_effect): 

256 cmd = tsc_plugin._get_tsc_command() 

257 

258 assert_that(cmd).is_equal_to(["npx", "tsc"]) 

259 

260 

261def test_get_tsc_command_fallback_to_tsc(tsc_plugin: TscPlugin) -> None: 

262 """Fall back to tsc when nothing else available. 

263 

264 Args: 

265 tsc_plugin: The TscPlugin instance to test. 

266 """ 

267 with patch("shutil.which", return_value=None): 

268 cmd = tsc_plugin._get_tsc_command() 

269 

270 assert_that(cmd).is_equal_to(["tsc"]) 

271 

272 

273# ============================================================================= 

274# Tests for TscPlugin._build_command method 

275# ============================================================================= 

276 

277 

278def test_build_command_default(tsc_plugin: TscPlugin) -> None: 

279 """Build command with default options. 

280 

281 Args: 

282 tsc_plugin: The TscPlugin instance to test. 

283 """ 

284 with patch.object(tsc_plugin, "_get_tsc_command", return_value=["tsc"]): 

285 cmd = tsc_plugin._build_command(files=["src/main.ts"]) 

286 

287 assert_that(cmd).contains("tsc") 

288 assert_that(cmd).contains("--noEmit") 

289 assert_that(cmd).contains("--pretty", "false") 

290 assert_that(cmd).contains("--skipLibCheck") 

291 assert_that(cmd).contains("src/main.ts") 

292 

293 

294def test_build_command_with_project(tsc_plugin: TscPlugin) -> None: 

295 """Build command with project option. 

296 

297 Args: 

298 tsc_plugin: The TscPlugin instance to test. 

299 """ 

300 with patch.object(tsc_plugin, "_get_tsc_command", return_value=["tsc"]): 

301 cmd = tsc_plugin._build_command( 

302 files=[], 

303 project_path="tsconfig.build.json", 

304 ) 

305 

306 assert_that(cmd).contains("--project") 

307 assert_that(cmd).contains("tsconfig.build.json") 

308 

309 

310def test_build_command_with_strict(tsc_plugin: TscPlugin) -> None: 

311 """Build command with strict mode enabled. 

312 

313 Args: 

314 tsc_plugin: The TscPlugin instance to test. 

315 """ 

316 tsc_plugin.set_options(strict=True) 

317 with patch.object(tsc_plugin, "_get_tsc_command", return_value=["tsc"]): 

318 cmd = tsc_plugin._build_command(files=["src/main.ts"]) 

319 

320 assert_that(cmd).contains("--strict") 

321 

322 

323def test_build_command_without_strict(tsc_plugin: TscPlugin) -> None: 

324 """Build command with strict mode disabled. 

325 

326 Args: 

327 tsc_plugin: The TscPlugin instance to test. 

328 """ 

329 tsc_plugin.set_options(strict=False) 

330 with patch.object(tsc_plugin, "_get_tsc_command", return_value=["tsc"]): 

331 cmd = tsc_plugin._build_command(files=["src/main.ts"]) 

332 

333 # --strict is off by default in tsc, so no flag is emitted when strict=False 

334 assert_that("--strict" in cmd).is_false() 

335 

336 

337def test_build_command_without_skip_lib_check(tsc_plugin: TscPlugin) -> None: 

338 """Build command without skip lib check. 

339 

340 Args: 

341 tsc_plugin: The TscPlugin instance to test. 

342 """ 

343 tsc_plugin.set_options(skip_lib_check=False) 

344 with patch.object(tsc_plugin, "_get_tsc_command", return_value=["tsc"]): 

345 cmd = tsc_plugin._build_command(files=["src/main.ts"]) 

346 

347 assert_that("--skipLibCheck" in cmd).is_false()