Coverage for tests / integration / tools / test_prettier_integration.py: 97%

94 statements  

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

1"""Integration tests for Prettier tool definition. 

2 

3These tests require prettier to be installed (npm install -g prettier). 

4They verify the PrettierPlugin definition, check command, fix command, and set_options method. 

5""" 

6 

7from __future__ import annotations 

8 

9import json 

10import shutil 

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# Skip all tests if prettier is not installed 

22pytestmark = pytest.mark.skipif( 

23 shutil.which("prettier") is None, 

24 reason="prettier not installed", 

25) 

26 

27 

28@pytest.fixture 

29def temp_json_file_unformatted(tmp_path: Path) -> str: 

30 """Create a temporary JSON file with formatting issues. 

31 

32 Creates a file containing a single-line JSON object that Prettier 

33 should format with proper indentation and newlines. 

34 

35 Args: 

36 tmp_path: Pytest fixture providing a temporary directory. 

37 

38 Returns: 

39 Path to the created file as a string. 

40 """ 

41 file_path = tmp_path / "unformatted.json" 

42 file_path.write_text('{"name":"test","version":"1.0.0","dependencies":{}}') 

43 return str(file_path) 

44 

45 

46# --- Tests for PrettierPlugin definition --- 

47 

48 

49@pytest.mark.parametrize( 

50 ("attr", "expected"), 

51 [ 

52 ("name", "prettier"), 

53 ("can_fix", True), 

54 ], 

55 ids=["name", "can_fix"], 

56) 

57def test_definition_attributes( 

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

59 attr: str, 

60 expected: object, 

61) -> None: 

62 """Verify PrettierPlugin definition has correct attribute values. 

63 

64 Tests that the plugin definition exposes the expected values for 

65 name and can_fix attributes. 

66 

67 Args: 

68 get_plugin: Fixture factory to get plugin instances. 

69 attr: The attribute name to check on the definition. 

70 expected: The expected value of the attribute. 

71 """ 

72 prettier_plugin = get_plugin("prettier") 

73 assert_that(getattr(prettier_plugin.definition, attr)).is_equal_to(expected) 

74 

75 

76def test_definition_file_patterns(get_plugin: Callable[[str], BaseToolPlugin]) -> None: 

77 """Verify PrettierPlugin definition includes non-JS/TS file patterns. 

78 

79 Tests that the plugin is configured to handle CSS, HTML, JSON, YAML, Markdown, 

80 GraphQL, and Astro files. JS/TS files are handled by oxfmt for better performance. 

81 

82 Args: 

83 get_plugin: Fixture factory to get plugin instances. 

84 """ 

85 prettier_plugin = get_plugin("prettier") 

86 patterns = prettier_plugin.definition.file_patterns 

87 # Prettier handles non-JS/TS files (JS/TS delegated to oxfmt) 

88 has_expected_patterns = "*.css" in patterns and "*.json" in patterns 

89 assert_that(has_expected_patterns).is_true() 

90 # Verify Astro pattern is included 

91 assert_that("*.astro" in patterns).is_true() 

92 # Verify JS/TS patterns are NOT in prettier (handled by oxfmt) 

93 assert_that("*.js" in patterns or "*.ts" in patterns).is_false() 

94 

95 

96# --- Integration tests for prettier check command --- 

97 

98 

99@pytest.fixture 

100def temp_json_file_formatted(tmp_path: Path) -> str: 

101 """Create a temporary JSON file that is already formatted. 

102 

103 Creates a file containing properly formatted JSON that 

104 Prettier should leave unchanged. 

105 

106 Args: 

107 tmp_path: Pytest fixture providing a temporary directory. 

108 

109 Returns: 

110 Path to the created file as a string. 

111 """ 

112 file_path = tmp_path / "formatted.json" 

113 file_path.write_text( 

114 """\ 

115{ 

116 "name": "test", 

117 "version": "1.0.0", 

118 "dependencies": {} 

119} 

120""", 

121 ) 

122 return str(file_path) 

123 

124 

125@pytest.mark.parametrize( 

126 ("file_fixture", "expect_issues"), 

127 [ 

128 ("temp_json_file_unformatted", True), 

129 ("temp_json_file_formatted", False), 

130 ], 

131 ids=["unformatted_json", "formatted_json"], 

132) 

133def test_check_json_file_formatting_state( 

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

135 file_fixture: str, 

136 expect_issues: bool, 

137 request: pytest.FixtureRequest, 

138) -> None: 

139 """Verify Prettier check correctly detects JSON file formatting state. 

140 

141 Runs Prettier in check mode on files with different formatting states 

142 and verifies the expected issue count. Note: JS/TS files are handled 

143 by oxfmt, so we test with JSON files here. 

144 

145 Args: 

146 get_plugin: Fixture factory to get plugin instances. 

147 file_fixture: Name of the fixture providing the file path. 

148 expect_issues: Whether issues are expected (True for unformatted). 

149 request: Pytest request fixture for dynamic fixture access. 

150 """ 

151 file_path = request.getfixturevalue(file_fixture) 

152 prettier_plugin = get_plugin("prettier") 

153 result = prettier_plugin.check([file_path], {}) 

154 

155 assert_that(result).is_not_none() 

156 assert_that(result.name).is_equal_to("prettier") 

157 if expect_issues: 

158 assert_that(result.issues_count).is_greater_than(0) 

159 else: 

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

161 

162 

163# --- Integration tests for prettier fix command --- 

164 

165 

166def test_fix_formats_json_file( 

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

168 temp_json_file_unformatted: str, 

169) -> None: 

170 """Verify Prettier fix reformats JSON files. 

171 

172 Runs Prettier fix on a JSON file and verifies the file is 

173 formatted with proper indentation. 

174 

175 Args: 

176 get_plugin: Fixture factory to get plugin instances. 

177 temp_json_file_unformatted: Path to JSON file with formatting issues. 

178 """ 

179 prettier_plugin = get_plugin("prettier") 

180 original = Path(temp_json_file_unformatted).read_text() 

181 

182 result = prettier_plugin.fix([temp_json_file_unformatted], {}) 

183 

184 assert_that(result).is_not_none() 

185 assert_that(result.success).is_true() 

186 

187 new_content = Path(temp_json_file_unformatted).read_text() 

188 assert_that(new_content).is_not_equal_to(original) 

189 

190 

191# --- Tests for PrettierPlugin.set_options method --- 

192 

193 

194@pytest.mark.parametrize( 

195 ("option_name", "option_value", "expected"), 

196 [ 

197 ("verbose_fix_output", True, True), 

198 ("line_length", 120, 120), 

199 ], 

200 ids=["verbose_fix_output", "line_length"], 

201) 

202def test_set_options( 

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

204 option_name: str, 

205 option_value: object, 

206 expected: object, 

207) -> None: 

208 """Verify PrettierPlugin.set_options correctly sets various options. 

209 

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

211 Note: Formatting options like tab_width, use_tabs, single_quote are 

212 configured via .prettierrc config file, not via set_options. 

213 

214 Args: 

215 get_plugin: Fixture factory to get plugin instances. 

216 option_name: Name of the option to set. 

217 option_value: Value to set for the option. 

218 expected: Expected value when retrieving the option. 

219 """ 

220 prettier_plugin = get_plugin("prettier") 

221 prettier_plugin.set_options(**{option_name: option_value}) 

222 assert_that(prettier_plugin.options.get(option_name)).is_equal_to(expected) 

223 

224 

225# --- Integration tests for Astro file formatting --- 

226 

227PROJECT_ROOT = Path(__file__).resolve().parents[3] 

228 

229 

230@pytest.fixture 

231def astro_project_dir(tmp_path: Path) -> Path: 

232 """Create a temporary project directory with prettier-plugin-astro configured. 

233 

234 Sets up a .prettierrc that loads prettier-plugin-astro so Prettier 

235 can parse and format .astro files. Symlinks node_modules from the 

236 project root so the plugin can be resolved. 

237 

238 Args: 

239 tmp_path: Pytest fixture providing a temporary directory. 

240 

241 Returns: 

242 Path to the temporary project directory. 

243 """ 

244 # Symlink node_modules so prettier can resolve the astro plugin 

245 project_node_modules = PROJECT_ROOT / "node_modules" 

246 if not project_node_modules.exists(): 

247 pytest.skip("node_modules not found; run bun install first") 

248 try: 

249 (tmp_path / "node_modules").symlink_to(project_node_modules) 

250 except OSError as exc: 

251 pytest.skip(f"Cannot create symlink: {exc}") 

252 

253 prettierrc = {"plugins": ["prettier-plugin-astro"]} 

254 (tmp_path / ".prettierrc").write_text(json.dumps(prettierrc)) 

255 return tmp_path 

256 

257 

258@pytest.fixture 

259def temp_astro_file_unformatted(astro_project_dir: Path) -> str: 

260 """Create a temporary Astro file with formatting issues. 

261 

262 Creates a file with bad indentation, missing semicolons, and 

263 inconsistent quotes that Prettier should reformat. 

264 

265 Args: 

266 astro_project_dir: Temporary project with .prettierrc configured. 

267 

268 Returns: 

269 Path to the created file as a string. 

270 """ 

271 file_path = astro_project_dir / "unformatted.astro" 

272 file_path.write_text( 

273 '---\nconst greeting = "Hello"\n' 

274 'const items = ["apple","banana","cherry"]\n' 

275 "---\n\n<html>\n<head>\n<title>Test</title>\n" 

276 "</head>\n<body>\n <h1>{greeting}</h1>\n" 

277 "<ul>\n{items.map((item) => (\n<li>{item}</li>\n" 

278 "))}\n</ul>\n</body>\n</html>\n", 

279 ) 

280 return str(file_path) 

281 

282 

283@pytest.fixture 

284def temp_astro_file_formatted(astro_project_dir: Path) -> str: 

285 """Create a temporary Astro file that is already formatted. 

286 

287 Creates a file that conforms to Prettier's default formatting 

288 with prettier-plugin-astro. 

289 

290 Args: 

291 astro_project_dir: Temporary project with .prettierrc configured. 

292 

293 Returns: 

294 Path to the created file as a string. 

295 """ 

296 file_path = astro_project_dir / "formatted.astro" 

297 file_path.write_text( 

298 '---\nconst greeting = "Hello";\n' 

299 'const items = ["apple", "banana", "cherry"];\n' 

300 "---\n\n<html>\n <head>\n <title>Test</title>\n" 

301 " </head>\n <body>\n <h1>{greeting}</h1>\n" 

302 " <ul>\n {items.map((item) => <li>{item}</li>)}\n" 

303 " </ul>\n </body>\n</html>\n", 

304 ) 

305 return str(file_path) 

306 

307 

308@pytest.mark.parametrize( 

309 ("file_fixture", "expect_issues"), 

310 [ 

311 ("temp_astro_file_unformatted", True), 

312 ("temp_astro_file_formatted", False), 

313 ], 

314 ids=["unformatted_astro", "formatted_astro"], 

315) 

316def test_check_astro_file_formatting_state( 

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

318 file_fixture: str, 

319 expect_issues: bool, 

320 request: pytest.FixtureRequest, 

321) -> None: 

322 """Verify Prettier check correctly detects Astro file formatting state. 

323 

324 Runs Prettier in check mode on .astro files with different formatting 

325 states and verifies the expected issue count. Requires prettier-plugin-astro 

326 to be installed and configured via .prettierrc. 

327 

328 Args: 

329 get_plugin: Fixture factory to get plugin instances. 

330 file_fixture: Name of the fixture providing the file path. 

331 expect_issues: Whether issues are expected (True for unformatted). 

332 request: Pytest request fixture for dynamic fixture access. 

333 """ 

334 file_path = request.getfixturevalue(file_fixture) 

335 prettier_plugin = get_plugin("prettier") 

336 result = prettier_plugin.check([file_path], {}) 

337 

338 assert_that(result).is_not_none() 

339 assert_that(result.name).is_equal_to("prettier") 

340 if expect_issues: 

341 assert_that(result.issues_count).is_greater_than(0) 

342 else: 

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

344 

345 

346def test_fix_formats_astro_file( 

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

348 temp_astro_file_unformatted: str, 

349) -> None: 

350 """Verify Prettier fix reformats Astro files. 

351 

352 Runs Prettier fix on an Astro file and verifies the file is 

353 reformatted with proper indentation and consistent style. 

354 

355 Args: 

356 get_plugin: Fixture factory to get plugin instances. 

357 temp_astro_file_unformatted: Path to Astro file with formatting issues. 

358 """ 

359 prettier_plugin = get_plugin("prettier") 

360 original = Path(temp_astro_file_unformatted).read_text() 

361 

362 result = prettier_plugin.fix([temp_astro_file_unformatted], {}) 

363 

364 assert_that(result).is_not_none() 

365 assert_that(result.success).is_true() 

366 

367 new_content = Path(temp_astro_file_unformatted).read_text() 

368 assert_that(new_content).is_not_equal_to(original)