Coverage for tests / unit / parsers / test_astro_check_parser.py: 100%

78 statements  

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

1"""Unit tests for astro-check parser.""" 

2 

3from __future__ import annotations 

4 

5from assertpy import assert_that 

6 

7from lintro.parsers.astro_check.astro_check_issue import AstroCheckIssue 

8from lintro.parsers.astro_check.astro_check_parser import parse_astro_check_output 

9 

10 

11def test_parse_astro_check_output_empty() -> None: 

12 """Handle empty output.""" 

13 assert_that(parse_astro_check_output("")).is_empty() 

14 assert_that(parse_astro_check_output(" \n\n ")).is_empty() 

15 

16 

17def test_parse_astro_check_output_single_error() -> None: 

18 """Parse a single astro check error from text output.""" 

19 output = "src/pages/index.astro:10:5 - error ts2322: Type 'string' is not assignable to type 'number'." 

20 issues = parse_astro_check_output(output) 

21 

22 assert_that(issues).is_length(1) 

23 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

24 assert_that(issues[0].line).is_equal_to(10) 

25 assert_that(issues[0].column).is_equal_to(5) 

26 assert_that(issues[0].code).is_equal_to("TS2322") 

27 assert_that(issues[0].severity).is_equal_to("error") 

28 assert_that(issues[0].message).contains("not assignable") 

29 

30 

31def test_parse_astro_check_output_multiple_errors() -> None: 

32 """Parse multiple errors from astro check output.""" 

33 output = """src/pages/index.astro:10:5 - error ts2322: Type 'string' is not assignable to type 'number'. 

34src/pages/about.astro:15:10 - error ts2339: Property 'foo' does not exist on type 'Bar'. 

35src/components/Card.astro:3:1 - warning ts6133: 'x' is declared but its value is never read.""" 

36 issues = parse_astro_check_output(output) 

37 

38 assert_that(issues).is_length(3) 

39 assert_that(issues[0].code).is_equal_to("TS2322") 

40 assert_that(issues[1].code).is_equal_to("TS2339") 

41 assert_that(issues[2].code).is_equal_to("TS6133") 

42 assert_that(issues[2].severity).is_equal_to("warning") 

43 

44 

45def test_parse_astro_check_output_tsc_style() -> None: 

46 """Parse tsc-style output format (parentheses).""" 

47 output = "src/pages/index.astro(10,5): error TS2322: Type 'string' is not assignable to type 'number'." 

48 issues = parse_astro_check_output(output) 

49 

50 assert_that(issues).is_length(1) 

51 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

52 assert_that(issues[0].line).is_equal_to(10) 

53 assert_that(issues[0].column).is_equal_to(5) 

54 assert_that(issues[0].code).is_equal_to("TS2322") 

55 

56 

57def test_parse_astro_check_output_simple_format() -> None: 

58 """Parse simple format without severity or code.""" 

59 output = ( 

60 "src/pages/index.astro:10:5 Type 'string' is not assignable to type 'number'." 

61 ) 

62 issues = parse_astro_check_output(output) 

63 

64 assert_that(issues).is_length(1) 

65 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

66 assert_that(issues[0].line).is_equal_to(10) 

67 assert_that(issues[0].column).is_equal_to(5) 

68 assert_that(issues[0].message).contains("not assignable") 

69 

70 

71def test_parse_astro_check_output_windows_paths() -> None: 

72 """Normalize Windows backslashes to forward slashes.""" 

73 output = r"src\pages\index.astro:10:5 - error ts2322: Type mismatch." 

74 issues = parse_astro_check_output(output) 

75 

76 assert_that(issues).is_length(1) 

77 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

78 

79 

80def test_parse_astro_check_output_ansi_codes() -> None: 

81 """Strip ANSI escape codes from output.""" 

82 # Simulated ANSI color codes around file path 

83 output = "\x1b[31msrc/pages/index.astro:10:5 - error ts2322: Type mismatch.\x1b[0m" 

84 issues = parse_astro_check_output(output) 

85 

86 assert_that(issues).is_length(1) 

87 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

88 

89 

90def test_parse_astro_check_output_skips_noise_lines() -> None: 

91 """Skip non-error lines like summary and progress.""" 

92 output = """Checking project... 

93Result: 2 errors 

94src/pages/index.astro:10:5 - error ts2322: Type mismatch. 

95Found 1 error in 1 file.""" 

96 issues = parse_astro_check_output(output) 

97 

98 assert_that(issues).is_length(1) 

99 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

100 

101 

102def test_parse_astro_check_output_hint_severity() -> None: 

103 """Parse hint severity level.""" 

104 output = "src/pages/index.astro:10:5 - hint ts80001: Use optional chaining." 

105 issues = parse_astro_check_output(output) 

106 

107 assert_that(issues).is_length(1) 

108 assert_that(issues[0].severity).is_equal_to("hint") 

109 

110 

111def test_astro_check_issue_type() -> None: 

112 """Verify parsed issues are AstroCheckIssue instances.""" 

113 output = "src/pages/index.astro:10:5 - error ts2322: Type error." 

114 issues = parse_astro_check_output(output) 

115 

116 assert_that(issues).is_length(1) 

117 assert_that(issues[0]).is_instance_of(AstroCheckIssue) 

118 

119 

120def test_parse_astro_check_output_skips_timestamp_lines() -> None: 

121 """Skip astro-check stderr lines with HH:MM:SS timestamp prefix. 

122 

123 astro-check emits informational messages to stderr with a timestamp 

124 prefix (e.g. "15:19:56 [content] Syncing content"). The HH:MM:SS 

125 format matches the fallback file:line:col regex, producing phantom 

126 issues on a non-existent file named after the hour. 

127 """ 

128 output = ( 

129 "15:19:56 [WARN] Missing pages directory: src/pages\n" 

130 "15:19:56 [content] Syncing content\n" 

131 "15:19:56 [content] Synced content\n" 

132 "15:19:56 [types] Generated 177ms\n" 

133 "15:19:56 [check] Getting diagnostics for Astro files in /workspace/src...\n" 

134 "Result (15 files):\n" 

135 "- 0 errors\n" 

136 "- 0 warnings\n" 

137 "- 0 hints\n" 

138 ) 

139 assert_that(parse_astro_check_output(output)).is_empty() 

140 

141 

142def test_parse_astro_check_output_skips_timestamps_keeps_real_errors() -> None: 

143 """Timestamp noise is filtered while real diagnostics are preserved.""" 

144 output = ( 

145 "16:25:27 [content] Syncing content\n" 

146 "16:25:27 [content] Synced content\n" 

147 "16:25:27 [types] Generated 836ms\n" 

148 "16:25:27 [check] Getting diagnostics for Astro files in /code...\n" 

149 "src/pages/index.astro:10:5 - error ts2322: Type 'string' is not assignable to type 'number'.\n" 

150 "Result (52 files):\n" 

151 "- 1 error\n" 

152 "- 0 warnings\n" 

153 "- 0 hints\n" 

154 ) 

155 issues = parse_astro_check_output(output) 

156 

157 assert_that(issues).is_length(1) 

158 assert_that(issues[0].file).is_equal_to("src/pages/index.astro") 

159 assert_that(issues[0].code).is_equal_to("TS2322") 

160 

161 

162def test_parse_astro_check_output_skips_docker_warn_lines() -> None: 

163 """Skip [WARN] lines from astro-check running in degraded mode in Docker.""" 

164 output = ( 

165 "15:19:56 [WARN] Missing pages directory: src/pages\n" 

166 "15:19:56 [WARN] [vite] Failed to resolve dependency: astro > cssesc, " 

167 "present in client 'optimizeDeps.include'\n" 

168 "15:19:56 [WARN] [vite] Failed to resolve dependency: astro > aria-query, " 

169 "present in client 'optimizeDeps.include'\n" 

170 "15:19:56 [WARN] [vite] Failed to resolve dependency: astro > axobject-query, " 

171 "present in client 'optimizeDeps.include'\n" 

172 ) 

173 assert_that(parse_astro_check_output(output)).is_empty()