UNPKG

fabric

Version:

Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.

257 lines (239 loc) 8.76 kB
import { describe, expect, it } from 'vitest'; import { version } from '../../package.json'; import { Rect } from './Rect'; import { FabricObject } from './Object/FabricObject'; import { Gradient } from '../gradient'; import { Pattern } from '../Pattern'; import { loadSVGFromString } from '../parser/loadSVGFromString'; import { createReferenceObject, createSVGElement } from '../../test/utils'; const REFERENCE_RECT = createReferenceObject('Rect', { rx: 0, ry: 0, }); describe('Rect', () => { it('constructor', function () { const rect = new Rect(); expect(rect).toBeInstanceOf(Rect); expect(rect, 'Inherits from FabricObject').toBeInstanceOf(FabricObject); expect(rect.constructor).toHaveProperty('type', 'Rect'); }); it('cache properties', function () { expect(Rect.cacheProperties, 'rx is in cacheProperties array').toContain( 'rx', ); expect(Rect.cacheProperties, 'ry is in cacheProperties array').toContain( 'ry', ); }); it('toObject', function () { const rect = new Rect(); const object = rect.toObject(); expect(object).toEqual(REFERENCE_RECT); }); it('fromObject', async () => { const rect = await Rect.fromObject(REFERENCE_RECT); expect(rect).toBeInstanceOf(Rect); expect(rect.toObject()).toEqual(REFERENCE_RECT); const expectedObject = { ...REFERENCE_RECT, fill: { type: 'linear', coords: { x1: 0, y1: 0, x2: 200, y2: 0 }, colorStops: [ { offset: '0', color: 'rgb(255,0,0)', opacity: 1 }, { offset: '1', color: 'rgb(0,0,255)', opacity: 1 }, ], offsetX: 0, offsetY: 0, }, stroke: { type: 'linear', coords: { x1: 0, y1: 0, x2: 200, y2: 0 }, colorStops: [ { offset: '0', color: 'rgb(255,0,0)', opacity: 1 }, { offset: '1', color: 'rgb(0,0,255)', opacity: 1 }, ], offsetX: 0, offsetY: 0, }, }; const rect2 = await Rect.fromObject(expectedObject); expect(rect2.fill).toBeInstanceOf(Gradient); expect(rect2.stroke).toBeInstanceOf(Gradient); }); it('Rect.fromObject with pattern fill', async () => { const fillObj = { type: 'Pattern', source: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z/C/HgAGgwJ/lK3Q6wAAAABJRU5ErkJggg==', }; const rect = await Rect.fromObject({ fill: fillObj }); expect(rect.fill).toBeInstanceOf(Pattern); }); it('Rect.fromElement', async () => { const elRect = createSVGElement('rect'); const rect = await Rect.fromElement(elRect); expect(rect).toBeInstanceOf(Rect); expect(rect.toObject()).toEqual({ ...REFERENCE_RECT, visible: false }); }); it('fromElement with custom attributes', async () => { const elRectWithAttrs = createSVGElement('rect', { x: 10, y: 20, width: 222, height: 333, rx: 11, ry: 12, fill: 'rgb(255,255,255)', opacity: 0.45, stroke: 'blue', 'stroke-width': 3, 'stroke-dasharray': '5, 2', 'stroke-linecap': 'round', 'stroke-linejoin': 'bevel', 'stroke-miterlimit': 5, 'vector-effect': 'non-scaling-stroke', }); const rectWithAttrs = await Rect.fromElement(elRectWithAttrs); expect(rectWithAttrs).toBeInstanceOf(Rect); expect(rectWithAttrs.strokeUniform, 'strokeUniform is parsed').toBe(true); const expectedObject = { ...REFERENCE_RECT, left: 10, top: 20, width: 222, height: 333, fill: 'rgb(255,255,255)', opacity: 0.45, stroke: 'blue', strokeWidth: 3, strokeDashArray: [5, 2], strokeLineCap: 'round', strokeLineJoin: 'bevel', strokeMiterLimit: 5, rx: 11, ry: 12, strokeUniform: true, }; expect(rectWithAttrs.toObject()).toEqual(expectedObject); }); it('clone with rounded corners', async () => { const rect = new Rect({ width: 100, height: 100, rx: 20, ry: 30 }); const clone = await rect.clone(); expect(clone.get('rx'), rect.get('rx')); expect(clone.get('ry'), rect.get('ry')); }); it('toSVG with rounded corners', async () => { const rect = new Rect({ left: 50, top: 50, width: 100, height: 100, rx: 20, ry: 30, strokeWidth: 0, }); const svg = rect.toSVG(); expect(svg).toBe( '<g transform="matrix(1 0 0 1 50 50)" >\n<rect style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" x="-50" y="-50" rx="20" ry="30" width="100" height="100" />\n</g>\n', ); }); it('toSVG with alpha colors fill', async () => { const rect = new Rect({ left: 50, top: 50, width: 100, height: 100, strokeWidth: 0, fill: 'rgba(255, 0, 0, 0.5)', }); const svg = rect.toSVG(); expect(svg).toBe( '<g transform="matrix(1 0 0 1 50 50)" >\n<rect style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-opacity: 0.5; fill-rule: nonzero; opacity: 1;" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />\n</g>\n', ); }); it('toSVG with id', async () => { const rect = new Rect({ id: 'myRect', width: 100, height: 100, strokeWidth: 0, fill: 'rgba(255, 0, 0, 0.5)', }); const svg = rect.toSVG(); expect(svg).toBe( '<g transform="matrix(1 0 0 1 0 0)" id="myRect" >\n<rect style="stroke: none; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-opacity: 0.5; fill-rule: nonzero; opacity: 1;" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />\n</g>\n', ); }); it('toSVG with alpha colors stroke', async () => { const rect = new Rect({ top: 50, width: 100, height: 100, strokeWidth: 0, fill: '', stroke: 'rgba(255, 0, 0, 0.5)', }); const svg = rect.toSVG(); expect(svg).toBe( '<g transform="matrix(1 0 0 1 0 50)" >\n<rect style="stroke: rgb(255,0,0); stroke-opacity: 0.5; stroke-width: 0; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: none; fill-rule: nonzero; opacity: 1;" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />\n</g>\n', ); }); it('toSVG with paintFirst set to stroke', async () => { const rect = new Rect({ left: 50, width: 100, height: 100, paintFirst: 'stroke', }); const svg = rect.toSVG(); expect(svg).toBe( '<g transform="matrix(1 0 0 1 50 0)" >\n<rect style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1;" paint-order="stroke" x="-50" y="-50" rx="0" ry="0" width="100" height="100" />\n</g>\n', ); }); it('toObject without default values', async () => { const options = { width: 69, height: 50, left: 10, top: 20, version, }; const rect = new Rect(options); rect.includeDefaultValues = false; expect(rect.toObject()).toEqual({ type: 'Rect', ...options }); }); it('paintFirst life cycle', async () => { const svg = '<svg><rect x="10" y="10" height="50" width="55" fill="red" stroke="blue" paint-order="stroke" /></svg>'; const { objects } = await loadSVGFromString(svg); const rect = objects[0]; expect(rect).toBeTruthy(); const rectObject = rect!.toObject(); const rectSvg = rect!.toSVG(); expect(rect?.paintFirst).toBe('stroke'); expect(rectObject.paintFirst).toBe('stroke'); expect(rectSvg).toContain('paint-order="stroke"'); }); describe('svg attribute injection', () => { it('properties are properly escaped', () => { const rect = new Rect({ id: 'asd"><script>alert(1)</script>', width: 100, height: 100, }); const svg = rect.toSVG(); expect(svg).toContain( `id="asd&quot;&gt;&lt;script&gt;alert(1)&lt;/script&gt;"`, ); }); it('polyglot test', () => { const polyglotPayload = 'jaVasCript:/*-/*`/*\\`/*\'/*"/**/(/* */oNcliCk=alert() )'; const rect = new Rect({ id: polyglotPayload, width: 100, height: 100 }); const svg = rect.toSVG(); // Should escape all special characters expect(svg).not.toContain(polyglotPayload); }); }); });