Skip to content

Commit 4d4e9af

Browse files
Flyrellclaude
andcommitted
test: expand test coverage for AttributeFolder edge cases
Add comprehensive tests before refactoring: folding method 1, single-quoted attributes, custom attribute lists, valueless attributes, multiple/nested elements, empty values, whitespace around =, single-brace JSX, deeply nested braces, mixed attributes, and deeply nested JSX element trees. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 18642b0 commit 4d4e9af

2 files changed

Lines changed: 300 additions & 1 deletion

File tree

src/test/kotlin/dev.zbinski.htmlattributefolder/AttributeFolderHTMLTest.kt

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,157 @@ class AttributeFolderHTMLTest : BaseAttributeFolderTest() {
3939
setupDocument("test.html", HTML_SNIPPET)
4040
}
4141
}
42+
43+
/**
44+
* Tests for folding method 1 (name-only placeholder).
45+
*/
46+
class AttributeFolderHTMLMethod1Test : BaseAttributeFolderTest() {
47+
fun testFoldingMethod1ReplacesEntireAttribute() {
48+
setupDocument("method1.html", """<div class="a b" id="main" />""")
49+
val state = AttributeFolderState.instance
50+
state.attributes = arrayListOf("class")
51+
state.foldingMethod = 1
52+
state.collapseByDefault = true
53+
state.placeholder = "__PLACEHOLDER__"
54+
55+
val visualText = applyPluginFoldingAndRender()
56+
// Method 1 folds the entire attribute and uses the attribute name as placeholder
57+
assertContains(visualText, "class")
58+
// The value "a b" should be hidden
59+
kotlin.test.assertFalse(
60+
visualText.contains("""class="a b""""),
61+
"Expected the full attribute to be folded, but found it in: $visualText"
62+
)
63+
// id should remain untouched
64+
assertContains(visualText, """id="main"""")
65+
}
66+
67+
fun testFoldingMethod1UncollapsedShowsFullAttribute() {
68+
setupDocument("method1u.html", """<div class="a b" />""")
69+
val state = AttributeFolderState.instance
70+
state.attributes = arrayListOf("class")
71+
state.foldingMethod = 1
72+
state.collapseByDefault = false
73+
state.placeholder = "__PLACEHOLDER__"
74+
75+
val visualText = applyPluginFoldingAndRender()
76+
assertContains(visualText, """class="a b"""")
77+
}
78+
}
79+
80+
/**
81+
* Tests for single-quoted attribute values.
82+
*/
83+
class AttributeFolderHTMLSingleQuoteTest : BaseAttributeFolderTest() {
84+
fun testFoldingSingleQuotedAttributes() {
85+
setupDocument("sq.html", """<div class='foo bar' />""")
86+
configureAttributeFolder(collapseByDefault = true)
87+
88+
val visualText = applyPluginFoldingAndRender()
89+
assertContains(visualText, """class='__PLACEHOLDER__'""")
90+
}
91+
92+
fun testFoldingSingleQuotedUncollapsed() {
93+
setupDocument("squ.html", """<div class='foo bar' />""")
94+
configureAttributeFolder(collapseByDefault = false)
95+
96+
val visualText = applyPluginFoldingAndRender()
97+
assertContains(visualText, """class='foo bar'""")
98+
}
99+
}
100+
101+
/**
102+
* Tests for custom attribute lists (e.g. data-foo).
103+
*/
104+
class AttributeFolderHTMLCustomAttributeTest : BaseAttributeFolderTest() {
105+
fun testFoldingCustomAttributeInList() {
106+
setupDocument("custom.html", """<div data-foo="bar baz" class="x" />""")
107+
configureAttributeFolder(collapseByDefault = true, arrayListOf("data-foo"))
108+
109+
val visualText = applyPluginFoldingAndRender()
110+
assertContains(visualText, """data-foo="__PLACEHOLDER__"""")
111+
// class is NOT in the fold list, so it should remain
112+
assertContains(visualText, """class="x"""")
113+
}
114+
}
115+
116+
/**
117+
* Tests for attributes without values (e.g. disabled).
118+
*/
119+
class AttributeFolderHTMLNoValueAttributeTest : BaseAttributeFolderTest() {
120+
fun testAttributeWithoutValueIsSkipped() {
121+
setupDocument("noval.html", """<input disabled class="btn" />""")
122+
configureAttributeFolder(collapseByDefault = true)
123+
124+
val visualText = applyPluginFoldingAndRender()
125+
// disabled has no =, so it should remain as-is
126+
assertContains(visualText, "disabled")
127+
// class should be folded
128+
assertContains(visualText, """class="__PLACEHOLDER__"""")
129+
}
130+
}
131+
132+
/**
133+
* Tests for multiple sibling elements.
134+
*/
135+
class AttributeFolderHTMLMultipleElementsTest : BaseAttributeFolderTest() {
136+
fun testFoldingMultipleSiblingElements() {
137+
setupDocument("multi.html", """<div class="a"><span class="b">text</span></div><div class="c" />""")
138+
configureAttributeFolder(collapseByDefault = true)
139+
140+
val visualText = applyPluginFoldingAndRender()
141+
assertContains(visualText, """class="__PLACEHOLDER__""", "first div class")
142+
// Check that all three class attributes are folded
143+
val count = Regex("""class="__PLACEHOLDER__"""").findAll(visualText).count()
144+
kotlin.test.assertEquals(3, count, "Expected 3 folded class attributes, got $count in: $visualText")
145+
}
146+
}
147+
148+
/**
149+
* Tests for nested elements.
150+
*/
151+
class AttributeFolderHTMLNestedElementsTest : BaseAttributeFolderTest() {
152+
fun testFoldingNestedElements() {
153+
setupDocument("nested.html", """<div class="outer"><span class="inner">hello</span></div>""")
154+
configureAttributeFolder(collapseByDefault = true)
155+
156+
val visualText = applyPluginFoldingAndRender()
157+
assertContains(visualText, """class="__PLACEHOLDER__""")
158+
// Both outer and inner should be folded
159+
val count = Regex("""class="__PLACEHOLDER__"""").findAll(visualText).count()
160+
kotlin.test.assertEquals(2, count, "Expected 2 folded class attributes in nested elements, got $count in: $visualText")
161+
}
162+
}
163+
164+
/**
165+
* Tests for empty attribute values.
166+
*/
167+
class AttributeFolderHTMLEmptyValueTest : BaseAttributeFolderTest() {
168+
fun testFoldingEmptyAttributeValue() {
169+
setupDocument("empty.html", """<div class="" />""")
170+
configureAttributeFolder(collapseByDefault = true)
171+
172+
val visualText = applyPluginFoldingAndRender()
173+
// Empty value: start == end, so no fold region should be created.
174+
// The attribute should remain as-is.
175+
assertContains(visualText, """class=""""")
176+
}
177+
}
178+
179+
/**
180+
* Tests for whitespace around = in attributes.
181+
*/
182+
class AttributeFolderHTMLWhitespaceAroundEqualsTest : BaseAttributeFolderTest() {
183+
fun testAttributeWithWhitespaceAroundEquals() {
184+
// Note: HTML parsers typically normalize this, so the PSI tree
185+
// may or may not preserve whitespace. We test that no crash occurs
186+
// and the output is reasonable.
187+
setupDocument("ws.html", """<div class = "foo bar" />""")
188+
configureAttributeFolder(collapseByDefault = true)
189+
190+
// Should not crash; behavior depends on parser normalization
191+
val visualText = applyPluginFoldingAndRender()
192+
// The text should at least contain div
193+
assertContains(visualText, "div")
194+
}
195+
}

src/test/kotlin/dev.zbinski.htmlattributefolder/AttributeFolderTSXTest.kt

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,149 @@ class AttributeFolderTSXTest : BaseAttributeFolderTest() {
4242
setupDocument("test.tsx", TSX_SNIPPET)
4343
skipTestIfJSXIsNotSupported()
4444
}
45-
}
45+
}
46+
47+
/**
48+
* Tests for single-brace JSX attributes like onClick={handler}.
49+
*/
50+
class AttributeFolderTSXSingleBraceTest : BaseAttributeFolderTest() {
51+
fun testFoldingSingleBraceJSXAttribute() {
52+
setupDocument("singlebrace.tsx", """
53+
export const C = () => (
54+
<button onClick={handleClick} />
55+
)
56+
""")
57+
skipTestIfJSXIsNotSupported()
58+
59+
configureAttributeFolder(collapseByDefault = true, arrayListOf("onClick"))
60+
61+
val visualText = applyPluginFoldingAndRender()
62+
assertContains(visualText, """onClick={__PLACEHOLDER__}""")
63+
}
64+
65+
fun testFoldingSingleBraceUncollapsed() {
66+
setupDocument("singlebrace_u.tsx", """
67+
export const C = () => (
68+
<button onClick={handleClick} />
69+
)
70+
""")
71+
skipTestIfJSXIsNotSupported()
72+
73+
configureAttributeFolder(collapseByDefault = false, arrayListOf("onClick"))
74+
75+
val visualText = applyPluginFoldingAndRender()
76+
assertContains(visualText, """onClick={handleClick}""")
77+
}
78+
}
79+
80+
/**
81+
* Tests for deeply nested braces in JSX.
82+
*/
83+
class AttributeFolderTSXDeeplyNestedBracesTest : BaseAttributeFolderTest() {
84+
fun testFoldingDeeplyNestedBraces() {
85+
setupDocument("deep.tsx", """
86+
export const C = () => (
87+
<div style={{ a: { b: { c: 1 } } }} />
88+
)
89+
""")
90+
skipTestIfJSXIsNotSupported()
91+
92+
configureAttributeFolder(collapseByDefault = true, arrayListOf("style"))
93+
94+
val visualText = applyPluginFoldingAndRender()
95+
// Double braces: folds inside inner braces
96+
assertContains(visualText, """style={{__PLACEHOLDER__}}""")
97+
}
98+
}
99+
100+
/**
101+
* Tests for mixed quote and brace attributes on the same element.
102+
*/
103+
class AttributeFolderTSXMixedAttributesTest : BaseAttributeFolderTest() {
104+
fun testFoldingMixedQuoteAndBraceAttributes() {
105+
setupDocument("mixed.tsx", """
106+
export const C = () => (
107+
<div className="my-class" style={{ color: "red" }} onClick={handler} />
108+
)
109+
""")
110+
skipTestIfJSXIsNotSupported()
111+
112+
configureAttributeFolder(collapseByDefault = true, arrayListOf("className", "style", "onClick"))
113+
114+
val visualText = applyPluginFoldingAndRender()
115+
assertContains(visualText, """className="__PLACEHOLDER__"""")
116+
assertContains(visualText, """style={{__PLACEHOLDER__}}""")
117+
assertContains(visualText, """onClick={__PLACEHOLDER__}""")
118+
}
119+
120+
fun testMixedAttributesUncollapsed() {
121+
setupDocument("mixed_u.tsx", """
122+
export const C = () => (
123+
<div className="my-class" style={{ color: "red" }} onClick={handler} />
124+
)
125+
""")
126+
skipTestIfJSXIsNotSupported()
127+
128+
configureAttributeFolder(collapseByDefault = false, arrayListOf("className", "style", "onClick"))
129+
130+
val visualText = applyPluginFoldingAndRender()
131+
assertContains(visualText, """className="my-class"""")
132+
assertContains(visualText, """style={{ color: "red" }}""")
133+
assertContains(visualText, """onClick={handler}""")
134+
}
135+
}
136+
137+
/**
138+
* Tests for deeply nested JSX element trees with foldable attributes at multiple levels.
139+
*/
140+
class AttributeFolderTSXDeeplyNestedElementsTest : BaseAttributeFolderTest() {
141+
fun testFoldingDeeplyNestedJSXElements() {
142+
setupDocument("deepnest.tsx", """
143+
export const C = () => (
144+
<div className="level-1">
145+
<section className="level-2" style={{ padding: "10px" }}>
146+
<article className="level-3">
147+
<span className="level-4" style={{ color: "blue" }}>text</span>
148+
</article>
149+
</section>
150+
</div>
151+
)
152+
""")
153+
skipTestIfJSXIsNotSupported()
154+
155+
configureAttributeFolder(collapseByDefault = true, arrayListOf("className", "style"))
156+
157+
val visualText = applyPluginFoldingAndRender()
158+
// All className attributes at every nesting level should be folded
159+
val classCount = Regex("""className="__PLACEHOLDER__"""").findAll(visualText).count()
160+
kotlin.test.assertEquals(4, classCount, "Expected 4 folded className attributes across nesting levels, got $classCount in: $visualText")
161+
// Both style attributes should be folded
162+
val styleCount = Regex("""style=\{\{__PLACEHOLDER__\}\}""").findAll(visualText).count()
163+
kotlin.test.assertEquals(2, styleCount, "Expected 2 folded style attributes across nesting levels, got $styleCount in: $visualText")
164+
}
165+
166+
fun testFoldingDeeplyNestedJSXElementsUncollapsed() {
167+
setupDocument("deepnest_u.tsx", """
168+
export const C = () => (
169+
<div className="level-1">
170+
<section className="level-2" style={{ padding: "10px" }}>
171+
<article className="level-3">
172+
<span className="level-4" style={{ color: "blue" }}>text</span>
173+
</article>
174+
</section>
175+
</div>
176+
)
177+
""")
178+
skipTestIfJSXIsNotSupported()
179+
180+
configureAttributeFolder(collapseByDefault = false, arrayListOf("className", "style"))
181+
182+
val visualText = applyPluginFoldingAndRender()
183+
assertContains(visualText, """className="level-1"""")
184+
assertContains(visualText, """className="level-2"""")
185+
assertContains(visualText, """className="level-3"""")
186+
assertContains(visualText, """className="level-4"""")
187+
assertContains(visualText, """style={{ padding: "10px" }}""")
188+
assertContains(visualText, """style={{ color: "blue" }}""")
189+
}
190+
}

0 commit comments

Comments
 (0)