Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.vscode
node_modules
dist
*.tgz
*.tgz
src/tests/*/__screenshots__
5 changes: 4 additions & 1 deletion src/display/elements/Relations.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const ComposedRelations = mixins(Element, Linksable, Relationstyleable);
export class Relations extends ComposedRelations {
static isSelectable = true;
static hitScope = 'children';
linkPoints = [];

_renderDirty = true;

Expand All @@ -25,7 +26,7 @@ export class Relations extends ComposedRelations {

initPath() {
const path = new Graphics();
Object.assign(path, { type: 'path', links: [], allowContainsPoint: true });
Object.assign(path, { type: 'path', allowContainsPoint: true });
this.addChild(path);
return path;
}
Expand All @@ -49,6 +50,7 @@ export class Relations extends ComposedRelations {
const { links } = this.props;
if (!this.path) return;
this.path.clear();
this.linkPoints.length = 0;
let lastPoint = null;

for (const link of links) {
Expand Down Expand Up @@ -82,6 +84,7 @@ export class Relations extends ComposedRelations {
}
this.path.lineTo(...targetPoint);
lastPoint = targetPoint;
this.linkPoints.push({ sourcePoint, targetPoint });
}
this.path.stroke();
}
Expand Down
124 changes: 124 additions & 0 deletions src/tests/render/Relations.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { setupPatchmapTests } from './patchmap.setup';

describe('Relations Component Rendering Tests', () => {
const { getPatchmap } = setupPatchmapTests();

beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});

const baseMapData = [
{ type: 'item', id: 'item-A', size: 50, attrs: { x: 100, y: 100 } },
{ type: 'item', id: 'item-B', size: 50, attrs: { x: 300, y: 100 } },
{ type: 'item', id: 'item-C', size: 50, attrs: { x: 200, y: 300 } },
{
type: 'relations',
id: 'rel-1',
links: [{ source: 'item-A', target: 'item-B' }],
style: { width: 2, color: 'primary.default' }, // 0x0c73bf
},
];

const getRelations = (patchmap) => {
return patchmap.selector('$..[?(@.id=="rel-1")]')[0];
};

const getPath = (patchmap) => {
return getRelations(patchmap).children[0];
};

it('should render correctly with initial properties', async () => {
const patchmap = getPatchmap();
patchmap.draw(baseMapData);
await vi.advanceTimersByTimeAsync(100);

const relations = getRelations(patchmap);
const path = getPath(patchmap);

expect(relations).toBeDefined();
expect(path).toBeDefined();
expect(path.type).toBe('path');

expect(relations.props.links).toHaveLength(1);
expect(relations.props.style.width).toBe(2);

expect(relations.linkPoints).toHaveLength(1);
const points = relations.linkPoints[0];
const itemA = patchmap.selector('$..[?(@.id=="item-A")]')[0];
const itemB = patchmap.selector('$..[?(@.id=="item-B")]')[0];

expect(points.sourcePoint).toEqual([itemA.x, itemA.y]);
expect(points.targetPoint).toEqual([itemB.x, itemB.y]);
});

it('should update path style when the "style" property changes', async () => {
const patchmap = getPatchmap();
patchmap.draw(baseMapData);
await vi.advanceTimersByTimeAsync(100);

patchmap.update({
path: '$..[?(@.id=="rel-1")]',
changes: { style: { width: 5, color: 'primary.accent' } }, // 0xef4444
});

const path = getPath(patchmap);
expect(path.strokeStyle.width).toBe(5);
expect(path.strokeStyle.color).toBe(0xef4444);
});

it('should recalculate points and redraw when "links" property changes', async () => {
const patchmap = getPatchmap();
patchmap.draw(baseMapData);
await vi.advanceTimersByTimeAsync(100);

const relations = getRelations(patchmap);
const originalPointsLength = relations.linkPoints.length;
expect(originalPointsLength).toBe(1);

patchmap.update({
path: '$..[?(@.id=="rel-1")]',
changes: {
links: [{ source: 'item-B', target: 'item-C' }],
},
});
await vi.advanceTimersByTimeAsync(100);

const updatedRelations = getRelations(patchmap);
expect(updatedRelations.props.links).toHaveLength(2);
expect(updatedRelations.linkPoints).toHaveLength(2);

const newPoints = updatedRelations.linkPoints[1];
const itemB = patchmap.selector('$..[?(@.id=="item-B")]')[0];
const itemC = patchmap.selector('$..[?(@.id=="item-C")]')[0];
expect(newPoints.sourcePoint).toEqual([itemB.x, itemB.y]);
expect(newPoints.targetPoint).toEqual([itemC.x, itemC.y]);
});

it('should recalculate points and redraw when a linked item is moved', async () => {
const patchmap = getPatchmap();
patchmap.draw(baseMapData);
await vi.advanceTimersByTimeAsync(100);

const relations = getRelations(patchmap);
const originalPoint = [...relations.linkPoints[0].targetPoint];
expect(originalPoint).toEqual([300, 100]);

patchmap.update({
path: '$..[?(@.id=="item-B")]',
changes: { attrs: { x: 400, y: 250 } },
});

await vi.advanceTimersByTimeAsync(100);

const updatedPoints = relations.linkPoints[0].targetPoint;
expect(updatedPoints).not.toEqual(originalPoint);
expect(updatedPoints).toEqual([400, 250]);

const path = getPath(patchmap);
expect(path.getBounds().width).toBeGreaterThan(0);
});
});