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 = "Dominoes (0.9, 0.5)"; const result = parse(source); assert.strictEqual(result.components.length, 1); assert.strictEqual(result.components[0].label, "Dominoes"); assert.deepStrictEqual( result.components[0].coordinates, [0.9, 0.5], ); assert.strictEqual(result.components[0].shape, "circle"); }); it("should parse a component with shape", async () => { const source = "Database (0.33, 0.81) [Square]"; const result = parse(source); assert.strictEqual(result.components.length, 1); assert.strictEqual(result.components[0].label, "Database"); assert.deepStrictEqual( result.components[0].coordinates, [0.33, 0.81], ); assert.strictEqual(result.components[0].shape, "square"); }); it("should parse component with x shape", async () => { const source = "API (0.5, 0.6) [x]"; const result = parse(source); assert.strictEqual(result.components[0].shape, "x"); }); it("should parse component with triangle shape", async () => { const source = "Service (0.4, 0.7) [Triangle]"; const result = parse(source); assert.strictEqual(result.components[0].shape, "triangle"); }); it("should parse component with circle shape", async () => { const source = "Process (0.2, 0.9) [CIRCLE]"; const result = parse(source); assert.strictEqual(result.components[0].shape, "circle"); }); it("should handle decimal numbers", async () => { const source = "Item (.5, .123)"; const result = parse(source); assert.deepStrictEqual( result.components[0].coordinates, [0.5, 0.123], ); }); it("should handle integers as coordinates", async () => { const source = "Item (1, 0)"; const result = parse(source); assert.deepStrictEqual(result.components[0].coordinates, [1, 0]); }); it("should preserve case in component labels", async () => { const source = "RubberGaskets (0.5, 0.5)"; const result = parse(source); assert.strictEqual(result.components[0].label, "RubberGaskets"); }); it("should handle component labels with spaces", async () => { const source = "Big Lawnmowers (0.5, 0.5)"; const result = parse(source); assert.strictEqual(result.components[0].label, "Big Lawnmowers"); }); it("should handle whitespace around tokens", async () => { const source = " Outer Space ( 0.9 , 0.5 ) [ Square ] "; const result = parse(source); assert.strictEqual(result.components[0].label, "Outer Space"); assert.deepStrictEqual( result.components[0].coordinates, [0.9, 0.5], ); assert.strictEqual(result.components[0].shape, "square"); }); }); describe("dependencies (edges)", () => { it("should parse undirected dependency", async () => { const source = "Smoke -- Fire"; const result = parse(source); assert.strictEqual(result.dependencies.length, 1); assert.strictEqual(result.dependencies[0].from, "Smoke"); assert.strictEqual(result.dependencies[0].to, "Fire"); assert.strictEqual(result.dependencies[0].isDirected, false); }); it("should parse directed dependency", async () => { const source = "Cool Thing -> Different Cool Thing"; const result = parse(source); assert.strictEqual(result.dependencies[0].from, "Cool Thing"); assert.strictEqual( result.dependencies[0].to, "Different Cool Thing", ); assert.strictEqual(result.dependencies[0].isDirected, true); }); it("should preserve case in dependency labels", async () => { const source = "SaRcAsM -> Seriousness."; const result = parse(source); assert.strictEqual(result.dependencies[0].from, "SaRcAsM"); assert.strictEqual(result.dependencies[0].to, "Seriousness."); }); it("should ignore whitespace in dependencies", async () => { const source = " loose --TIGHT"; const result = parse(source); assert.strictEqual(result.dependencies[0].from, "loose"); assert.strictEqual(result.dependencies[0].to, "TIGHT"); }); }); describe("notes", () => { it("should parse a note", async () => { const source = "[Note] (0.5, 0.5) This is a note"; const result = parse(source); assert.strictEqual(result.notes.length, 1); assert.deepStrictEqual(result.notes[0].coordinates, [0.5, 0.5]); assert.strictEqual(result.notes[0].text, "This is a note"); }); it("should handle case insensitive note keyword", async () => { const source = "[note] (0.1, 0.2) Text"; const result = parse(source); assert.strictEqual(result.notes.length, 1); }); it("should preserve case in note text", async () => { const source = "[NOTE] (0.77, 0.19) Important NOTE"; const result = parse(source); assert.strictEqual(result.notes[0].text, "Important NOTE"); }); it("should handle empty note text", async () => { const source = "[Note] (0.5, 0.5) "; const result = parse(source); assert.strictEqual(result.notes[0].text, ""); }); }); describe("stages", () => { it("should parse stage I", async () => { const source = "[I] 0.25"; const result = parse(source); assert.strictEqual(result.stages.length, 1); assert.strictEqual(result.stages[0].stage, "i"); assert.strictEqual(result.stages[0].value, 0.25); }); it("should parse stage II", async () => { const source = "[ii] 0.5"; const result = parse(source); assert.strictEqual(result.stages[0].stage, "ii"); assert.strictEqual(result.stages[0].value, 0.5); }); it("should parse stage III", async () => { const source = "[III] 0.75"; const result = parse(source); assert.strictEqual(result.stages[0].stage, "iii"); assert.strictEqual(result.stages[0].value, 0.75); }); it("should parse stage IV", async () => { const source = "[Iv] 1.0"; const result = parse(source); assert.strictEqual(result.stages[0].stage, "iv"); assert.strictEqual(result.stages[0].value, 1.0); }); it("should handle decimal stage values", async () => { const source = "[I] .333"; const result = parse(source); assert.strictEqual(result.stages[0].value, 0.333); }); }); describe("groups", () => { it("should parse a group with single component", async () => { const source = "[Group] Lonely"; const result = parse(source); assert.strictEqual(result.groups.length, 1); assert.deepStrictEqual(result.groups[0].components, ["Lonely"]); }); it("should parse a group with multiple components", async () => { const source = "[Group] Huey, Dewey, Louie"; const result = parse(source); assert.deepStrictEqual(result.groups[0].components, [ "Huey", "Dewey", "Louie", ]); }); it("should handle case insensitive group keyword", async () => { const source = "[group] Salt, Pepper"; const result = parse(source); assert.strictEqual(result.groups.length, 1); }); it("should preserve case in group component labels", async () => { const source = "[GROUP] XML, LaTeX"; const result = parse(source); assert.deepStrictEqual(result.groups[0].components, [ "XML", "LaTeX", ]); }); it("should handle whitespace around commas", async () => { const source = "[Group] sky, is large, emperor"; const result = parse(source); assert.deepStrictEqual(result.groups[0].components, [ "sky", "is large", "emperor", ]); }); }); describe("inertia", () => { it("should parse inertia", async () => { const source = "[Inertia] Print Media"; const result = parse(source); assert.strictEqual(result.inertias.length, 1); assert.strictEqual(result.inertias[0].component, "Print Media"); }); it("should handle case insensitive inertia keyword", async () => { const source = "[inertia] Yes"; const result = parse(source); assert.strictEqual(result.inertias.length, 1); }); it("should preserve case in inertia component label", async () => { const source = "[INERTIA] SpOnGeBoB"; const result = parse(source); assert.strictEqual(result.inertias[0].component, "SpOnGeBoB"); }); }); describe("evolution", () => { it("should parse evolution with positive sign", async () => { const source = "[Evolution] Nanomachines + 0.5"; const result = parse(source); assert.strictEqual(result.evolutions.length, 1); assert.strictEqual(result.evolutions[0].component, "Nanomachines"); assert.strictEqual(result.evolutions[0].value, 0.5); }); it("should parse evolution with negative sign", async () => { const source = "[Evolution] FOXDIE - 0.3"; const result = parse(source); assert.strictEqual(result.evolutions[0].value, -0.3); }); it("should handle case insensitive evolution keyword", async () => { const source = "[evolution] tamales + 0.73"; const result = parse(source); assert.strictEqual(result.evolutions.length, 1); }); it("should preserve case in evolution component label", async () => { const source = "[EVOLUTION] iMac - 0.2"; const result = parse(source); assert.strictEqual(result.evolutions[0].component, "iMac"); }); it("should handle decimal evolution values", async () => { const source = "[Evolution] Numerals + .75"; const result = parse(source); assert.strictEqual(result.evolutions[0].value, 0.75); }); }); describe("multiple entities", () => { it("should parse multiple entities", async () => { const source = `Water (0.9, 0.5) Bucket (0.8, 0.4) [x] Water -> Bucket`; const result = parse(source); assert.strictEqual(result.components.length, 2); assert.strictEqual(result.dependencies.length, 1); }); it("should handle different line endings (LF)", async () => { const source = "Penguins (0.9, 0.5)\nI Use Arch BTW (0.8, 0.4)"; const result = parse(source); assert.strictEqual(result.components.length, 2); }); it("should handle different line endings (CRLF)", async () => { const source = "Clippy (0.9, 0.5)\r\nSpace Cadet (0.8, 0.4)"; const result = parse(source); assert.strictEqual(result.components.length, 2); }); it("should handle different line endings (CR)", async () => { const source = "SE/30 (0.9, 0.5)\rPerforma (0.8, 0.4)"; const result = parse(source); assert.strictEqual(result.components.length, 2); }); it("should handle empty lines", async () => { const source = `The Void (0.9, 0.5) The Abyss (0.8, 0.4)`; const result = parse(source); assert.strictEqual(result.components.length, 2); }); it("should ignore invalid lines", async () => { const source = `Hello (0.9, 0.5) Oh No It's OK (0.8, 0.4) [ijole] NO IT ISN'T A-- B-> Hello -> It's OK`; const result = parse(source); assert.strictEqual(result.components.length, 2); assert.strictEqual(result.components[0].label, "Hello"); assert.strictEqual(result.components[1].label, "It's OK"); assert.strictEqual(result.dependencies.length, 1); }); }); describe("edge cases", () => { it("should handle empty input", async () => { const source = ""; const result = parse(source); assert.strictEqual(result.components.length, 0); assert.strictEqual(result.dependencies.length, 0); assert.strictEqual(result.notes.length, 0); assert.strictEqual(result.stages.length, 0); assert.strictEqual(result.groups.length, 0); assert.strictEqual(result.inertias.length, 0); assert.strictEqual(result.evolutions.length, 0); }); it("should handle only whitespace", async () => { const source = " \n \n "; const result = parse(source); assert.strictEqual(result.components.length, 0); }); it("should handle only invalid lines", async () => { const source = "invalid\nstill invalid"; const result = parse(source); assert.strictEqual(result.components.length, 0); }); it("should handle complex component labels", async () => { const source = "My Sü'per Complex Label 123 (0.5, 0.5)"; const result = parse(source); assert.strictEqual( result.components[0].label, "My Sü'per 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 = parse(source); // 4 stages + 4 components + 3 dependencies + 1 note + 1 group + 1 inertia + 1 evolution assert.strictEqual(result.stages.length, 4); assert.strictEqual(result.components.length, 4); assert.strictEqual(result.dependencies.length, 3); assert.strictEqual(result.notes.length, 1); assert.strictEqual(result.groups.length, 1); assert.strictEqual(result.inertias.length, 1); assert.strictEqual(result.evolutions.length, 1); assert.strictEqual(result.components[0].label, "Tea"); assert.strictEqual(result.components[0].shape, "circle"); assert.strictEqual(result.dependencies[0].from, "Tea"); assert.strictEqual(result.dependencies[0].to, "Cup"); assert.strictEqual(result.groups[0].components.length, 3); }); }); });