Coverage for tests / integration / tools / test_oxfmt_integration.py: 97%
129 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"""Integration tests for Oxfmt tool definition.
3These tests require oxfmt to be installed and available in PATH.
4They verify the OxfmtPlugin definition, check command, fix command, and set_options method.
5"""
7from __future__ import annotations
9import shutil
10import subprocess
11from collections.abc import Callable
12from pathlib import Path
13from typing import TYPE_CHECKING
15import pytest
16from assertpy import assert_that
18if TYPE_CHECKING:
19 from lintro.plugins.base import BaseToolPlugin
22def oxfmt_is_available() -> bool:
23 """Check if oxfmt is installed and actually works.
25 This is more robust than just checking shutil.which() because wrapper
26 scripts may exist even when the underlying npm package isn't installed.
27 We verify the tool works by actually formatting a simple JavaScript snippet.
29 Returns:
30 True if oxfmt is available and functional, False otherwise.
31 """
32 if shutil.which("oxfmt") is None:
33 return False
34 try:
35 # First check --version works
36 version_result = subprocess.run(
37 ["oxfmt", "--version"],
38 capture_output=True,
39 timeout=10,
40 check=False,
41 )
42 if version_result.returncode != 0:
43 return False
45 # Then verify it can actually format code (catches missing npm packages)
46 format_result = subprocess.run(
47 ["oxfmt", "--stdin-filepath", "test.js"],
48 input=b"const x=1;\n",
49 capture_output=True,
50 timeout=10,
51 check=False,
52 )
53 return format_result.returncode == 0
54 except (subprocess.TimeoutExpired, OSError):
55 return False
58# Skip all tests if oxfmt is not installed or not working
59pytestmark = pytest.mark.skipif(
60 not oxfmt_is_available(),
61 reason="oxfmt not installed or not working",
62)
65@pytest.fixture
66def temp_js_file_unformatted(tmp_path: Path) -> str:
67 """Create a temporary JavaScript file with formatting issues.
69 Creates a file containing code with formatting issues that Oxfmt
70 should fix, including:
71 - Missing spaces around operators
72 - Missing spaces after commas
73 - Missing newlines
75 Args:
76 tmp_path: Pytest fixture providing a temporary directory.
78 Returns:
79 Path to the created file as a string.
80 """
81 file_path = tmp_path / "test_file.js"
82 file_path.write_text(
83 """\
84function foo(x,y,z){return x+y+z}
85const obj={a:1,b:2,c:3}
86""",
87 )
88 return str(file_path)
91@pytest.fixture
92def temp_js_file_formatted(tmp_path: Path) -> str:
93 """Create a temporary JavaScript file that is already formatted.
95 Creates a file and formats it with oxfmt to ensure it passes checking
96 regardless of oxfmt version differences.
98 Args:
99 tmp_path: Pytest fixture providing a temporary directory.
101 Returns:
102 Path to the created file as a string.
103 """
104 file_path = tmp_path / "formatted_file.js"
105 # Write unformatted code first
106 file_path.write_text(
107 """\
108function foo(x,y,z){return x+y+z}
109const obj={a:1,b:2,c:3}
110""",
111 )
112 # Format it with oxfmt so it's guaranteed to be "formatted" for this version
113 subprocess.run(
114 ["oxfmt", "--write", str(file_path)],
115 check=True,
116 capture_output=True,
117 )
118 return str(file_path)
121@pytest.fixture
122def temp_ts_file_unformatted(tmp_path: Path) -> str:
123 """Create a temporary TypeScript file with formatting issues.
125 Creates a file containing TypeScript code with formatting issues that Oxfmt
126 should fix.
128 Args:
129 tmp_path: Pytest fixture providing a temporary directory.
131 Returns:
132 Path to the created file as a string.
133 """
134 file_path = tmp_path / "test_file.ts"
135 file_path.write_text(
136 """\
137interface User{name:string;age:number}
138const user:User={name:"John",age:30}
139""",
140 )
141 return str(file_path)
144# --- Tests for OxfmtPlugin definition ---
147@pytest.mark.parametrize(
148 ("attr", "expected"),
149 [
150 ("name", "oxfmt"),
151 ("can_fix", True),
152 ],
153 ids=["name", "can_fix"],
154)
155def test_definition_attributes(
156 get_plugin: Callable[[str], BaseToolPlugin],
157 attr: str,
158 expected: object,
159) -> None:
160 """Verify OxfmtPlugin definition has correct attribute values.
162 Tests that the plugin definition exposes the expected values for
163 name and can_fix attributes.
165 Args:
166 get_plugin: Fixture factory to get plugin instances.
167 attr: The attribute name to check on the definition.
168 expected: The expected value of the attribute.
169 """
170 oxfmt_plugin = get_plugin("oxfmt")
171 assert_that(getattr(oxfmt_plugin.definition, attr)).is_equal_to(expected)
174def test_definition_file_patterns(
175 get_plugin: Callable[[str], BaseToolPlugin],
176) -> None:
177 """Verify OxfmtPlugin definition includes JavaScript/TypeScript/Vue file patterns.
179 Tests that the plugin is configured to handle JS/TS and Vue files.
181 Args:
182 get_plugin: Fixture factory to get plugin instances.
183 """
184 oxfmt_plugin = get_plugin("oxfmt")
185 assert_that(oxfmt_plugin.definition.file_patterns).contains("*.js")
186 assert_that(oxfmt_plugin.definition.file_patterns).contains("*.ts")
187 assert_that(oxfmt_plugin.definition.file_patterns).contains("*.jsx")
188 assert_that(oxfmt_plugin.definition.file_patterns).contains("*.tsx")
189 assert_that(oxfmt_plugin.definition.file_patterns).contains("*.vue")
192def test_definition_has_version_command(
193 get_plugin: Callable[[str], BaseToolPlugin],
194) -> None:
195 """Verify OxfmtPlugin definition has a version command.
197 Tests that the plugin exposes a version command for checking
198 the installed Oxfmt version.
200 Args:
201 get_plugin: Fixture factory to get plugin instances.
202 """
203 oxfmt_plugin = get_plugin("oxfmt")
204 assert_that(oxfmt_plugin.definition.version_command).is_not_none()
207# --- Integration tests for oxfmt check command ---
210def test_check_file_unformatted(
211 get_plugin: Callable[[str], BaseToolPlugin],
212 temp_js_file_unformatted: str,
213) -> None:
214 """Verify Oxfmt check detects formatting issues in unformatted files.
216 Runs Oxfmt on a file containing formatting issues and verifies that
217 issues are found.
219 Args:
220 get_plugin: Fixture factory to get plugin instances.
221 temp_js_file_unformatted: Path to file with formatting issues.
222 """
223 oxfmt_plugin = get_plugin("oxfmt")
224 result = oxfmt_plugin.check([temp_js_file_unformatted], {})
226 assert_that(result).is_not_none()
227 assert_that(result.name).is_equal_to("oxfmt")
228 assert_that(result.issues_count).is_greater_than(0)
231def test_check_file_formatted(
232 get_plugin: Callable[[str], BaseToolPlugin],
233 temp_js_file_formatted: str,
234) -> None:
235 """Verify Oxfmt check passes on properly formatted files.
237 Runs Oxfmt on a clean file and verifies no issues are found.
239 Args:
240 get_plugin: Fixture factory to get plugin instances.
241 temp_js_file_formatted: Path to formatted file.
242 """
243 oxfmt_plugin = get_plugin("oxfmt")
244 result = oxfmt_plugin.check([temp_js_file_formatted], {})
246 assert_that(result).is_not_none()
247 assert_that(result.name).is_equal_to("oxfmt")
248 assert_that(result.success).is_true()
251def test_check_typescript_file(
252 get_plugin: Callable[[str], BaseToolPlugin],
253 temp_ts_file_unformatted: str,
254) -> None:
255 """Verify Oxfmt check works with TypeScript files.
257 Runs Oxfmt on a TypeScript file and verifies issues are found.
259 Args:
260 get_plugin: Fixture factory to get plugin instances.
261 temp_ts_file_unformatted: Path to TypeScript file with formatting issues.
262 """
263 oxfmt_plugin = get_plugin("oxfmt")
264 result = oxfmt_plugin.check([temp_ts_file_unformatted], {})
266 assert_that(result).is_not_none()
267 assert_that(result.name).is_equal_to("oxfmt")
268 assert_that(result.issues_count).is_greater_than(0)
271def test_check_empty_directory(
272 get_plugin: Callable[[str], BaseToolPlugin],
273 tmp_path: Path,
274) -> None:
275 """Verify Oxfmt check handles empty directories gracefully.
277 Runs Oxfmt on an empty directory and verifies a result is returned
278 with zero issues.
280 Args:
281 get_plugin: Fixture factory to get plugin instances.
282 tmp_path: Pytest fixture providing a temporary directory.
283 """
284 oxfmt_plugin = get_plugin("oxfmt")
285 result = oxfmt_plugin.check([str(tmp_path)], {})
287 assert_that(result).is_not_none()
288 assert_that(result.issues_count).is_equal_to(0)
291# --- Integration tests for oxfmt fix command ---
294def test_fix_formats_file(
295 get_plugin: Callable[[str], BaseToolPlugin],
296 temp_js_file_unformatted: str,
297) -> None:
298 """Verify Oxfmt fix reformats files with formatting issues.
300 Runs Oxfmt fix on a file with formatting issues and verifies
301 the file content changes.
303 Args:
304 get_plugin: Fixture factory to get plugin instances.
305 temp_js_file_unformatted: Path to file with formatting issues.
306 """
307 oxfmt_plugin = get_plugin("oxfmt")
308 original = Path(temp_js_file_unformatted).read_text()
310 result = oxfmt_plugin.fix([temp_js_file_unformatted], {})
312 assert_that(result).is_not_none()
313 assert_that(result.name).is_equal_to("oxfmt")
315 new_content = Path(temp_js_file_unformatted).read_text()
316 assert_that(new_content).is_not_equal_to(original)
319def test_fix_typescript_file(
320 get_plugin: Callable[[str], BaseToolPlugin],
321 temp_ts_file_unformatted: str,
322) -> None:
323 """Verify Oxfmt fix works with TypeScript files.
325 Runs Oxfmt fix on a TypeScript file and verifies content changes.
327 Args:
328 get_plugin: Fixture factory to get plugin instances.
329 temp_ts_file_unformatted: Path to TypeScript file with formatting issues.
330 """
331 oxfmt_plugin = get_plugin("oxfmt")
332 original = Path(temp_ts_file_unformatted).read_text()
334 result = oxfmt_plugin.fix([temp_ts_file_unformatted], {})
336 assert_that(result).is_not_none()
338 new_content = Path(temp_ts_file_unformatted).read_text()
339 assert_that(new_content).is_not_equal_to(original)
342def test_fix_formatted_file_unchanged(
343 get_plugin: Callable[[str], BaseToolPlugin],
344 temp_js_file_formatted: str,
345) -> None:
346 """Verify Oxfmt fix doesn't change already formatted files.
348 Runs Oxfmt fix on a clean file and verifies the content stays the same.
350 Args:
351 get_plugin: Fixture factory to get plugin instances.
352 temp_js_file_formatted: Path to formatted file.
353 """
354 oxfmt_plugin = get_plugin("oxfmt")
355 original = Path(temp_js_file_formatted).read_text()
357 result = oxfmt_plugin.fix([temp_js_file_formatted], {})
359 assert_that(result).is_not_none()
360 assert_that(result.success).is_true()
362 new_content = Path(temp_js_file_formatted).read_text()
363 assert_that(new_content).is_equal_to(original)
366def test_fix_reports_issue_counts(
367 get_plugin: Callable[[str], BaseToolPlugin],
368 temp_js_file_unformatted: str,
369) -> None:
370 """Verify Oxfmt fix reports correct issue counts.
372 Runs Oxfmt fix and verifies that initial, fixed, and remaining
373 issue counts are reported.
375 Args:
376 get_plugin: Fixture factory to get plugin instances.
377 temp_js_file_unformatted: Path to file with formatting issues.
378 """
379 oxfmt_plugin = get_plugin("oxfmt")
380 result = oxfmt_plugin.fix([temp_js_file_unformatted], {})
382 assert_that(result).is_not_none()
383 assert_that(result.initial_issues_count).is_not_none()
384 assert_that(result.fixed_issues_count).is_not_none()
385 assert_that(result.remaining_issues_count).is_not_none()
388# --- Tests for OxfmtPlugin.set_options method ---
391@pytest.mark.parametrize(
392 ("option_name", "option_value", "expected"),
393 [
394 ("verbose_fix_output", True, True),
395 ("verbose_fix_output", False, False),
396 ],
397 ids=["verbose_fix_output_true", "verbose_fix_output_false"],
398)
399def test_set_options(
400 get_plugin: Callable[[str], BaseToolPlugin],
401 option_name: str,
402 option_value: object,
403 expected: object,
404) -> None:
405 """Verify OxfmtPlugin.set_options correctly sets various options.
407 Tests that plugin options can be set and retrieved correctly.
409 Args:
410 get_plugin: Fixture factory to get plugin instances.
411 option_name: Name of the option to set.
412 option_value: Value to set for the option.
413 expected: Expected value when retrieving the option.
414 """
415 oxfmt_plugin = get_plugin("oxfmt")
416 oxfmt_plugin.set_options(**{option_name: option_value})
417 assert_that(oxfmt_plugin.options.get(option_name)).is_equal_to(expected)
420# --- Integration tests for new oxfmt options ---
423@pytest.mark.parametrize(
424 ("option_name", "option_value", "expected"),
425 [
426 ("config", ".oxfmtrc.json", ".oxfmtrc.json"),
427 ("ignore_path", ".oxfmtignore", ".oxfmtignore"),
428 ],
429 ids=[
430 "config",
431 "ignore_path",
432 ],
433)
434def test_set_formatting_options(
435 get_plugin: Callable[[str], BaseToolPlugin],
436 option_name: str,
437 option_value: object,
438 expected: object,
439) -> None:
440 """Verify OxfmtPlugin.set_options correctly sets CLI options.
442 Tests that CLI options can be set and retrieved correctly.
444 Note:
445 Formatting options (print_width, tab_width, use_tabs, semi, single_quote)
446 are only supported via config file (.oxfmtrc.json), not CLI flags.
448 Args:
449 get_plugin: Fixture factory to get plugin instances.
450 option_name: Name of the option to set.
451 option_value: Value to set for the option.
452 expected: Expected value when retrieving the option.
453 """
454 oxfmt_plugin = get_plugin("oxfmt")
455 oxfmt_plugin.set_options(**{option_name: option_value})
456 assert_that(oxfmt_plugin.options.get(option_name)).is_equal_to(expected)
459def test_set_exclude_patterns(
460 get_plugin: Callable[[str], BaseToolPlugin],
461) -> None:
462 """Verify OxfmtPlugin.set_options correctly sets exclude_patterns.
464 Tests that exclude patterns can be set and retrieved correctly.
466 Args:
467 get_plugin: Fixture factory to get plugin instances.
468 """
469 oxfmt_plugin = get_plugin("oxfmt")
470 oxfmt_plugin.set_options(exclude_patterns=["node_modules", "dist"])
471 assert_that(oxfmt_plugin.exclude_patterns).contains("node_modules")
472 assert_that(oxfmt_plugin.exclude_patterns).contains("dist")
475def test_config_option_accepted_by_fix(
476 get_plugin: Callable[[str], BaseToolPlugin],
477 tmp_path: Path,
478) -> None:
479 """Verify config option is accepted and passed to oxfmt.
481 Tests that setting config path is accepted by the plugin and the fix
482 command completes successfully.
484 Note:
485 Formatting options (print_width, tab_width, use_tabs, semi, single_quote)
486 are only supported via config file (.oxfmtrc.json), not CLI flags.
488 Args:
489 get_plugin: Fixture factory to get plugin instances.
490 tmp_path: Pytest fixture providing a temporary directory.
491 """
492 # Create a config file
493 config_path = tmp_path / ".oxfmtrc.json"
494 config_path.write_text('{"printWidth": 40}\n')
496 # Create file with a long line
497 file_path = tmp_path / "test.js"
498 file_path.write_text(
499 "const longLine = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9 };\n",
500 )
502 oxfmt_plugin = get_plugin("oxfmt")
503 # Set the config path
504 oxfmt_plugin.set_options(config=str(config_path))
506 result = oxfmt_plugin.fix([str(file_path)], {})
508 assert_that(result).is_not_none()
509 assert_that(result.name).is_equal_to("oxfmt")
510 # Verify the option was stored correctly
511 assert_that(oxfmt_plugin.options.get("config")).is_equal_to(str(config_path))