import test from 'node:test' import assert from 'node:assert/strict' import { USE_CASE_EXAMPLE_DSL, buildDefaultUseCaseDsl, buildUseCaseDiagram, buildUseCaseExportSvg, parseUseCaseDsl } from './useCaseDiagram.js' const shoppingDsl = `用户 浏览商品 搜索商品 查看商品详情 筛选商品 加入购物车 选择规格 修改数量 提交订单 选择收货地址 选择优惠券 评价商品 上传图片? 打分` test('parseUseCaseDsl parses screenshot-style actor, use cases, and include relations', () => { const parsed = parseUseCaseDsl(shoppingDsl) assert.equal(parsed.actors.length, 1) assert.equal(parsed.actors[0].name, '用户') assert.equal(parsed.primaryUseCases.length, 4) assert.equal(parsed.useCases.length, 13) assert.equal(parsed.relations.filter((relation) => relation.type === 'association').length, 4) assert.equal(parsed.relations.filter((relation) => relation.type === 'include').length, 8) assert.equal(parsed.relations.filter((relation) => relation.type === 'extend').length, 1) assert.equal(parsed.warnings.length, 0) assert.deepEqual( parsed.primaryUseCases.map((item) => item.name), ['浏览商品', '加入购物车', '提交订单', '评价商品'] ) }) test('parseUseCaseDsl normalizes tabs to two-space indentation', () => { const parsed = parseUseCaseDsl('用户\n浏览商品\n\t搜索商品') assert.equal(parsed.primaryUseCases.length, 1) assert.equal(parsed.useCases.find((item) => item.name === '搜索商品').parentId, 'use_case_1') assert.equal(parsed.relations.some((relation) => relation.type === 'include'), true) }) test('parseUseCaseDsl marks question-suffixed children as extend relations', () => { const parsed = parseUseCaseDsl('用户\n支付订单\n 支付失败?') assert.equal(parsed.relations.find((relation) => relation.type === 'extend').target, 'use_case_1') assert.equal(parsed.useCases.find((item) => item.name === '支付失败').optional, true) }) test('parseUseCaseDsl returns warnings for empty DSL and falls back to actor when missing', () => { const empty = parseUseCaseDsl('') assert.equal(empty.actors[0].name, '用户') assert.equal(empty.useCases.length, 0) assert.ok(empty.warnings.some((warning) => warning.includes('至少添加一个用例'))) const missingActor = parseUseCaseDsl(' 搜索商品') assert.equal(missingActor.actors[0].name, '用户') assert.equal(missingActor.primaryUseCases.length, 1) assert.ok(missingActor.warnings.some((warning) => warning.includes('缺少参与者'))) }) test('buildDefaultUseCaseDsl creates project-aware starter content from blueprint menus', () => { const dsl = buildDefaultUseCaseDsl({ projectName: '图书借阅系统', appBlueprint: JSON.stringify({ frontendMenus: [{ name: '图书浏览' }, { title: '我的借阅' }], adminMenus: [{ name: '图书管理' }, { code: 'borrowAudit' }] }) }) assert.match(dsl, /用户/) assert.match(dsl, /图书浏览/) assert.match(dsl, /我的借阅/) assert.match(dsl, /图书管理/) assert.match(dsl, /borrowAudit/) }) test('buildUseCaseDiagram lays out actor, ovals, relations, and grows canvas', () => { const small = buildUseCaseDiagram(parseUseCaseDsl('用户\n浏览商品')) const large = buildUseCaseDiagram(parseUseCaseDsl(shoppingDsl), { title: '商城用例图' }) assert.equal(large.title, '商城用例图') assert.equal(large.stats.actorCount, 1) assert.equal(large.stats.primaryUseCaseCount, 4) assert.equal(large.stats.useCaseCount, 13) assert.ok(large.canvas.height > small.canvas.height) assert.ok(large.actors[0].x < large.useCases[0].x) assert.ok(large.useCases.some((item) => item.kind === 'child')) assert.ok(large.relations.some((relation) => relation.label === '<>')) assert.ok(large.relations.some((relation) => relation.label === '<>')) }) test('buildUseCaseExportSvg serializes safe UML SVG without foreignObject', () => { const diagram = buildUseCaseDiagram(parseUseCaseDsl('用户\n浏览