aboutsummaryrefslogtreecommitdiff
path: root/test/parser.test.js
diff options
context:
space:
mode:
authorRuben Beltran del Rio <jj@r.bdr.sh>2025-12-12 01:02:45 +0100
committerRuben Beltran del Rio <jj@r.bdr.sh>2025-12-12 12:30:14 +0100
commit68a999ed21907ef8f14c1aa0bf760d3c9b4b4c8c (patch)
treea9cb57d999005c2b0fc7c94f45b89aacea23a95b /test/parser.test.js
Add parsing
Diffstat (limited to 'test/parser.test.js')
-rw-r--r--test/parser.test.js464
1 files changed, 464 insertions, 0 deletions
diff --git a/test/parser.test.js b/test/parser.test.js
new file mode 100644
index 0000000..055e756
--- /dev/null
+++ b/test/parser.test.js
@@ -0,0 +1,464 @@
+import { describe, it } from "node:test";
+import assert from "node:assert";
+import { parse } from "../src/index.js";
+
+describe("wmap parser", () => {
+ describe("components", () => {
+ it("should parse a basic component", async () => {
+ const source = "Tea (0.9, 0.5)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "component");
+ assert.strictEqual(result.entities[0].label, "Tea");
+ assert.deepStrictEqual(result.entities[0].coordinates, [0.9, 0.5]);
+ assert.strictEqual(result.entities[0].shape, null);
+ });
+
+ it("should parse a component with shape", async () => {
+ const source = "Database (0.3, 0.8) [Square]";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "component");
+ assert.strictEqual(result.entities[0].label, "Database");
+ assert.deepStrictEqual(result.entities[0].coordinates, [0.3, 0.8]);
+ assert.strictEqual(result.entities[0].shape, "square");
+ });
+
+ it("should parse component with x shape", async () => {
+ const source = "API (0.5, 0.6) [x]";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].shape, "x");
+ });
+
+ it("should parse component with triangle shape", async () => {
+ const source = "Service (0.4, 0.7) [Triangle]";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].shape, "triangle");
+ });
+
+ it("should parse component with circle shape", async () => {
+ const source = "Process (0.2, 0.9) [CIRCLE]";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].shape, "circle");
+ });
+
+ it("should handle decimal numbers", async () => {
+ const source = "Item (.5, .123)";
+ const result = await parse(source);
+
+ assert.deepStrictEqual(
+ result.entities[0].coordinates,
+ [0.5, 0.123],
+ );
+ });
+
+ it("should handle integers as coordinates", async () => {
+ const source = "Item (1, 0)";
+ const result = await parse(source);
+
+ assert.deepStrictEqual(result.entities[0].coordinates, [1, 0]);
+ });
+
+ it("should preserve case in component labels", async () => {
+ const source = "MySpecialTea (0.5, 0.5)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].label, "MySpecialTea");
+ });
+
+ it("should handle component labels with spaces", async () => {
+ const source = "Cup of Tea (0.5, 0.5)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].label, "Cup of Tea");
+ });
+
+ it("should handle whitespace around tokens", async () => {
+ const source = " Tea ( 0.9 , 0.5 ) [ Square ] ";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].label, "Tea");
+ assert.deepStrictEqual(result.entities[0].coordinates, [0.9, 0.5]);
+ assert.strictEqual(result.entities[0].shape, "square");
+ });
+ });
+
+ describe("dependencies (edges)", () => {
+ it("should parse undirected dependency", async () => {
+ const source = "Tea -- Cup";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "dependency");
+ assert.strictEqual(result.entities[0].from, "Tea");
+ assert.strictEqual(result.entities[0].to, "Cup");
+ assert.strictEqual(result.entities[0].isDirected, false);
+ });
+
+ it("should parse directed dependency", async () => {
+ const source = "Tea -> Cup";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].type, "dependency");
+ assert.strictEqual(result.entities[0].from, "Tea");
+ assert.strictEqual(result.entities[0].to, "Cup");
+ assert.strictEqual(result.entities[0].isDirected, true);
+ });
+
+ it("should preserve case in dependency labels", async () => {
+ const source = "MyTea -> YourCup";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].from, "MyTea");
+ assert.strictEqual(result.entities[0].to, "YourCup");
+ });
+
+ it("should ignore whitespace in dependencies", async () => {
+ const source = " Tea -- Cup ";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].from, "Tea");
+ assert.strictEqual(result.entities[0].to, "Cup");
+ });
+ });
+
+ describe("notes", () => {
+ it("should parse a note", async () => {
+ const source = "[Note] (0.5, 0.5) This is a note";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "note");
+ assert.deepStrictEqual(result.entities[0].coordinates, [0.5, 0.5]);
+ assert.strictEqual(result.entities[0].text, "This is a note");
+ });
+
+ it("should handle case insensitive note keyword", async () => {
+ const source = "[note] (0.5, 0.5) Text";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].type, "note");
+ });
+
+ it("should preserve case in note text", async () => {
+ const source = "[NOTE] (0.5, 0.5) Important NOTE";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].text, "Important NOTE");
+ });
+
+ it("should handle empty note text", async () => {
+ const source = "[Note] (0.5, 0.5) ";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].text, "");
+ });
+ });
+
+ describe("stages", () => {
+ it("should parse stage I", async () => {
+ const source = "[I] 0.25";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "stage");
+ assert.strictEqual(result.entities[0].number, "i");
+ assert.strictEqual(result.entities[0].value, 0.25);
+ });
+
+ it("should parse stage II", async () => {
+ const source = "[ii] 0.5";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].number, "ii");
+ assert.strictEqual(result.entities[0].value, 0.5);
+ });
+
+ it("should parse stage III", async () => {
+ const source = "[III] 0.75";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].number, "iii");
+ assert.strictEqual(result.entities[0].value, 0.75);
+ });
+
+ it("should parse stage IV", async () => {
+ const source = "[Iv] 1.0";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].number, "iv");
+ assert.strictEqual(result.entities[0].value, 1.0);
+ });
+
+ it("should handle decimal stage values", async () => {
+ const source = "[I] .333";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].value, 0.333);
+ });
+ });
+
+ describe("groups", () => {
+ it("should parse a group with single component", async () => {
+ const source = "[Group] Tea";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "group");
+ assert.deepStrictEqual(result.entities[0].vertices, ["Tea"]);
+ });
+
+ it("should parse a group with multiple components", async () => {
+ const source = "[Group] Tea, Cup, Kettle";
+ const result = await parse(source);
+
+ assert.deepStrictEqual(result.entities[0].vertices, [
+ "Tea",
+ "Cup",
+ "Kettle",
+ ]);
+ });
+
+ it("should handle case insensitive group keyword", async () => {
+ const source = "[group] Tea, Cup";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].type, "group");
+ });
+
+ it("should preserve case in group component labels", async () => {
+ const source = "[GROUP] MyTea, YourCup";
+ const result = await parse(source);
+
+ assert.deepStrictEqual(result.entities[0].vertices, [
+ "MyTea",
+ "YourCup",
+ ]);
+ });
+
+ it("should handle whitespace around commas", async () => {
+ const source = "[Group] Tea , Cup , Kettle";
+ const result = await parse(source);
+
+ assert.deepStrictEqual(result.entities[0].vertices, [
+ "Tea",
+ "Cup",
+ "Kettle",
+ ]);
+ });
+ });
+
+ describe("inertia", () => {
+ it("should parse inertia", async () => {
+ const source = "[Inertia] Tea";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "inertia");
+ assert.strictEqual(result.entities[0].vertex, "Tea");
+ });
+
+ it("should handle case insensitive inertia keyword", async () => {
+ const source = "[inertia] Tea";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].type, "inertia");
+ });
+
+ it("should preserve case in inertia vertex label", async () => {
+ const source = "[INERTIA] MyTea";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].vertex, "MyTea");
+ });
+ });
+
+ describe("evolution", () => {
+ it("should parse evolution with positive sign", async () => {
+ const source = "[Evolution] Tea + 0.5";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 1);
+ assert.strictEqual(result.entities[0].type, "evolution");
+ assert.strictEqual(result.entities[0].vertex, "Tea");
+ assert.strictEqual(result.entities[0].isPositive, true);
+ assert.strictEqual(result.entities[0].value, 0.5);
+ });
+
+ it("should parse evolution with negative sign", async () => {
+ const source = "[Evolution] Tea - 0.3";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].isPositive, false);
+ assert.strictEqual(result.entities[0].value, 0.3);
+ });
+
+ it("should handle case insensitive evolution keyword", async () => {
+ const source = "[evolution] Tea + 0.5";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].type, "evolution");
+ });
+
+ it("should preserve case in evolution vertex label", async () => {
+ const source = "[EVOLUTION] MyTea - 0.2";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].vertex, "MyTea");
+ });
+
+ it("should handle decimal evolution values", async () => {
+ const source = "[Evolution] Tea + .75";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities[0].value, 0.75);
+ });
+ });
+
+ describe("multiple entities", () => {
+ it("should parse multiple entities", async () => {
+ const source = `Tea (0.9, 0.5)
+Cup (0.8, 0.4)
+Tea -> Cup`;
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 3);
+ assert.strictEqual(result.entities[0].type, "component");
+ assert.strictEqual(result.entities[1].type, "component");
+ assert.strictEqual(result.entities[2].type, "dependency");
+ });
+
+ it("should handle different line endings (LF)", async () => {
+ const source = "Tea (0.9, 0.5)\nCup (0.8, 0.4)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 2);
+ });
+
+ it("should handle different line endings (CRLF)", async () => {
+ const source = "Tea (0.9, 0.5)\r\nCup (0.8, 0.4)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 2);
+ });
+
+ it("should handle different line endings (CR)", async () => {
+ const source = "Tea (0.9, 0.5)\rCup (0.8, 0.4)";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 2);
+ });
+
+ it("should handle empty lines", async () => {
+ const source = `Tea (0.9, 0.5)
+
+Cup (0.8, 0.4)`;
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 2);
+ });
+
+ it("should ignore invalid lines", async () => {
+ const source = `Tea (0.9, 0.5)
+This is invalid
+Cup (0.8, 0.4)
+Another invalid line
+Tea -> Cup`;
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 3);
+ assert.strictEqual(result.entities[0].label, "Tea");
+ assert.strictEqual(result.entities[1].label, "Cup");
+ assert.strictEqual(result.entities[2].type, "dependency");
+ });
+ });
+
+ describe("edge cases", () => {
+ it("should handle empty input", async () => {
+ const source = "";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 0);
+ });
+
+ it("should handle only whitespace", async () => {
+ const source = " \n \n ";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 0);
+ });
+
+ it("should handle only invalid lines", async () => {
+ const source = "invalid\nstill invalid";
+ const result = await parse(source);
+
+ assert.strictEqual(result.entities.length, 0);
+ });
+
+ it("should handle complex component labels", async () => {
+ const source = "My Super Complex Label 123 (0.5, 0.5)";
+ const result = await parse(source);
+
+ assert.strictEqual(
+ result.entities[0].label,
+ "My Super Complex Label 123",
+ );
+ });
+ });
+
+ describe("comprehensive example", () => {
+ it("should parse a complete wardley map", async () => {
+ const source = `[I] 0.25
+[II] 0.5
+[III] 0.75
+[IV] 1.0
+
+Tea (0.9, 0.5) [Circle]
+Cup (0.8, 0.4)
+Kettle (0.7, 0.3) [Square]
+Hot Water (0.6, 0.2)
+
+Tea -> Cup
+Cup -> Kettle
+Kettle -> Hot Water
+
+[Note] (0.5, 0.5) This is our tea supply chain
+
+[Group] Tea, Cup, Kettle
+[Inertia] Kettle
+[Evolution] Tea + 0.1`;
+
+ const result = await parse(source);
+
+ // 4 stages + 4 components + 3 dependencies + 1 note + 1 group + 1 inertia + 1 evolution = 15
+ assert.strictEqual(result.entities.length, 15);
+
+ assert.strictEqual(result.entities[0].type, "stage");
+ assert.strictEqual(result.entities[1].type, "stage");
+ assert.strictEqual(result.entities[2].type, "stage");
+ assert.strictEqual(result.entities[3].type, "stage");
+
+ assert.strictEqual(result.entities[4].type, "component");
+ assert.strictEqual(result.entities[4].label, "Tea");
+ assert.strictEqual(result.entities[4].shape, "circle");
+
+ assert.strictEqual(result.entities[8].type, "dependency");
+ assert.strictEqual(result.entities[8].from, "Tea");
+ assert.strictEqual(result.entities[8].to, "Cup");
+
+ assert.strictEqual(result.entities[11].type, "note");
+
+ assert.strictEqual(result.entities[12].type, "group");
+ assert.strictEqual(result.entities[12].vertices.length, 3);
+
+ assert.strictEqual(result.entities[13].type, "inertia");
+
+ assert.strictEqual(result.entities[14].type, "evolution");
+ });
+ });
+});