1import { describe, expect, it } from 'vitest'2import { graphvizAdapter, optionsFor, renderGraphvizSource } from '../src/adapters/graphviz'3import type { RenderRequest } from '../src/runtime/types'45describe('GraphViz option lowering', () => {6 it('maps layout and default graph/node/edge attributes', () => {7 expect(optionsFor({8 layout: 'neato',9 'graph-attribute-rankdir': 'LR',10 'node-attribute-shape': 'box',11 'edge-attribute-color': 'gray50',12 })).toMatchObject({13 engine: 'neato',14 graphAttributes: { rankdir: 'LR' },15 nodeAttributes: { shape: 'box' },16 edgeAttributes: { color: 'gray50' },17 })18 })1920 it('rejects scale and unknown options instead of silently dropping them', () => {21 expect(() => optionsFor({ scale: '72' })).toThrow(/scale is not supported/)22 expect(() => optionsFor({ engine: 'dot' })).toThrow(/Unsupported GraphViz option/)23 })2425 it('rejects resource-loading attributes supplied through options', () => {26 for (const option of [27 'graph-attribute-imagepath',28 'graph-attribute-fontpath',29 'graph-attribute-stylesheet',30 'node-attribute-image',31 'node-attribute-shapefile',32 ]) {33 expect(() => optionsFor({ [option]: '/tmp/resource' })).toThrow(/resource attribute/)34 }35 })3637 it('rejects DOT and HTML image loading forms', async () => {38 const request = (source: string): RenderRequest => ({39 engine: 'graphviz', source, format: 'svg', options: {},40 metadata: { title: '', description: '' },41 presentation: { background: '', padding: 0, frame: false },42 })43 await expect(graphvizAdapter.render(request('digraph { a [image="/tmp/a.png"] }'), new AbortController().signal)).rejects.toThrow(/not supported/)44 await expect(graphvizAdapter.render(request('digraph { a [label=<<IMG SRC="a.png"/>>] }'), new AbortController().signal)).rejects.toThrow(/not supported/)45 await expect(graphvizAdapter.render(request('digraph { a [label="image=descriptive text"] }'), new AbortController().signal)).resolves.toMatchObject({ runtime: 'edge-wasm' })46 })4748 it('renders the HTML labels and ports needed by translated engines', async () => {49 const svg = await renderGraphvizSource('digraph { a [shape=plain label=<<TABLE><TR><TD PORT="p">A</TD></TR></TABLE>>]; b [label="B"]; a:p -> b }')50 expect(svg).toMatch(/<svg[\s>]/i)51 expect(svg).toContain('A')52 expect(svg).toContain('B')53 })5455 it('serializes shared-module renders without leaking errors', async () => {56 const results = await Promise.allSettled([57 renderGraphvizSource('digraph { a -> b }'),58 renderGraphvizSource('digraph { broken -> }'),59 renderGraphvizSource('graph { "safe" -- "still safe" }'),60 ])61 expect(results[0]).toMatchObject({ status: 'fulfilled' })62 expect(results[1]).toMatchObject({ status: 'rejected' })63 expect(results[2]).toMatchObject({ status: 'fulfilled' })64 if (results[2]!.status === 'fulfilled') expect(results[2]!.value).toContain('still safe')65 })66})