Coverage for tests / unit / utils / test_jsonc.py: 100%
65 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unit tests for lintro.utils.jsonc module."""
3from __future__ import annotations
5import json
7import pytest
8from assertpy import assert_that
10from lintro.utils.jsonc import load_jsonc, strip_jsonc_comments, strip_trailing_commas
12# =============================================================================
13# Tests for strip_jsonc_comments
14# =============================================================================
17def test_strip_jsonc_comments_no_comments() -> None:
18 """Return unchanged content when no comments are present."""
19 content = '{"key": "value", "num": 42}'
20 result = strip_jsonc_comments(content)
21 assert_that(result).is_equal_to(content)
24def test_strip_jsonc_comments_line_comment() -> None:
25 """Strip single-line // comments."""
26 content = '{"key": "value"} // this is a comment'
27 result = strip_jsonc_comments(content)
28 assert_that(result.strip()).is_equal_to('{"key": "value"}')
31def test_strip_jsonc_comments_block_comment() -> None:
32 """Strip /* */ block comments."""
33 content = '{"key": /* comment */ "value"}'
34 result = strip_jsonc_comments(content)
35 parsed = json.loads(result)
36 assert_that(parsed["key"]).is_equal_to("value")
39def test_strip_jsonc_comments_preserves_strings() -> None:
40 """Preserve // and /* patterns inside string values."""
41 content = '{"url": "https://example.com"}'
42 result = strip_jsonc_comments(content)
43 parsed = json.loads(result)
44 assert_that(parsed["url"]).is_equal_to("https://example.com")
47# =============================================================================
48# Tests for strip_trailing_commas
49# =============================================================================
52def test_strip_trailing_commas_removes_trailing_comma_before_brace() -> None:
53 """Remove trailing comma before closing brace."""
54 content = '{"a": 1, "b": 2,}'
55 result = strip_trailing_commas(content)
56 assert_that(result).is_equal_to('{"a": 1, "b": 2}')
59def test_strip_trailing_commas_removes_trailing_comma_before_bracket() -> None:
60 """Remove trailing comma before closing bracket."""
61 content = '["a", "b",]'
62 result = strip_trailing_commas(content)
63 assert_that(result).is_equal_to('["a", "b"]')
66def test_strip_trailing_commas_handles_whitespace() -> None:
67 """Remove trailing comma with whitespace before closing."""
68 content = '{"a": 1,\n}'
69 result = strip_trailing_commas(content)
70 assert_that(result).is_equal_to('{"a": 1\n}')
73def test_strip_trailing_commas_no_trailing_comma() -> None:
74 """Return unchanged content when no trailing commas."""
75 content = '{"a": 1, "b": 2}'
76 result = strip_trailing_commas(content)
77 assert_that(result).is_equal_to(content)
80# =============================================================================
81# Tests for load_jsonc
82# =============================================================================
85def test_load_jsonc_plain_json() -> None:
86 """Parse plain JSON without comments or trailing commas."""
87 result = load_jsonc('{"key": "value"}')
88 assert_that(result).is_equal_to({"key": "value"})
91def test_load_jsonc_with_comments() -> None:
92 """Parse JSONC with line and block comments."""
93 content = """{
94 // A line comment
95 "name": "test",
96 /* block comment */
97 "value": 42
98}"""
99 result = load_jsonc(content)
100 assert_that(result["name"]).is_equal_to("test")
101 assert_that(result["value"]).is_equal_to(42)
104def test_load_jsonc_with_trailing_commas() -> None:
105 """Parse JSONC with trailing commas."""
106 content = '{"a": 1, "b": [1, 2, 3,],}'
107 result = load_jsonc(content)
108 assert_that(result["a"]).is_equal_to(1)
109 assert_that(result["b"]).is_equal_to([1, 2, 3])
112def test_load_jsonc_with_comments_and_trailing_commas() -> None:
113 """Parse JSONC with both comments and trailing commas."""
114 content = """{
115 // compiler settings
116 "compilerOptions": {
117 "strict": true, // enable strict mode
118 "typeRoots": [
119 "./custom-types",
120 "./node_modules/@types",
121 ],
122 },
123}"""
124 result = load_jsonc(content)
125 assert_that(result["compilerOptions"]["strict"]).is_true()
126 assert_that(result["compilerOptions"]["typeRoots"]).is_equal_to(
127 ["./custom-types", "./node_modules/@types"],
128 )
131def test_load_jsonc_invalid_json_raises() -> None:
132 """Raise JSONDecodeError for invalid JSON after stripping."""
133 with pytest.raises(json.JSONDecodeError):
134 load_jsonc("{invalid}")
137def test_load_jsonc_tsconfig_with_comments_and_type_roots() -> None:
138 """Parse a realistic tsconfig.json with JSONC features and typeRoots.
140 This is the primary scenario from issue #570.
141 """
142 content = """{
143 // TypeScript configuration
144 "compilerOptions": {
145 "target": "ES2020",
146 "module": "ESNext",
147 "strict": true,
148 /* Custom type roots for monorepo */
149 "typeRoots": [
150 "./types",
151 "./node_modules/@types",
152 ],
153 "outDir": "./dist",
154 },
155 "include": ["src/**/*.ts"],
156 "exclude": ["node_modules"],
157}"""
158 result = load_jsonc(content)
159 assert_that(result["compilerOptions"]["typeRoots"]).is_equal_to(
160 ["./types", "./node_modules/@types"],
161 )
162 assert_that(result["compilerOptions"]["strict"]).is_true()