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

70 statements  

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

1"""Unit tests for VueTscPlugin temp tsconfig functionality.""" 

2 

3from __future__ import annotations 

4 

5import json 

6from pathlib import Path 

7 

8from assertpy import assert_that 

9 

10from lintro.tools.definitions.vue_tsc import VueTscPlugin 

11 

12# ============================================================================= 

13# Tests for JSONC tsconfig parsing (issue #570) 

14# ============================================================================= 

15 

16 

17def test_create_temp_tsconfig_preserves_type_roots_from_jsonc_base( 

18 vue_tsc_plugin: VueTscPlugin, 

19 tmp_path: Path, 

20) -> None: 

21 """Verify typeRoots are preserved when base tsconfig uses JSONC features. 

22 

23 This is the primary scenario from issue #570: a tsconfig.json with 

24 comments and trailing commas should still have its typeRoots read 

25 and propagated into the temporary tsconfig. 

26 

27 Args: 

28 vue_tsc_plugin: Plugin instance fixture. 

29 tmp_path: Pytest temporary directory. 

30 """ 

31 base_tsconfig = tmp_path / "tsconfig.json" 

32 base_tsconfig.write_text( 

33 """{ 

34 // TypeScript config with JSONC features 

35 "compilerOptions": { 

36 "strict": true, 

37 /* Custom type roots for this project */ 

38 "typeRoots": [ 

39 "./custom-types", 

40 "./node_modules/@types", 

41 ], 

42 }, 

43}""", 

44 ) 

45 

46 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

47 base_tsconfig=base_tsconfig, 

48 files=["src/App.vue"], 

49 cwd=tmp_path, 

50 ) 

51 

52 try: 

53 content = json.loads(temp_path.read_text()) 

54 # typeRoots should be resolved to absolute paths 

55 type_roots = content["compilerOptions"]["typeRoots"] 

56 assert_that(type_roots).is_length(2) 

57 assert_that(type_roots[0]).ends_with("custom-types") 

58 assert_that(type_roots[1]).ends_with("node_modules/@types") 

59 finally: 

60 temp_path.unlink(missing_ok=True) 

61 

62 

63def test_create_temp_tsconfig_no_type_roots_when_base_has_none( 

64 vue_tsc_plugin: VueTscPlugin, 

65 tmp_path: Path, 

66) -> None: 

67 """Verify no typeRoots are added when the base config has none. 

68 

69 Args: 

70 vue_tsc_plugin: Plugin instance fixture. 

71 tmp_path: Pytest temporary directory. 

72 """ 

73 base_tsconfig = tmp_path / "tsconfig.json" 

74 base_tsconfig.write_text('{"compilerOptions": {"strict": true}}') 

75 

76 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

77 base_tsconfig=base_tsconfig, 

78 files=["src/App.vue"], 

79 cwd=tmp_path, 

80 ) 

81 

82 try: 

83 content = json.loads(temp_path.read_text()) 

84 assert_that(content["compilerOptions"]).does_not_contain_key("typeRoots") 

85 finally: 

86 temp_path.unlink(missing_ok=True) 

87 

88 

89def test_create_temp_tsconfig_ignores_non_list_type_roots( 

90 vue_tsc_plugin: VueTscPlugin, 

91 tmp_path: Path, 

92) -> None: 

93 """Verify malformed typeRoots (non-list) are safely ignored. 

94 

95 Args: 

96 vue_tsc_plugin: Plugin instance fixture. 

97 tmp_path: Pytest temporary directory. 

98 """ 

99 base_tsconfig = tmp_path / "tsconfig.json" 

100 base_tsconfig.write_text( 

101 '{"compilerOptions": {"typeRoots": "not-a-list"}}', 

102 ) 

103 

104 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

105 base_tsconfig=base_tsconfig, 

106 files=["src/App.vue"], 

107 cwd=tmp_path, 

108 ) 

109 

110 try: 

111 content = json.loads(temp_path.read_text()) 

112 assert_that(content["compilerOptions"]).does_not_contain_key("typeRoots") 

113 finally: 

114 temp_path.unlink(missing_ok=True) 

115 

116 

117def test_create_temp_tsconfig_filters_non_string_type_roots( 

118 vue_tsc_plugin: VueTscPlugin, 

119 tmp_path: Path, 

120) -> None: 

121 """Verify non-string entries in typeRoots are filtered out. 

122 

123 Args: 

124 vue_tsc_plugin: Plugin instance fixture. 

125 tmp_path: Pytest temporary directory. 

126 """ 

127 base_tsconfig = tmp_path / "tsconfig.json" 

128 base_tsconfig.write_text( 

129 '{"compilerOptions": {"typeRoots": ["./valid-types", 123, null, true]}}', 

130 ) 

131 

132 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

133 base_tsconfig=base_tsconfig, 

134 files=["src/App.vue"], 

135 cwd=tmp_path, 

136 ) 

137 

138 try: 

139 content = json.loads(temp_path.read_text()) 

140 type_roots = content["compilerOptions"]["typeRoots"] 

141 assert_that(type_roots).is_length(1) 

142 assert_that(type_roots[0]).ends_with("valid-types") 

143 finally: 

144 temp_path.unlink(missing_ok=True) 

145 

146 

147def test_create_temp_tsconfig_fallback_honours_empty_typeroots( 

148 vue_tsc_plugin: VueTscPlugin, 

149 tmp_path: Path, 

150) -> None: 

151 """Verify fallback preserves explicit empty typeRoots. 

152 

153 When the base tsconfig explicitly sets ``typeRoots: []`` to disable 

154 automatic global type discovery, the fallback path must honour that 

155 intent and NOT inject the default ``node_modules/@types`` root. 

156 

157 Args: 

158 vue_tsc_plugin: Plugin instance fixture. 

159 tmp_path: Pytest temporary directory. 

160 """ 

161 import tempfile 

162 from typing import Any 

163 from unittest.mock import patch 

164 

165 base_tsconfig = tmp_path / "tsconfig.json" 

166 base_tsconfig.write_text( 

167 json.dumps( 

168 { 

169 "compilerOptions": { 

170 "typeRoots": [], 

171 }, 

172 }, 

173 ), 

174 ) 

175 

176 original_mkstemp = tempfile.mkstemp 

177 call_count = 0 

178 

179 def mock_mkstemp(**kwargs: Any) -> tuple[int, str]: 

180 nonlocal call_count 

181 call_count += 1 

182 if call_count == 1: 

183 raise OSError("Read-only filesystem") 

184 return original_mkstemp(**kwargs) 

185 

186 with patch("tempfile.mkstemp", side_effect=mock_mkstemp): 

187 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

188 base_tsconfig=base_tsconfig, 

189 files=["src/App.vue"], 

190 cwd=tmp_path, 

191 ) 

192 

193 try: 

194 content = json.loads(temp_path.read_text()) 

195 type_roots = content["compilerOptions"]["typeRoots"] 

196 assert_that(type_roots).is_empty() 

197 finally: 

198 temp_path.unlink(missing_ok=True) 

199 

200 

201def test_create_temp_tsconfig_ignores_non_dict_compiler_options( 

202 vue_tsc_plugin: VueTscPlugin, 

203 tmp_path: Path, 

204) -> None: 

205 """Verify malformed compilerOptions (non-dict) are safely ignored. 

206 

207 Args: 

208 vue_tsc_plugin: Plugin instance fixture. 

209 tmp_path: Pytest temporary directory. 

210 """ 

211 base_tsconfig = tmp_path / "tsconfig.json" 

212 base_tsconfig.write_text('{"compilerOptions": "not-a-dict"}') 

213 

214 temp_path = vue_tsc_plugin._create_temp_tsconfig( 

215 base_tsconfig=base_tsconfig, 

216 files=["src/App.vue"], 

217 cwd=tmp_path, 

218 ) 

219 

220 try: 

221 content = json.loads(temp_path.read_text()) 

222 assert_that(content["compilerOptions"]).does_not_contain_key("typeRoots") 

223 finally: 

224 temp_path.unlink(missing_ok=True)