clone

a browser-first diagram editor with live svg previews, local drafts and encrypted, accountless sharing

main
diagramzip / renderers / edge / test / d2.test.ts
d2.test.ts 2.7K
1import { describe, expect, it } from 'vitest'2import { DOMParser } from 'linkedom'3import { d2Adapter } from '../src/adapters/d2'4import type { RenderRequest } from '../src/runtime/types'56const request = (source: string, options: Record<string, string> = {}): RenderRequest => ({7 engine: 'd2',8 source, format: 'svg' as const, options,9 metadata: { title: '', description: '' },10 presentation: { background: '', padding: 0, frame: false },11})1213describe('D2 dedicated Worker adapter', () => {14 it('uses the Dagre top-to-bottom default layout', async () => {15 const result = await d2Adapter.render(request('a -> b -> c'), new AbortController().signal)16 const document = new DOMParser().parseFromString(result.body, 'image/svg+xml')17 const nodes = [...document.querySelectorAll('g.shape > rect')].slice(0, 3)18 const x = nodes.map(node => Number(node.getAttribute('x')))19 const y = nodes.map(node => Number(node.getAttribute('y')))20 const span = (values: number[]) => Math.max(...values) - Math.min(...values)2122 expect(nodes).toHaveLength(3)23 expect(span(y)).toBeGreaterThan(span(x))24 })2526 it('accepts Dagre and rejects ELK and out-of-range animation options explicitly', async () => {27 await expect(d2Adapter.render(request('a -> b', { layout: 'dagre' }), new AbortController().signal)).resolves.toMatchObject({ contentType: 'image/svg+xml' })28 await expect(d2Adapter.render(request('a -> b', { layout: 'elk' }), new AbortController().signal)).rejects.toMatchObject({ code: 'unsupported_options', status: 400 })29 await expect(d2Adapter.render(request('a -> b', { 'animate-interval': '0' }), new AbortController().signal)).rejects.toMatchObject({ code: 'invalid_options', status: 400 })30 await expect(d2Adapter.render(request('a -> b', { 'animate-interval': '60001' }), new AbortController().signal)).rejects.toMatchObject({ code: 'invalid_options', status: 400 })31 })3233 it('renders ordinary labels and animated multi-board SVG', async () => {34 const result = await d2Adapter.render(request('a -> b: { style.animated: true }\nsteps: {\n 1: {\n next -> done\n }\n}', { 'animate-interval': '1000' }), new AbortController().signal)35 expect(result.body).toMatch(/<svg[\s>]/i)36 expect(result.body).toContain('@keyframes')37 expect(result.body).toContain('<animate attributeName="stroke-dashoffset"')38 expect(result.body).toContain('repeatCount="indefinite"')39 expect(result.body).toMatch(/<path[^>]*animated-connection[^>]*><animate[^>]*><\/animate><\/path>/)40 expect(result.body).not.toContain('animation: dashdraw')41 expect(result.body).not.toMatch(/<path[^>]*animated-connection[^>]*style="[^"]*stroke-dashoffset/)42 expect(result.body).not.toMatch(/<path[^>]*animated-connection[^>]*mask=/)43 expect(result.body).toContain('next')44 })45})