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

1"""Integration tests for Oxfmt tool definition. 

2 

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""" 

6 

7from __future__ import annotations 

8 

9import shutil 

10import subprocess 

11from collections.abc import Callable 

12from pathlib import Path 

13from typing import TYPE_CHECKING 

14 

15import pytest 

16from assertpy import assert_that 

17 

18if TYPE_CHECKING: 

19 from lintro.plugins.base import BaseToolPlugin 

20 

21 

22def oxfmt_is_available() -> bool: 

23 """Check if oxfmt is installed and actually works. 

24 

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. 

28 

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 

44 

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 

56 

57 

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) 

63 

64 

65@pytest.fixture 

66def temp_js_file_unformatted(tmp_path: Path) -> str: 

67 """Create a temporary JavaScript file with formatting issues. 

68 

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 

74 

75 Args: 

76 tmp_path: Pytest fixture providing a temporary directory. 

77 

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) 

89 

90 

91@pytest.fixture 

92def temp_js_file_formatted(tmp_path: Path) -> str: 

93 """Create a temporary JavaScript file that is already formatted. 

94 

95 Creates a file and formats it with oxfmt to ensure it passes checking 

96 regardless of oxfmt version differences. 

97 

98 Args: 

99 tmp_path: Pytest fixture providing a temporary directory. 

100 

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) 

119 

120 

121@pytest.fixture 

122def temp_ts_file_unformatted(tmp_path: Path) -> str: 

123 """Create a temporary TypeScript file with formatting issues. 

124 

125 Creates a file containing TypeScript code with formatting issues that Oxfmt 

126 should fix. 

127 

128 Args: 

129 tmp_path: Pytest fixture providing a temporary directory. 

130 

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) 

142 

143 

144# --- Tests for OxfmtPlugin definition --- 

145 

146 

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. 

161 

162 Tests that the plugin definition exposes the expected values for 

163 name and can_fix attributes. 

164 

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) 

172 

173 

174def test_definition_file_patterns( 

175 get_plugin: Callable[[str], BaseToolPlugin], 

176) -> None: 

177 """Verify OxfmtPlugin definition includes JavaScript/TypeScript/Vue file patterns. 

178 

179 Tests that the plugin is configured to handle JS/TS and Vue files. 

180 

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") 

190 

191 

192def test_definition_has_version_command( 

193 get_plugin: Callable[[str], BaseToolPlugin], 

194) -> None: 

195 """Verify OxfmtPlugin definition has a version command. 

196 

197 Tests that the plugin exposes a version command for checking 

198 the installed Oxfmt version. 

199 

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() 

205 

206 

207# --- Integration tests for oxfmt check command --- 

208 

209 

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. 

215 

216 Runs Oxfmt on a file containing formatting issues and verifies that 

217 issues are found. 

218 

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], {}) 

225 

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) 

229 

230 

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. 

236 

237 Runs Oxfmt on a clean file and verifies no issues are found. 

238 

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], {}) 

245 

246 assert_that(result).is_not_none() 

247 assert_that(result.name).is_equal_to("oxfmt") 

248 assert_that(result.success).is_true() 

249 

250 

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. 

256 

257 Runs Oxfmt on a TypeScript file and verifies issues are found. 

258 

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], {}) 

265 

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) 

269 

270 

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. 

276 

277 Runs Oxfmt on an empty directory and verifies a result is returned 

278 with zero issues. 

279 

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)], {}) 

286 

287 assert_that(result).is_not_none() 

288 assert_that(result.issues_count).is_equal_to(0) 

289 

290 

291# --- Integration tests for oxfmt fix command --- 

292 

293 

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. 

299 

300 Runs Oxfmt fix on a file with formatting issues and verifies 

301 the file content changes. 

302 

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() 

309 

310 result = oxfmt_plugin.fix([temp_js_file_unformatted], {}) 

311 

312 assert_that(result).is_not_none() 

313 assert_that(result.name).is_equal_to("oxfmt") 

314 

315 new_content = Path(temp_js_file_unformatted).read_text() 

316 assert_that(new_content).is_not_equal_to(original) 

317 

318 

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. 

324 

325 Runs Oxfmt fix on a TypeScript file and verifies content changes. 

326 

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() 

333 

334 result = oxfmt_plugin.fix([temp_ts_file_unformatted], {}) 

335 

336 assert_that(result).is_not_none() 

337 

338 new_content = Path(temp_ts_file_unformatted).read_text() 

339 assert_that(new_content).is_not_equal_to(original) 

340 

341 

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. 

347 

348 Runs Oxfmt fix on a clean file and verifies the content stays the same. 

349 

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() 

356 

357 result = oxfmt_plugin.fix([temp_js_file_formatted], {}) 

358 

359 assert_that(result).is_not_none() 

360 assert_that(result.success).is_true() 

361 

362 new_content = Path(temp_js_file_formatted).read_text() 

363 assert_that(new_content).is_equal_to(original) 

364 

365 

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. 

371 

372 Runs Oxfmt fix and verifies that initial, fixed, and remaining 

373 issue counts are reported. 

374 

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], {}) 

381 

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() 

386 

387 

388# --- Tests for OxfmtPlugin.set_options method --- 

389 

390 

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. 

406 

407 Tests that plugin options can be set and retrieved correctly. 

408 

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) 

418 

419 

420# --- Integration tests for new oxfmt options --- 

421 

422 

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. 

441 

442 Tests that CLI options can be set and retrieved correctly. 

443 

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. 

447 

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) 

457 

458 

459def test_set_exclude_patterns( 

460 get_plugin: Callable[[str], BaseToolPlugin], 

461) -> None: 

462 """Verify OxfmtPlugin.set_options correctly sets exclude_patterns. 

463 

464 Tests that exclude patterns can be set and retrieved correctly. 

465 

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") 

473 

474 

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. 

480 

481 Tests that setting config path is accepted by the plugin and the fix 

482 command completes successfully. 

483 

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. 

487 

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') 

495 

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 ) 

501 

502 oxfmt_plugin = get_plugin("oxfmt") 

503 # Set the config path 

504 oxfmt_plugin.set_options(config=str(config_path)) 

505 

506 result = oxfmt_plugin.fix([str(file_path)], {}) 

507 

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))