clone

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

main
diagramzip / renderers / edge / test / unit.test.ts
unit.test.ts 4.8K
1import { describe, expect, it } from 'vitest'2import { cacheRequestFor, createRendererUnit, createRendererUnitGroup } from '../src/runtime/unit'3import { NORMALIZER_BUILD } from '../../../shared/svg/index.js'4import type { EngineId, RendererAdapter } from '../src/runtime/types'56const adapter: RendererAdapter = {7 id: 'vegalite',8 runtime: 'edge-js',9 version: 'test@1',10 async render() {11 return {12 body: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 10"><text x="1" y="8">ok</text></svg>',13 contentType: 'image/svg+xml',14 engineVersion: 'test@1',15 runtime: 'edge-js',16 }17 },18}1920const unit = createRendererUnit({ id: 'vegalite', kind: 'translate', adapter, pipeline: ['vega'] })21const fetchUnit = unit.fetch!22const env = { RENDERER_BUILD: 'unit-test' }23const context = {24 waitUntil() {},25 passThroughOnException() {},26 props: {},27} as unknown as ExecutionContext2829function request(body: unknown): Parameters<typeof fetchUnit>[0] {30 return new Request('https://vegalite.render.diagram.zip/v1/svg', {31 method: 'POST',32 headers: { 'Content-Type': 'application/json' },33 body: JSON.stringify(body),34 }) as Parameters<typeof fetchUnit>[0]35}3637describe('renderer unit protocol', () => {38 it('names the normalizer build in its cache namespace', async () => {39 const key = await cacheRequestFor({ engine: 'vegalite', source: '{}', format: 'svg', options: {}, metadata: { title: '', description: '' }, presentation: { background: '', padding: 0, frame: false } }, env.RENDERER_BUILD)40 expect(new URL(key.url).pathname).toContain(`/${NORMALIZER_BUILD}/vegalite/`)41 })4243 it('assigns its own engine and exposes the explicit translation pipeline', async () => {44 const response = await fetchUnit(request({ source: '{"mark":"point"}' }), env, context)45 expect(response.status).toBe(200)46 expect(response.headers.get('X-Diagram-Unit')).toBe('vegalite')47 expect(response.headers.get('X-Diagram-Pipeline')).toBe('vegalite,vega')48 expect(response.headers.get('Access-Control-Expose-Headers')).toContain('X-Diagram-Unit')49 expect(response.headers.get('Access-Control-Expose-Headers')).toContain('X-Renderer-Build')50 expect(response.headers.get('Content-Type')).toContain('image/svg+xml')51 expect(response.headers.get('Content-Security-Policy')).toBe("default-src 'none'; style-src 'unsafe-inline'; sandbox")52 expect(await response.text()).toContain('<text x="1" y="8">ok</text>')53 })5455 it('rejects attempts to select a different engine inside a unit', async () => {56 const response = await fetchUnit(request({ engine: 'mermaid', source: 'flowchart LR' }), env, context)57 expect(response.status).toBe(400)58 await expect(response.json()).resolves.toMatchObject({ error: { code: 'invalid_request' } })59 })6061 it('describes only itself and its declared downstream unit', async () => {62 const response = await fetchUnit(63 new Request('https://vegalite.render.diagram.zip/v1/capabilities') as Parameters<typeof fetchUnit>[0],64 env,65 context,66 )67 await expect(response.json()).resolves.toMatchObject({68 id: 'vegalite',69 kind: 'translate',70 pipeline: ['vegalite', 'vega'],71 normalization: {72 schema: '1',73 profile: 'safe-raw-1',74 conformance: 'raw',75 appearances: ['raw'],76 },77 })78 })79})8081function testAdapter(id: EngineId): RendererAdapter {82 return {83 id,84 runtime: 'edge-js',85 version: 'test@1',86 async render() {87 return {88 body: `<svg xmlns="http://www.w3.org/2000/svg"><text>${id}</text></svg>`,89 contentType: 'image/svg+xml',90 engineVersion: 'test@1',91 runtime: 'edge-js',92 }93 },94 }95}9697describe('renderer dependency groups', () => {98 const group = createRendererUnitGroup('blockdiag-family', [99 { id: 'blockdiag', kind: 'render', adapter: testAdapter('blockdiag') },100 { id: 'seqdiag', kind: 'render', adapter: testAdapter('seqdiag') },101 ])102 const fetchGroup = group.fetch!103104 it('selects an engine from its dedicated hostname while reporting the shared unit', async () => {105 const response = await fetchGroup(new Request('https://seqdiag.render.diagram.zip/v1/svg', {106 method: 'POST',107 headers: { 'Content-Type': 'application/json' },108 body: JSON.stringify({ source: 'A -> B' }),109 }) as Parameters<typeof fetchGroup>[0], env, context)110111 expect(response.status).toBe(200)112 expect(response.headers.get('X-Diagram-Engine')).toBe('seqdiag')113 expect(response.headers.get('X-Diagram-Unit')).toBe('blockdiag-family')114 expect(response.headers.get('X-Diagram-Pipeline')).toBe('blockdiag-family')115 })116117 it('does not serve an engine outside the dependency group', async () => {118 const response = await fetchGroup(119 new Request('https://graphviz.render.diagram.zip/v1/capabilities') as Parameters<typeof fetchGroup>[0],120 env,121 context,122 )123124 expect(response.status).toBe(404)125 })126})