Skip to content

Commit b44cb58

Browse files
authored
Add integration tests for all formatter options (#5)
Integration tests that verify XML formatting by comparing input files against expected output files for each tool option: Test cases: - Default options (id, layout_width, layout_height first) - Custom indentation (--indention) - Custom attribute indentation (--attribute-indention) - Custom attribute order (--attribute-order) - Alphabetical attribute sort (--attribute-sort) - Custom namespace order (--namespace-order) - Alphabetical namespace sort (--namespace-sort) - Combined options (multiple flags together) - Zero indentation - Valid XML output verification - Nested element structure verification
1 parent 9c6bc0e commit b44cb58

18 files changed

Lines changed: 464 additions & 0 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
<artifactId>jdom</artifactId>
3030
<version>1.1.3</version>
3131
</dependency>
32+
<dependency>
33+
<groupId>org.junit.jupiter</groupId>
34+
<artifactId>junit-jupiter</artifactId>
35+
<version>5.10.2</version>
36+
<scope>test</scope>
37+
</dependency>
3238
</dependencies>
3339

3440
<build>
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package com.bytehamster.androidxmlformatter;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import org.jdom.Document;
6+
import org.jdom.input.SAXBuilder;
7+
import org.junit.jupiter.api.DisplayName;
8+
import org.junit.jupiter.api.Test;
9+
10+
import java.io.ByteArrayOutputStream;
11+
import java.io.InputStream;
12+
import java.io.StringWriter;
13+
import java.nio.charset.StandardCharsets;
14+
15+
/**
16+
* Integration tests that verify XML formatting with various options by comparing input XML files
17+
* against expected output XML files.
18+
*/
19+
class IntegrationTest {
20+
21+
private static final String INTEGRATION_DIR = "/integration/";
22+
private static final String INPUT_SUFFIX = "_input.xml";
23+
private static final String EXPECTED_SUFFIX = "_expected.xml";
24+
25+
private String loadResource(String path) throws Exception {
26+
try (InputStream is = getClass().getResourceAsStream(path)) {
27+
if (is == null) {
28+
throw new IllegalArgumentException("Resource not found: " + path);
29+
}
30+
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
31+
int bytesRead;
32+
byte[] data = new byte[1024];
33+
while ((bytesRead = is.read(data, 0, data.length)) != -1) {
34+
buffer.write(data, 0, bytesRead);
35+
}
36+
return new String(buffer.toByteArray(), StandardCharsets.UTF_8);
37+
}
38+
}
39+
40+
private Document parseResource(String path) throws Exception {
41+
try (InputStream is = getClass().getResourceAsStream(path)) {
42+
if (is == null) {
43+
throw new IllegalArgumentException("Resource not found: " + path);
44+
}
45+
return new SAXBuilder().build(is);
46+
}
47+
}
48+
49+
private String formatDocument(AndroidXmlOutputter outputter, Document doc) throws Exception {
50+
StringWriter writer = new StringWriter();
51+
outputter.output(doc, writer);
52+
return writer.toString();
53+
}
54+
55+
private void assertFormattedOutputMatches(
56+
String testName,
57+
int indention,
58+
int attributeIndention,
59+
String[] namespaceOrder,
60+
String[] attributeOrder,
61+
boolean attributeSort,
62+
boolean namespaceSort)
63+
throws Exception {
64+
65+
Document inputDoc = parseResource(INTEGRATION_DIR + testName + INPUT_SUFFIX);
66+
String expected = loadResource(INTEGRATION_DIR + testName + EXPECTED_SUFFIX);
67+
68+
AndroidXmlOutputter outputter = new AndroidXmlOutputter(
69+
indention,
70+
attributeIndention,
71+
namespaceOrder,
72+
attributeOrder,
73+
attributeSort,
74+
namespaceSort);
75+
76+
String actual = formatDocument(outputter, inputDoc);
77+
78+
// Normalize line endings for comparison
79+
expected = expected.replace("\r\n", "\n").trim();
80+
actual = actual.replace("\r\n", "\n").trim();
81+
82+
assertEquals(expected, actual, "Formatted output should match expected for: " + testName);
83+
}
84+
85+
// === Default Options Test ===
86+
87+
@Test
88+
@DisplayName("Default options: id, layout_width, layout_height first with 4-space indentation")
89+
void testDefaultOptions() throws Exception {
90+
assertFormattedOutputMatches(
91+
"default_options",
92+
4, // indention
93+
4, // attribute indention
94+
new String[] { "android" }, // namespace order
95+
new String[] { "id", "layout_width", "layout_height" }, // attribute order
96+
false, // attribute sort
97+
false // namespace sort
98+
);
99+
}
100+
101+
// === Custom Indentation Test ===
102+
103+
@Test
104+
@DisplayName("Custom indentation: 2 spaces instead of 4")
105+
void testCustomIndention() throws Exception {
106+
assertFormattedOutputMatches(
107+
"custom_indention",
108+
2, // indention
109+
2, // attribute indention
110+
new String[] { "android" }, // namespace order
111+
new String[] { "id", "layout_width", "layout_height" }, // attribute order
112+
false, // attribute sort
113+
false // namespace sort
114+
);
115+
}
116+
117+
// === Custom Attribute Indentation Test ===
118+
119+
@Test
120+
@DisplayName("Custom attribute indentation: 8 spaces for attributes")
121+
void testCustomAttributeIndention() throws Exception {
122+
assertFormattedOutputMatches(
123+
"custom_attribute_indention",
124+
4, // indention
125+
8, // attribute indention
126+
new String[] { "android" }, // namespace order
127+
new String[] { "id", "layout_width", "layout_height" }, // attribute order
128+
false, // attribute sort
129+
false // namespace sort
130+
);
131+
}
132+
133+
// === Custom Attribute Order Test ===
134+
135+
@Test
136+
@DisplayName("Custom attribute order: text, background first")
137+
void testCustomAttributeOrder() throws Exception {
138+
assertFormattedOutputMatches(
139+
"custom_attribute_order",
140+
4, // indention
141+
4, // attribute indention
142+
new String[] { "android" }, // namespace order
143+
new String[] { "text", "background" }, // attribute order
144+
false, // attribute sort
145+
false // namespace sort
146+
);
147+
}
148+
149+
// === Alphabetical Attribute Sort Test ===
150+
151+
@Test
152+
@DisplayName("Attribute sort: alphabetical ordering of attributes")
153+
void testAttributeSort() throws Exception {
154+
assertFormattedOutputMatches(
155+
"attribute_sort",
156+
4, // indention
157+
4, // attribute indention
158+
new String[] { "android" }, // namespace order
159+
new String[] {}, // attribute order (empty for pure alphabetical)
160+
true, // attribute sort
161+
false // namespace sort
162+
);
163+
}
164+
165+
// === Custom Namespace Order Test ===
166+
167+
@Test
168+
@DisplayName("Custom namespace order: tools, app, android")
169+
void testCustomNamespaceOrder() throws Exception {
170+
assertFormattedOutputMatches(
171+
"custom_namespace_order",
172+
4, // indention
173+
4, // attribute indention
174+
new String[] { "tools", "app", "android" }, // namespace order
175+
new String[] { "id", "layout_width", "layout_height" }, // attribute order
176+
false, // attribute sort
177+
false // namespace sort
178+
);
179+
}
180+
181+
// === Alphabetical Namespace Sort Test ===
182+
183+
@Test
184+
@DisplayName("Namespace sort: alphabetical ordering of namespaces")
185+
void testNamespaceSort() throws Exception {
186+
assertFormattedOutputMatches(
187+
"namespace_sort",
188+
4, // indention
189+
4, // attribute indention
190+
new String[] {}, // namespace order (empty for pure alphabetical)
191+
new String[] { "id", "layout_width", "layout_height" }, // attribute order
192+
false, // attribute sort
193+
true // namespace sort
194+
);
195+
}
196+
197+
// === Combined Options Test ===
198+
199+
@Test
200+
@DisplayName("Combined options: 2-space indent, 6-space attr indent, both sorts enabled")
201+
void testCombinedOptions() throws Exception {
202+
assertFormattedOutputMatches(
203+
"combined_options",
204+
2, // indention
205+
6, // attribute indention
206+
new String[] {}, // namespace order (empty for alphabetical)
207+
new String[] {}, // attribute order (empty for alphabetical)
208+
true, // attribute sort
209+
true // namespace sort
210+
);
211+
}
212+
213+
// === Additional Integration Tests ===
214+
215+
@Test
216+
@DisplayName("Zero indentation: no indentation for elements or attributes")
217+
void testZeroIndentation() throws Exception {
218+
Document inputDoc = parseResource(INTEGRATION_DIR + "default_options" + INPUT_SUFFIX);
219+
220+
AndroidXmlOutputter outputter = new AndroidXmlOutputter(
221+
0,
222+
0,
223+
new String[] { "android" },
224+
new String[] { "id", "layout_width", "layout_height" },
225+
false,
226+
false);
227+
228+
String result = formatDocument(outputter, inputDoc);
229+
230+
// With zero indentation, child elements should not be indented
231+
assertTrue(result.contains("<Button"), "Should contain Button element");
232+
assertTrue(result.contains("<TextView"), "Should contain TextView element");
233+
assertFalse(
234+
result.contains("\n <Button"),
235+
"Button should not be indented with 4 spaces when indentation is 0");
236+
}
237+
238+
@Test
239+
@DisplayName("Verify output is valid XML that can be parsed")
240+
void testOutputIsValidXml() throws Exception {
241+
Document inputDoc = parseResource(INTEGRATION_DIR + "default_options" + INPUT_SUFFIX);
242+
243+
AndroidXmlOutputter outputter = new AndroidXmlOutputter(
244+
4,
245+
4,
246+
new String[] { "android" },
247+
new String[] { "id", "layout_width", "layout_height" },
248+
false,
249+
false);
250+
251+
String result = formatDocument(outputter, inputDoc);
252+
253+
// Parse the output to verify it's valid XML
254+
SAXBuilder builder = new SAXBuilder();
255+
Document parsedDoc = builder.build(new java.io.StringReader(result));
256+
257+
assertNotNull(parsedDoc, "Output should be parseable as XML");
258+
assertEquals(
259+
"LinearLayout",
260+
parsedDoc.getRootElement().getName(),
261+
"Root element should be LinearLayout");
262+
}
263+
264+
@Test
265+
@DisplayName("Nested elements maintain correct structure")
266+
void testNestedElementsStructure() throws Exception {
267+
Document inputDoc = parseResource(INTEGRATION_DIR + "default_options" + INPUT_SUFFIX);
268+
269+
AndroidXmlOutputter outputter = new AndroidXmlOutputter(
270+
4,
271+
4,
272+
new String[] { "android" },
273+
new String[] { "id", "layout_width", "layout_height" },
274+
false,
275+
false);
276+
277+
String result = formatDocument(outputter, inputDoc);
278+
279+
// Verify nested structure
280+
assertTrue(result.contains("<LinearLayout"), "Should contain LinearLayout");
281+
assertTrue(result.contains("</LinearLayout>"), "Should have closing LinearLayout tag");
282+
assertTrue(result.contains("<Button"), "Should contain Button");
283+
assertTrue(result.contains("<TextView"), "Should contain TextView");
284+
285+
// Verify proper nesting (Button and TextView are inside LinearLayout)
286+
int linearLayoutStart = result.indexOf("<LinearLayout");
287+
int linearLayoutEnd = result.indexOf("</LinearLayout>");
288+
int buttonPos = result.indexOf("<Button");
289+
int textViewPos = result.indexOf("<TextView");
290+
291+
assertTrue(
292+
buttonPos > linearLayoutStart && buttonPos < linearLayoutEnd,
293+
"Button should be inside LinearLayout");
294+
assertTrue(
295+
textViewPos > linearLayoutStart && textViewPos < linearLayoutEnd,
296+
"TextView should be inside LinearLayout");
297+
}
298+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<View
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:alpha="0.5"
5+
android:background="#FFF"
6+
android:elevation="4dp"
7+
android:id="@+id/view"
8+
android:zIndex="1" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<View xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:zIndex="1"
4+
android:alpha="0.5"
5+
android:background="#FFF"
6+
android:elevation="4dp"
7+
android:id="@+id/view" />
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<View
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
xmlns:app="http://schemas.android.com/apk/res-auto"
5+
android:alpha="0.5"
6+
android:id="@+id/view"
7+
android:zIndex="1"
8+
app:customA="a"
9+
app:customZ="z" />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<View xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:zIndex="1"
5+
android:alpha="0.5"
6+
android:id="@+id/view"
7+
app:customZ="z"
8+
app:customA="a" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Button
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:id="@+id/btn"
5+
android:layout_width="wrap_content"
6+
android:layout_height="wrap_content"
7+
android:text="Click" />
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Button xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:text="Click"
4+
android:id="@+id/btn"
5+
android:layout_width="wrap_content"
6+
android:layout_height="wrap_content" />
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Button
3+
xmlns:android="http://schemas.android.com/apk/res/android"
4+
android:text="Click"
5+
android:background="#FFFFFF"
6+
android:id="@+id/btn"
7+
android:layout_width="wrap_content"
8+
android:layout_height="wrap_content" />
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Button xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:id="@+id/btn"
4+
android:layout_width="wrap_content"
5+
android:layout_height="wrap_content"
6+
android:text="Click"
7+
android:background="#FFFFFF" />

0 commit comments

Comments
 (0)