diff options
Diffstat (limited to 'test/parser.test.js')
| -rw-r--r-- | test/parser.test.js | 464 |
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"); + }); + }); +}); |