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
« 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."""
3from __future__ import annotations
5from unittest.mock import patch
7import pytest
8from assertpy import assert_that
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)
18# =============================================================================
19# Tests for TscPlugin definition
20# =============================================================================
23def test_definition_name(tsc_plugin: TscPlugin) -> None:
24 """Plugin name is 'tsc'.
26 Args:
27 tsc_plugin: The TscPlugin instance to test.
28 """
29 assert_that(tsc_plugin.definition.name).is_equal_to("tsc")
32def test_definition_description(tsc_plugin: TscPlugin) -> None:
33 """Plugin has a description containing TypeScript.
35 Args:
36 tsc_plugin: The TscPlugin instance to test.
37 """
38 assert_that(tsc_plugin.definition.description).contains("TypeScript")
41def test_definition_can_fix(tsc_plugin: TscPlugin) -> None:
42 """Plugin cannot fix issues.
44 Args:
45 tsc_plugin: The TscPlugin instance to test.
46 """
47 assert_that(tsc_plugin.definition.can_fix).is_false()
50def test_definition_tool_type(tsc_plugin: TscPlugin) -> None:
51 """Plugin is a linter and type checker.
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()
61def test_definition_file_patterns(tsc_plugin: TscPlugin) -> None:
62 """Plugin handles TypeScript files.
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")
72def test_definition_native_configs(tsc_plugin: TscPlugin) -> None:
73 """Plugin recognizes tsconfig.json.
75 Args:
76 tsc_plugin: The TscPlugin instance to test.
77 """
78 assert_that(tsc_plugin.definition.native_configs).contains("tsconfig.json")
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.
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 )
116def test_definition_priority(tsc_plugin: TscPlugin) -> None:
117 """Plugin has correct priority.
119 Args:
120 tsc_plugin: The TscPlugin instance to test.
121 """
122 assert_that(tsc_plugin.definition.priority).is_equal_to(TSC_DEFAULT_PRIORITY)
125# =============================================================================
126# Tests for TscPlugin.set_options method
127# =============================================================================
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.
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)
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.
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 )
206# =============================================================================
207# Tests for TscPlugin._get_tsc_command method
208# =============================================================================
211def test_get_tsc_command_with_tsc_available(tsc_plugin: TscPlugin) -> None:
212 """Return direct tsc command when available.
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()
220 assert_that(cmd).is_equal_to(["tsc"])
223def test_get_tsc_command_with_bunx_fallback(tsc_plugin: TscPlugin) -> None:
224 """Fall back to bunx when tsc not directly available.
226 Args:
227 tsc_plugin: The TscPlugin instance to test.
228 """
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
237 with patch("shutil.which", side_effect=which_side_effect):
238 cmd = tsc_plugin._get_tsc_command()
240 assert_that(cmd).is_equal_to(["bunx", "tsc"])
243def test_get_tsc_command_with_npx_fallback(tsc_plugin: TscPlugin) -> None:
244 """Fall back to npx when tsc and bunx not available.
246 Args:
247 tsc_plugin: The TscPlugin instance to test.
248 """
250 def which_side_effect(cmd: str) -> str | None:
251 if cmd == "npx":
252 return "/usr/bin/npx"
253 return None
255 with patch("shutil.which", side_effect=which_side_effect):
256 cmd = tsc_plugin._get_tsc_command()
258 assert_that(cmd).is_equal_to(["npx", "tsc"])
261def test_get_tsc_command_fallback_to_tsc(tsc_plugin: TscPlugin) -> None:
262 """Fall back to tsc when nothing else available.
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()
270 assert_that(cmd).is_equal_to(["tsc"])
273# =============================================================================
274# Tests for TscPlugin._build_command method
275# =============================================================================
278def test_build_command_default(tsc_plugin: TscPlugin) -> None:
279 """Build command with default options.
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"])
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")
294def test_build_command_with_project(tsc_plugin: TscPlugin) -> None:
295 """Build command with project option.
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 )
306 assert_that(cmd).contains("--project")
307 assert_that(cmd).contains("tsconfig.build.json")
310def test_build_command_with_strict(tsc_plugin: TscPlugin) -> None:
311 """Build command with strict mode enabled.
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"])
320 assert_that(cmd).contains("--strict")
323def test_build_command_without_strict(tsc_plugin: TscPlugin) -> None:
324 """Build command with strict mode disabled.
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"])
333 # --strict is off by default in tsc, so no flag is emitted when strict=False
334 assert_that("--strict" in cmd).is_false()
337def test_build_command_without_skip_lib_check(tsc_plugin: TscPlugin) -> None:
338 """Build command without skip lib check.
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"])
347 assert_that("--skipLibCheck" in cmd).is_false()