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, "circle"); }); 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].components, ["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].components, [ "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].components, [ "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].components, [ "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].component, "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 component label", async () => { const source = "[INERTIA] MyTea"; const result = await parse(source); assert.strictEqual(result.entities[0].component, "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].component, "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 component label", async () => { const source = "[EVOLUTION] MyTea - 0.2"; const result = await parse(source); assert.strictEqual(result.entities[0].component, "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].components.length, 3); assert.strictEqual(result.entities[13].type, "inertia"); assert.strictEqual(result.entities[14].type, "evolution"); }); }); });