diff --git a/src/formatting/formatters/v2-xml-formatter.ts b/src/formatting/formatters/v2-xml-formatter.ts
index 8f60585..19b1dd1 100644
--- a/src/formatting/formatters/v2-xml-formatter.ts
+++ b/src/formatting/formatters/v2-xml-formatter.ts
@@ -25,7 +25,7 @@ export class V2XmlFormatter implements XmlFormatter {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.CData;
}
-
+
// exiting CData
else if (location === Location.CData && cc === "]" && nc === "]" && nnc === ">") {
output += "]]>";
@@ -34,13 +34,13 @@ export class V2XmlFormatter implements XmlFormatter {
lastNonTextLocation = location;
location = Location.Text;
}
-
+
// entering Comment
else if (location === Location.Text && cc === "<" && nc === "!" && nnc === "-") {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.Comment;
}
-
+
// exiting Comment
else if (location === Location.Comment && cc === "-" && nc === "-" && nnc === ">") {
output += "-->";
@@ -49,20 +49,20 @@ export class V2XmlFormatter implements XmlFormatter {
lastNonTextLocation = location;
location = Location.Text;
}
-
+
// entering SpecialTag
else if (location === Location.Text && cc === "<" && (nc === "!" || nc === "?")) {
output += `${this._getIndent(options, indentLevel)}<`;
location = Location.SpecialTag;
}
-
+
// exiting SpecialTag
else if (location === Location.SpecialTag && cc === ">") {
output += `>`;
lastNonTextLocation = location;
location = Location.Text;
}
-
+
// entering StartTag.StartTagName
else if (location === Location.Text && cc === "<" && ["/", "!"].indexOf(nc) === -1) {
// if this occurs after another tag, prepend a line break
@@ -77,17 +77,20 @@ export class V2XmlFormatter implements XmlFormatter {
indentLevel++;
location = Location.StartTagName;
}
-
+
// exiting StartTag.StartTagName, enter StartTag
else if (location === Location.StartTagName && cc === " ") {
output += " ";
lastNonTextLocation = location;
location = Location.StartTag;
}
-
+
// entering StartTag.Attribute
else if (location === Location.StartTag && [" ", "/", ">"].indexOf(cc) === -1) {
- if (lastNonTextLocation === Location.AttributeValue && ((options.splitXmlnsOnFormat && xml.substr(i, 5).toLowerCase() === "xmlns") || options.splitAttributesOnFormat)) {
+ if (lastNonTextLocation === Location.AttributeValue
+ && ((options.splitXmlnsOnFormat
+ && xml.substr(i, 5).toLowerCase() === "xmlns")
+ || options.splitAttributesOnFormat)) {
output += `${options.newLine}${this._getIndent(options, indentLevel)}`;
}
@@ -95,14 +98,14 @@ export class V2XmlFormatter implements XmlFormatter {
lastNonTextLocation = location;
location = Location.Attribute;
}
-
+
// entering StartTag.Attribute.AttributeValue
else if (location === Location.Attribute && cc === "\"") {
output += "\"";
lastNonTextLocation = location;
location = Location.AttributeValue;
}
-
+
// exiting StartTag.Attribute.AttributeValue, entering StartTag
else if (location === Location.AttributeValue && cc === "\"") {
output += "\"";
@@ -118,6 +121,21 @@ export class V2XmlFormatter implements XmlFormatter {
output += `>${options.newLine}`;
}
+ // if this is an open tag followed by a line break, add an indent before the text (after the line break)
+ // TODO: there could be multiple lines of text here, so we'll need a less naive implementation at some point
+ else if (nc === "\r" || nc === "\n") {
+ output += `>${options.newLine}${this._getIndent(options, indentLevel)}`;
+
+ // fast-forward based on what type of line break was used
+ if (nc === "\r") {
+ i += 2;
+ }
+
+ else {
+ i++;
+ }
+ }
+
else {
output += ">";
}
@@ -125,31 +143,36 @@ export class V2XmlFormatter implements XmlFormatter {
lastNonTextLocation = location;
location = Location.Text;
}
-
+
// entering EndTag
else if (location === Location.Text && cc === "<" && nc === "/") {
indentLevel--;
// if the end tag immediately follows another end tag, add a line break and indent
+ // if the end tag immediately follows a line break, just add an indentation
// otherwise, this should be treated as a same-line end tag(ex. text)
if (lastNonTextLocation === Location.EndTag) {
output += `${options.newLine}${this._getIndent(options, indentLevel)}<`;
}
-
+
+ else if (pc === "\n") {
+ output += `${this._getIndent(options, indentLevel)}<`;
+ }
+
else {
output += "<";
}
location = Location.EndTag;
}
-
+
// exiting EndTag, entering Text
else if (location === Location.EndTag && cc === ">") {
output += ">";
lastNonTextLocation = location;
location = Location.Text;
}
-
+
// Text
else {
output += cc;
diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts
index d4c8a71..e5de0ab 100644
--- a/src/test/extension.test.ts
+++ b/src/test/extension.test.ts
@@ -1,17 +1,42 @@
-//
-// Note: This example test is leveraging the Mocha test framework.
-// Please refer to their documentation on https://mochajs.org/ for help.
-//
-
-// The module 'assert' provides assertion methods from node
import * as assert from "assert";
+import { FormattingOptions } from "vscode";
-// You can import and use all API from the 'vscode' module
-// as well as import your extension to test it
-import * as vscode from "vscode";
-import * as myExtension from "../extension";
+import { TestDataLoader } from "./test-utils/test-data-loader";
+
+import { XmlFormatter } from "../formatting/xml-formatter";
+import { XmlFormattingOptions } from "../formatting/xml-formatting-options";
+import { V2XmlFormatter } from "../formatting/formatters/v2-xml-formatter";
+
+describe("V2XmlFormatter", () => {
+
+ const xmlFormatter = new V2XmlFormatter();
+
+ describe("#formatXml(xml, options)", () => {
+
+ const options = {
+ editorOptions: {
+ insertSpaces: true,
+ tabSize: 4
+ },
+ newLine: "\r\n",
+ removeCommentsOnMinify: false,
+ splitAttributesOnFormat: false,
+ splitXmlnsOnFormat: true
+ };
+
+ it("should handle basic XML", () => {
+ testFormatter(xmlFormatter, options, "basic");
+ });
+
+ });
-// Defines a Mocha test suite to group tests of similar kind together
-suite("Extension Tests", () => {
- // TODO: implement tests
});
+
+function testFormatter(xmlFormatter: XmlFormatter, options: XmlFormattingOptions, fileLabel: string): void {
+ const expectedFormattedXml = TestDataLoader.load(`${fileLabel}.formatted.xml`);
+ const unformattedXml = TestDataLoader.load(`${fileLabel}.unformatted.xml`);
+
+ const actualFormattedXml = xmlFormatter.formatXml(unformattedXml, options);
+
+ assert.equal(actualFormattedXml, expectedFormattedXml, "Actual formatted XML does not match expected formatted XML.");
+}
diff --git a/src/test/index.ts b/src/test/index.ts
index edb6206..f2ca26a 100644
--- a/src/test/index.ts
+++ b/src/test/index.ts
@@ -15,7 +15,7 @@ import * as testRunner from "vscode/lib/testrunner";
// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
testRunner.configure({
- ui: "tdd", // the TDD UI is being used in extension.test.ts (suite, test, etc.)
+ ui: "bdd",
useColors: true // colored output from test results
});
diff --git a/src/test/test-data/basic.formatted.xml b/src/test/test-data/basic.formatted.xml
new file mode 100644
index 0000000..1bfeb09
--- /dev/null
+++ b/src/test/test-data/basic.formatted.xml
@@ -0,0 +1,3 @@
+
+ text
+
\ No newline at end of file
diff --git a/src/test/test-data/basic.unformatted.xml b/src/test/test-data/basic.unformatted.xml
new file mode 100644
index 0000000..cd114a6
--- /dev/null
+++ b/src/test/test-data/basic.unformatted.xml
@@ -0,0 +1 @@
+text
\ No newline at end of file
diff --git a/src/test/test-utils/test-data-loader.ts b/src/test/test-utils/test-data-loader.ts
new file mode 100644
index 0000000..79707af
--- /dev/null
+++ b/src/test/test-utils/test-data-loader.ts
@@ -0,0 +1,7 @@
+import * as fs from "fs";
+
+export class TestDataLoader {
+ static load(fileName: string): string {
+ return fs.readFileSync(`${__dirname}/../../../src/test/test-data/${fileName}`, "UTF-8");
+ }
+}