2026-07-02 23:22:21 +02:00

125 lines
3.4 KiB
TypeScript

import '@testing-library/jest-dom/vitest';
/**
* SVG polyfills for jsdom so JointJS (@joint/core) can run in tests.
* Vectorizer feature-detects `window.SVGAngle` at module load and jsdom
* implements neither it nor the SVG geometry APIs (matrices, points, bbox).
* These must be installed before @joint/core is imported.
*/
class Matrix {
a = 1; b = 0; c = 0; d = 1; e = 0; f = 0;
multiply(m: Matrix): Matrix {
const r = new Matrix();
r.a = this.a * m.a + this.c * m.b;
r.b = this.b * m.a + this.d * m.b;
r.c = this.a * m.c + this.c * m.d;
r.d = this.b * m.c + this.d * m.d;
r.e = this.a * m.e + this.c * m.f + this.e;
r.f = this.b * m.e + this.d * m.f + this.f;
return r;
}
translate(tx: number, ty: number): Matrix {
const m = new Matrix();
m.e = tx;
m.f = ty;
return this.multiply(m);
}
scale(s: number): Matrix {
return this.scaleNonUniform(s, s);
}
scaleNonUniform(sx: number, sy: number): Matrix {
const m = new Matrix();
m.a = sx;
m.d = sy;
return this.multiply(m);
}
rotate(deg: number): Matrix {
const rad = (deg * Math.PI) / 180;
const m = new Matrix();
m.a = Math.cos(rad);
m.b = Math.sin(rad);
m.c = -Math.sin(rad);
m.d = Math.cos(rad);
return this.multiply(m);
}
rotateFromVector(): Matrix { return new Matrix(); }
flipX(): Matrix { return this.scaleNonUniform(-1, 1); }
flipY(): Matrix { return this.scaleNonUniform(1, -1); }
skewX(): Matrix { return new Matrix(); }
skewY(): Matrix { return new Matrix(); }
inverse(): Matrix {
const det = this.a * this.d - this.b * this.c;
if (det === 0) throw new Error('non-invertible matrix');
const r = new Matrix();
r.a = this.d / det;
r.b = -this.b / det;
r.c = -this.c / det;
r.d = this.a / det;
r.e = (this.c * this.f - this.d * this.e) / det;
r.f = (this.b * this.e - this.a * this.f) / det;
return r;
}
}
class Point {
x = 0; y = 0;
matrixTransform(m: Matrix): Point {
const p = new Point();
p.x = m.a * this.x + m.c * this.y + m.e;
p.y = m.b * this.x + m.d * this.y + m.f;
return p;
}
}
const g = globalThis as Record<string, unknown>;
if (!('SVGAngle' in g)) {
g.SVGAngle = class SVGAngle {};
}
const svgElementProto = (window.SVGSVGElement ?? window.SVGElement)?.prototype as unknown as Record<string, unknown>;
if (svgElementProto) {
svgElementProto.createSVGMatrix = () => new Matrix();
svgElementProto.createSVGPoint = () => new Point();
svgElementProto.createSVGTransform = () => ({
matrix: new Matrix(),
setMatrix(m: Matrix) { this.matrix = m; },
setTranslate() {},
});
}
const graphicsProto = window.SVGElement?.prototype as unknown as Record<string, unknown>;
if (graphicsProto) {
graphicsProto.getBBox = function getBBox() {
return { x: 0, y: 0, width: 0, height: 0 };
};
graphicsProto.getScreenCTM = function getScreenCTM() {
return new Matrix();
};
graphicsProto.getCTM = function getCTM() {
return new Matrix();
};
}
if (!window.Element.prototype.checkVisibility) {
window.Element.prototype.checkVisibility = function checkVisibility() {
return this.isConnected;
};
}
const pathProto = (window.SVGPathElement?.prototype ?? graphicsProto) as unknown as Record<string, unknown>;
if (pathProto) {
pathProto.getTotalLength = function getTotalLength() { return 0; };
pathProto.getPointAtLength = function getPointAtLength() {
const p = new Point();
return p;
};
}