UNPKG

arkit

Version:

Visualises JavaScript, TypeScript and Flow codebases as meaningful and committable architecture diagrams

184 lines (153 loc) 6.22 kB
import { execSync } from "child_process"; import * as path from "path"; import * as fs from "fs"; jest.setTimeout(60000); // Normalize SVG output by replacing environment-specific values function normalizeSvg(svg: string): string { let normalized = svg; // Remove duplicate error SVG if present (PlantUML generates error SVG for empty diagrams) const closingSvgTag = "</svg>"; const firstSvgEnd = normalized.indexOf(closingSvgTag); if (firstSvgEnd !== -1) { const afterFirstSvg = normalized.substring( firstSvgEnd + closingSvgTag.length, ); if (afterFirstSvg.includes('<?xml version="1.0"')) { // Multiple SVG documents found, keep only the first one normalized = normalized.substring(0, firstSvgEnd + closingSvgTag.length); } } return ( normalized // Normalize PlantUML filter IDs (random per run) - must be before number normalization .replace(/id="f[a-z0-9]+"/g, 'id="FILTER"') .replace(/url\(#f[a-z0-9]+\)/g, "url(#FILTER)") // Strip path/polygon elements entirely (different GraphViz versions generate different geometries) .replace(/<path[^/]*\/>/g, "") .replace(/<polygon[^/]*\/>/g, "") .replace(/<line[^/]*\/>/g, "") // Normalize all numeric values (coordinates, dimensions differ between GraphViz versions) .replace(/\d+(\.\d+)?/g, "0") // Normalize PlantUML version timestamp (timezone variations) .replace(/PlantUML version [^\n]+/g, "PlantUML version NORMALIZED") // Normalize operating system name (macOS vs Linux in CI) .replace(/Operating System: [^\n<]+/g, "Operating System: NORMALIZED") // Normalize Java version .replace(/Java Version: [^\n<]+/g, "Java Version: NORMALIZED") // Normalize OS version .replace(/OS Version: [^\n<]+/g, "OS Version: NORMALIZED") // Normalize country (can be null or US) .replace(/Country: [^\n<]+/g, "Country: NORMALIZED") // Normalize machine name .replace(/Machine: [^\n<]+/g, "Machine: NORMALIZED") // Normalize memory values (can vary) .replace(/Max Memory: [\d,]+/g, "Max Memory: NORMALIZED") .replace(/Total Memory: [\d,]+/g, "Total Memory: NORMALIZED") .replace(/Free Memory: [\d,]+/g, "Free Memory: NORMALIZED") .replace(/Used Memory: [\d,]+/g, "Used Memory: NORMALIZED") ); } describe("CLI", () => { const arkit = path.resolve(__dirname, "../index.js"); const exec = (command: string): string => { return execSync(command).toString().split(__dirname).join("__dirname"); }; describe("Options", () => { test("should output help", () => { expect(exec(`${arkit} -h`)).toMatchSnapshot(); }); }); describe("Arkit", () => { describe("package.json", () => { const svgPath = path.resolve(__dirname, "../dist/arkit.svg"); const pngPath = path.resolve(__dirname, "../dist/arkit.png"); beforeAll(() => { if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath); if (fs.existsSync(pngPath)) fs.unlinkSync(pngPath); exec(`npm run architecture`); }); test("should generate correct png", () => { const stat = fs.statSync(pngPath); // Verify file exists and has reasonable size (not empty, not too large) expect(stat.size).toBeGreaterThan(1000); // At least 1KB expect(stat.size).toBeLessThan(200000); // Less than 200KB // Verify it's a valid PNG by checking the header const buffer = fs.readFileSync(pngPath); const pngHeader = Buffer.from([ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ]); expect(buffer.subarray(0, 8).equals(pngHeader)).toBe(true); }); test("should generate correct svg", () => { const svg = fs.readFileSync(svgPath).toString(); expect(normalizeSvg(svg)).toMatchSnapshot(); }); }); }); describe("Sample", () => { describe("no args", () => { const dir = path.resolve(__dirname, "./sample"); const pumlPath = path.resolve(dir, "./docs/architecture.puml"); const svgPath = path.resolve(dir, "./docs/architecture.svg"); beforeAll(() => { if (fs.existsSync(pumlPath)) fs.unlinkSync(pumlPath); if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath); process.chdir(dir); exec(arkit); }); test("should generate correct puml", () => { expect(fs.readFileSync(pumlPath).toString()).toMatchSnapshot(); }); test("should generate correct svg", () => { const svg = fs.readFileSync(svgPath).toString(); expect(normalizeSvg(svg)).toMatchSnapshot(); }); }); }); describe("Angular2 Todo", () => { describe("no args", () => { test("should output correct svg", () => { const dir = path.resolve(__dirname, "./angular2_es2015"); process.chdir(dir); expect(exec(arkit)).toMatchSnapshot(); }); }); describe("exclude and puml", () => { test("should output correct puml", () => { const dir = path.resolve(__dirname, "./angular2_es2015"); process.chdir(dir); expect( exec(`${arkit} -o puml -e "app/components/**"`), ).toMatchSnapshot(); }); }); }); describe("Express", () => { describe("no args", () => { test("should output correct svg", () => { const dir = path.resolve(__dirname, "./express"); const svgPath = path.resolve(dir, "./express.svg"); if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath); process.chdir(dir); exec(arkit); expect( normalizeSvg(fs.readFileSync(svgPath).toString()), ).toMatchSnapshot(); }); }); }); describe("ReactDOM", () => { describe("config path", () => { test("should output correct svg", () => { const dir = path.resolve(__dirname, "./react-dom"); const svgPath = path.resolve(dir, "./arkit.svg"); if (fs.existsSync(svgPath)) fs.unlinkSync(svgPath); process.chdir(dir); exec(`${arkit} -c react-arkit.json`); expect( normalizeSvg(fs.readFileSync(svgPath).toString()), ).toMatchSnapshot(); }); }); }); });