fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
209 lines (181 loc) • 6.9 kB
text/typescript
import { describe, expect, it } from 'vitest';
import { Line } from './Line';
import { getFabricDocument, version } from '../../fabric';
import { FabricObject } from './Object/Object';
describe('Line', () => {
const LINE_OBJECT = {
version: version,
type: 'Line',
originX: 'center',
originY: 'center',
left: 12,
top: 13,
width: 2,
height: 2,
fill: 'rgb(0,0,0)',
stroke: null,
strokeWidth: 1,
strokeDashArray: null,
strokeLineCap: 'butt',
strokeDashOffset: 0,
strokeLineJoin: 'miter',
strokeMiterLimit: 4,
scaleX: 1,
scaleY: 1,
angle: 0,
flipX: false,
flipY: false,
opacity: 1,
x1: -1,
y1: -1,
x2: 1,
y2: 1,
shadow: null,
visible: true,
backgroundColor: '',
fillRule: 'nonzero',
paintFirst: 'fill',
globalCompositeOperation: 'source-over',
skewX: 0,
skewY: 0,
strokeUniform: false,
} as const;
it('initializes constructor correctly', () => {
expect(Line).toBeTruthy();
const line = new Line([10, 11, 20, 21]);
expect(line).toBeInstanceOf(Line);
expect(line).toBeInstanceOf(FabricObject);
expect(line.constructor).toHaveProperty('type', 'Line');
expect(line.get('x1')).toBe(10);
expect(line.get('y1')).toBe(11);
expect(line.get('x2')).toBe(20);
expect(line.get('y2')).toBe(21);
const lineWithoutPoints = new Line();
expect(lineWithoutPoints.get('x1')).toBe(0);
expect(lineWithoutPoints.get('y1')).toBe(0);
expect(lineWithoutPoints.get('x2')).toBe(0);
expect(lineWithoutPoints.get('y2')).toBe(0);
});
it('has complexity function', () => {
const line = new Line();
expect(line.complexity).toBeTypeOf('function');
});
it('generates SVG correctly', () => {
const line = new Line([11, 12, 13, 14]);
const EXPECTED_SVG =
'<g transform="matrix(1 0 0 1 12 13)" >\n<line 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;" x1="-1" y1="-1" x2="1" y2="1" />\n</g>\n';
expect(line.toSVG()).toEqual(EXPECTED_SVG);
});
it('converts to object correctly', () => {
const line = new Line([11, 12, 13, 14]);
expect(line.toObject).toBeTypeOf('function');
expect(line.toObject()).toEqual(LINE_OBJECT);
});
it('creates from object correctly', async () => {
expect(Line.fromObject).toBeTypeOf('function');
const line = await Line.fromObject(LINE_OBJECT);
expect(line).toBeInstanceOf(Line);
expect(line.toObject()).toEqual(LINE_OBJECT);
});
it('creates from SVG element correctly', async () => {
expect(Line.fromElement).toBeTypeOf('function');
const namespace = 'http://www.w3.org/2000/svg';
// TODO: should fromElement also accept SVGElement since test is doing it?
const lineEl = getFabricDocument().createElementNS(
namespace,
'line',
) as unknown as HTMLElement;
const x1 = 11;
const y1 = 23;
const x2 = 34;
const y2 = 7;
const stroke = 'ff5555';
const strokeWidth = 2;
const strokeDashArray = [5, 2];
const strokeLineCap = 'round';
const strokeLineJoin = 'bevel';
const strokeMiterLimit = 5;
lineEl.setAttributeNS(namespace, 'x1', String(x1));
lineEl.setAttributeNS(namespace, 'x2', String(x2));
lineEl.setAttributeNS(namespace, 'y1', String(y1));
lineEl.setAttributeNS(namespace, 'y2', String(y2));
lineEl.setAttributeNS(namespace, 'stroke', stroke);
lineEl.setAttributeNS(namespace, 'stroke-width', String(strokeWidth));
lineEl.setAttributeNS(namespace, 'stroke-dasharray', '5, 2');
lineEl.setAttributeNS(namespace, 'stroke-linecap', strokeLineCap);
lineEl.setAttributeNS(namespace, 'stroke-linejoin', strokeLineJoin);
lineEl.setAttributeNS(
namespace,
'stroke-miterlimit',
String(strokeMiterLimit),
);
const oLine = await Line.fromElement(lineEl);
expect(oLine).toBeInstanceOf(Line);
expect(oLine.get('x1')).toBe(x1);
expect(oLine.get('y1')).toBe(y1);
expect(oLine.get('x2')).toBe(x2);
expect(oLine.get('y2')).toBe(y2);
expect(oLine.get('stroke')).toBe(stroke);
expect(oLine.get('strokeWidth')).toBe(strokeWidth);
expect(oLine.get('strokeDashArray')).toEqual(strokeDashArray);
expect(oLine.get('strokeLineCap')).toBe(strokeLineCap);
expect(oLine.get('strokeLineJoin')).toBe(strokeLineJoin);
expect(oLine.get('strokeMiterLimit')).toBe(strokeMiterLimit);
const lineElWithMissingAttributes = getFabricDocument().createElementNS(
namespace,
'line',
) as unknown as HTMLElement;
lineElWithMissingAttributes.setAttributeNS(namespace, 'x1', String(10));
lineElWithMissingAttributes.setAttributeNS(namespace, 'y1', String(20));
const oLine2 = await Line.fromElement(lineElWithMissingAttributes);
expect(oLine2.get('x2')).toBe(0);
expect(oLine2.get('y2')).toBe(0);
});
it('allows straight lines to have 0 width or height', () => {
const line1 = new Line([10, 10, 100, 10]);
const line2 = new Line([10, 10, 10, 100]);
expect(line1.get('height')).toBe(0);
expect(line2.get('width')).toBe(0);
});
it('updates width/height when changing x/y coordinates', () => {
const line = new Line([50, 50, 100, 100]);
expect(line.width).toBe(50);
line.set({ x1: 75, y1: 75, x2: 175, y2: 175 });
expect(line.width).toBe(100);
expect(line.height).toBe(100);
});
it('parses stroke width from style attribute', async () => {
const namespace = 'http://www.w3.org/2000/svg';
const lineEl = getFabricDocument().createElementNS(
namespace,
'line',
) as unknown as HTMLElement;
lineEl.setAttribute('style', 'stroke-width:4');
const oLine = await Line.fromElement(lineEl);
expect(oLine.strokeWidth).toBe(4);
});
describe('line positioning', () => {
(['left', 'center', 'right'] as const).forEach((originX) => {
(['top', 'center', 'bottom'] as const).forEach((originY) => {
[0, 7].forEach((strokeWidth) => {
[0, 33, 90].forEach((angle) => {
(['butt', 'round', 'square'] as const).forEach((strokeLineCap) => {
it(`positions on center regardless of strokeWidth or origin (${originX}/${originY} stroke:${strokeWidth} angle:${angle} cap:${strokeLineCap})`, () => {
const line = new Line([1, 1, 15, 7], {
strokeWidth,
originX,
originY,
angle,
strokeLineCap,
});
const center = line.getCenterPoint();
expect(Math.round(center.x)).toBe(8);
expect(Math.round(center.y)).toBe(4);
});
});
});
});
});
});
});
});