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( '\n\n\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( '\n\n\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( '\n\n\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( '\n\n\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( '\n\n\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 = ''; 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">', width: 100, height: 100, }); const svg = rect.toSVG(); expect(svg).toContain( `id="asd"><script>alert(1)</script>"`, ); }); 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); }); }); });