Coverage for tests / unit / tools / vue_tsc / test_execution.py: 100%

85 statements  

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

1"""Unit tests for vue-tsc plugin check method execution.""" 

2 

3from __future__ import annotations 

4 

5from pathlib import Path 

6from typing import Any 

7from unittest.mock import patch 

8 

9import pytest 

10from assertpy import assert_that 

11 

12from lintro.parsers.vue_tsc.vue_tsc_issue import VueTscIssue 

13from lintro.tools.definitions.vue_tsc import VueTscPlugin 

14 

15 

16def _mock_subprocess_success(**kwargs: Any) -> tuple[bool, str]: 

17 """Mock subprocess that returns success with no output. 

18 

19 Args: 

20 **kwargs: Ignored keyword arguments. 

21 

22 Returns: 

23 Tuple of (success=True, empty string). 

24 """ 

25 return (True, "") 

26 

27 

28def _mock_subprocess_with_issues(**kwargs: Any) -> tuple[bool, str]: 

29 """Mock subprocess that returns output with type errors. 

30 

31 Args: 

32 **kwargs: Ignored keyword arguments. 

33 

34 Returns: 

35 Tuple of (success=False, error output). 

36 """ 

37 output = ( 

38 "src/components/Button.vue(10,5): error TS2322: " 

39 "Type 'string' is not assignable to type 'number'.\n" 

40 "src/components/Card.vue(15,10): error TS2339: " 

41 "Property 'foo' does not exist on type 'Props'." 

42 ) 

43 return (False, output) 

44 

45 

46def test_check_no_vue_files( 

47 vue_tsc_plugin: VueTscPlugin, 

48 tmp_path: Path, 

49) -> None: 

50 """Check returns early when no Vue files found. 

51 

52 Args: 

53 vue_tsc_plugin: The VueTscPlugin instance to test. 

54 tmp_path: Temporary directory path for test files. 

55 """ 

56 # Create a non-Vue file 

57 test_file = tmp_path / "test.ts" 

58 test_file.write_text("const x = 1;") 

59 

60 result = vue_tsc_plugin.check([str(test_file)], {}) 

61 

62 assert_that(result.success).is_true() 

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

64 

65 

66def test_check_with_mocked_subprocess_success( 

67 vue_tsc_plugin: VueTscPlugin, 

68 tmp_path: Path, 

69) -> None: 

70 """Check returns success when vue-tsc finds no issues. 

71 

72 Args: 

73 vue_tsc_plugin: The VueTscPlugin instance to test. 

74 tmp_path: Temporary directory path for test files. 

75 """ 

76 # Create Vue file and tsconfig 

77 vue_file = tmp_path / "test.vue" 

78 vue_file.write_text("<template><div>Hello</div></template>") 

79 tsconfig = tmp_path / "tsconfig.json" 

80 tsconfig.write_text('{"compilerOptions": {}}') 

81 

82 with patch.object( 

83 vue_tsc_plugin, 

84 "_run_subprocess", 

85 side_effect=_mock_subprocess_success, 

86 ): 

87 result = vue_tsc_plugin.check([str(tmp_path)], {}) 

88 

89 assert_that(result.success).is_true() 

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

91 

92 

93def test_check_with_mocked_subprocess_issues_found( 

94 vue_tsc_plugin: VueTscPlugin, 

95 tmp_path: Path, 

96) -> None: 

97 """Check returns issues when vue-tsc finds type errors. 

98 

99 Args: 

100 vue_tsc_plugin: The VueTscPlugin instance to test. 

101 tmp_path: Temporary directory path for test files. 

102 """ 

103 # Create Vue file and tsconfig 

104 vue_file = tmp_path / "Button.vue" 

105 vue_file.write_text("<template><div>Hello</div></template>") 

106 tsconfig = tmp_path / "tsconfig.json" 

107 tsconfig.write_text('{"compilerOptions": {}}') 

108 

109 with patch.object( 

110 vue_tsc_plugin, 

111 "_run_subprocess", 

112 side_effect=_mock_subprocess_with_issues, 

113 ): 

114 result = vue_tsc_plugin.check([str(tmp_path)], {}) 

115 

116 assert_that(result.success).is_false() 

117 assert_that(result.issues_count).is_equal_to(2) 

118 issues = result.issues 

119 assert_that(issues).is_not_none() 

120 assert issues is not None 

121 assert_that(issues).is_length(2) 

122 

123 # Verify first issue 

124 first_issue = issues[0] 

125 assert isinstance(first_issue, VueTscIssue) 

126 assert_that(first_issue.file).is_equal_to("src/components/Button.vue") 

127 assert_that(first_issue.line).is_equal_to(10) 

128 assert_that(first_issue.code).is_equal_to("TS2322") 

129 

130 

131def test_fix_raises_not_implemented( 

132 vue_tsc_plugin: VueTscPlugin, 

133 tmp_path: Path, 

134) -> None: 

135 """Fix method raises NotImplementedError. 

136 

137 Args: 

138 vue_tsc_plugin: The VueTscPlugin instance to test. 

139 tmp_path: Temporary directory path for test files. 

140 """ 

141 with pytest.raises(NotImplementedError, match="cannot automatically fix"): 

142 vue_tsc_plugin.fix([str(tmp_path)], {}) 

143 

144 

145def test_check_with_project_option( 

146 vue_tsc_plugin: VueTscPlugin, 

147 tmp_path: Path, 

148) -> None: 

149 """Check uses project option when provided. 

150 

151 Args: 

152 vue_tsc_plugin: The VueTscPlugin instance to test. 

153 tmp_path: Temporary directory path for test files. 

154 """ 

155 # Create Vue file and tsconfig 

156 vue_file = tmp_path / "test.vue" 

157 vue_file.write_text("<template><div>Hello</div></template>") 

158 tsconfig = tmp_path / "tsconfig.app.json" 

159 tsconfig.write_text('{"compilerOptions": {}}') 

160 

161 captured_cmd: list[str] = [] 

162 

163 def capture_cmd(cmd: list[str], **kwargs: Any) -> tuple[bool, str]: 

164 captured_cmd.extend(cmd) 

165 return (True, "") 

166 

167 with patch.object( 

168 vue_tsc_plugin, 

169 "_run_subprocess", 

170 side_effect=capture_cmd, 

171 ): 

172 vue_tsc_plugin.check( 

173 [str(tmp_path)], 

174 {"project": str(tsconfig)}, 

175 ) 

176 

177 # Verify --project was passed 

178 assert_that(captured_cmd).contains("--project") 

179 project_idx = captured_cmd.index("--project") 

180 assert_that(captured_cmd[project_idx + 1]).is_equal_to(str(tsconfig)) 

181 

182 

183def test_check_no_tsconfig_passes_files_directly( 

184 vue_tsc_plugin: VueTscPlugin, 

185 tmp_path: Path, 

186) -> None: 

187 """Check passes files directly when no tsconfig.json is found. 

188 

189 Args: 

190 vue_tsc_plugin: The VueTscPlugin instance to test. 

191 tmp_path: Temporary directory path for test files. 

192 """ 

193 # Create Vue file but NO tsconfig.json 

194 vue_file = tmp_path / "App.vue" 

195 vue_file.write_text("<template><div>Hello</div></template>") 

196 

197 captured_cmd: list[str] = [] 

198 

199 def capture_cmd(cmd: list[str], **kwargs: Any) -> tuple[bool, str]: 

200 captured_cmd.extend(cmd) 

201 return (True, "") 

202 

203 with patch.object( 

204 vue_tsc_plugin, 

205 "_run_subprocess", 

206 side_effect=capture_cmd, 

207 ): 

208 result = vue_tsc_plugin.check([str(tmp_path)], {}) 

209 

210 assert_that(result.success).is_true() 

211 # No --project flag should be present 

212 assert_that(captured_cmd).does_not_contain("--project") 

213 # File should be passed directly in the command 

214 assert_that(" ".join(captured_cmd)).contains("App.vue") 

215 

216 

217def test_check_timeout_handling( 

218 vue_tsc_plugin: VueTscPlugin, 

219 tmp_path: Path, 

220) -> None: 

221 """Check handles subprocess timeout gracefully. 

222 

223 Args: 

224 vue_tsc_plugin: The VueTscPlugin instance to test. 

225 tmp_path: Temporary directory path for test files. 

226 """ 

227 import subprocess 

228 

229 # Create Vue file and tsconfig 

230 vue_file = tmp_path / "test.vue" 

231 vue_file.write_text("<template><div>Hello</div></template>") 

232 tsconfig = tmp_path / "tsconfig.json" 

233 tsconfig.write_text('{"compilerOptions": {}}') 

234 

235 with patch.object( 

236 vue_tsc_plugin, 

237 "_run_subprocess", 

238 side_effect=subprocess.TimeoutExpired("vue-tsc", 120), 

239 ): 

240 result = vue_tsc_plugin.check([str(tmp_path)], {}) 

241 

242 assert_that(result.success).is_false() 

243 assert_that(result.output).contains("timeout")