clone

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

main
diagramzip / apps / editor / test / editable-svg-input.test.js
editable-svg-input.test.js 3.0K
1import assert from 'node:assert/strict'2import test from 'node:test'3import { canonicalizeSvg } from '../../../shared/svg/index.js'4import { exportEditableSvg } from '../src/editable-svg.js'5import {6 EditableSvgInputError,7 MAX_EDITABLE_SVG_INPUT_BYTES,8 importEditableSvgFile,9 importEditableSvgInput,10 importEditableSvgText,11} from '../src/editable-svg-input.js'1213const state = {14 type: 'pikchr',15 source: 'box "input"',16 options: {},17 meta: { title: 'Input', description: '' },18 presentation: { background: '', padding: 0, frame: false, appearance: 'raw' },19}20const canonical = canonicalizeSvg(21 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="1" y="1" width="18" height="18" fill="white"/></svg>',22 state.meta,23 'pikchr',24 'pikchr@85e65b9686/edge-wasm-1',25)26const editable = exportEditableSvg(canonical, state)2728test('imports pasted SVG text through the editable-document importer', () => {29 assert.deepEqual(importEditableSvgText(editable), state)30})3132test('imports HTTP, HTTPS, and data URLs with an injected fetch', async () => {33 const calls = []34 const fetchImpl = async url => {35 calls.push(url)36 return new Response(editable, { headers: { 'Content-Type': 'image/svg+xml; charset=utf-8' } })37 }38 assert.deepEqual(await importEditableSvgInput('https://example.test/diagram.svg', { fetchImpl }), state)39 assert.deepEqual(await importEditableSvgInput('data:image/svg+xml,fixture', { fetchImpl }), state)40 assert.deepEqual(calls, ['https://example.test/diagram.svg', 'data:image/svg+xml,fixture'])41})4243test('imports File and Blob inputs and enforces the shared 5 MiB cap', async () => {44 assert.deepEqual(await importEditableSvgFile(new Blob([editable], { type: 'image/svg+xml' })), state)45 await assert.rejects(46 () => importEditableSvgFile({ size: MAX_EDITABLE_SVG_INPUT_BYTES + 1, arrayBuffer: async () => new ArrayBuffer(0) }),47 error => error instanceof EditableSvgInputError && error.code === 'input_too_large',48 )49})5051test('rejects unsuccessful responses and non-SVG content types', async () => {52 await assert.rejects(53 () => importEditableSvgInput('https://example.test/missing.svg', {54 fetchImpl: async () => new Response('', { status: 404 }),55 }),56 error => error.code === 'fetch_failed',57 )58 await assert.rejects(59 () => importEditableSvgInput('https://example.test/page', {60 fetchImpl: async () => new Response(editable, { headers: { 'Content-Type': 'text/html' } }),61 }),62 error => error.code === 'invalid_content_type',63 )64})6566test('rejects streamed responses that exceed 5 MiB before importing', async () => {67 const stream = new ReadableStream({68 start(controller) {69 controller.enqueue(new Uint8Array(MAX_EDITABLE_SVG_INPUT_BYTES))70 controller.enqueue(new Uint8Array(1))71 controller.close()72 },73 })74 await assert.rejects(75 () => importEditableSvgInput('https://example.test/large.svg', {76 fetchImpl: async () => new Response(stream, { headers: { 'Content-Type': 'image/svg+xml' } }),77 }),78 error => error.code === 'input_too_large',79 )80})81