fix: add safe check for arrow points length in tranformToExcalidrawElements (#7863)

* fix: add safe check for arrow points length in tranformToExcalidrawElements

* add spec

* throw error only for dev mode

* fix lint
This commit is contained in:
Aakansha Doshi 2024-04-09 09:56:21 +05:30 committed by GitHub
parent 8a162a4cb4
commit a33a400f01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 721 additions and 39 deletions

View File

@ -18,7 +18,7 @@ import { TTDDialogInput } from "./TTDDialogInput";
import { TTDDialogOutput } from "./TTDDialogOutput";
import { EditorLocalStorage } from "../../data/EditorLocalStorage";
import { EDITOR_LS_KEYS } from "../../constants";
import { debounce } from "../../utils";
import { debounce, isDevEnv } from "../../utils";
import { TTDDialogSubmitShortcut } from "./TTDDialogSubmitShortcut";
const MERMAID_EXAMPLE =
@ -54,7 +54,11 @@ const MermaidToExcalidraw = ({
mermaidToExcalidrawLib,
setError,
mermaidDefinition: deferredText,
}).catch(() => {});
}).catch((err) => {
if (isDevEnv()) {
console.error("Failed to parse mermaid definition", err);
}
});
debouncedSaveMermaidDefinition(deferredText);
}, [deferredText, mermaidToExcalidrawLib]);

View File

@ -1271,6 +1271,546 @@ exports[`Test Transform > should transform text element 2`] = `
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 1`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id54",
"type": "text",
},
{
"id": "Bob_B",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [
"subgraph_group_B",
],
"height": 163,
"id": Any<String>,
"index": "a0",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"width": 166.03125,
"x": 0,
"y": 0,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 2`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id55",
"type": "text",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [
"subgraph_group_A",
],
"height": 114,
"id": Any<String>,
"index": "a1",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 2,
"versionNonce": Any<Number>,
"width": 120.265625,
"x": 364.546875,
"y": 0,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 3`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id56",
"type": "text",
},
{
"id": "Bob_Alice",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [
"subgraph_group_A",
],
"height": 44,
"id": Any<String>,
"index": "a2",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"width": 70.265625,
"x": 389.546875,
"y": 35,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 4`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id57",
"type": "text",
},
{
"id": "Bob_Alice",
"type": "arrow",
},
{
"id": "Bob_B",
"type": "arrow",
},
],
"customData": undefined,
"fillStyle": "solid",
"frameId": null,
"groupIds": [
"subgraph_group_B",
],
"height": 44,
"id": Any<String>,
"index": "a3",
"isDeleted": false,
"link": null,
"locked": false,
"opacity": 100,
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "rectangle",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 56.4921875,
"x": 54.76953125,
"y": 35,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 5`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id58",
"type": "text",
},
],
"customData": undefined,
"endArrowhead": "arrow",
"endBinding": {
"elementId": "Alice",
"focus": 0,
"gap": 5.299874999999986,
},
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a4",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
"locked": false,
"opacity": 100,
"points": [
[
0.5,
0,
],
[
272.485,
0,
],
],
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": Any<Number>,
"startArrowhead": null,
"startBinding": {
"elementId": "Bob",
"focus": 0,
"gap": 1,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 272.985,
"x": 111.262,
"y": 57,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 6`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": [
{
"id": "id59",
"type": "text",
},
],
"customData": undefined,
"endArrowhead": "arrow",
"endBinding": {
"elementId": "B",
"focus": 0,
"gap": 1,
},
"fillStyle": "solid",
"frameId": null,
"groupIds": [],
"height": 0,
"id": Any<String>,
"index": "a5",
"isDeleted": false,
"lastCommittedPoint": null,
"link": null,
"locked": false,
"opacity": 100,
"points": [
[
0,
0,
],
],
"roughness": 1,
"roundness": {
"type": 2,
},
"seed": Any<Number>,
"startArrowhead": null,
"startBinding": {
"elementId": "Bob",
"focus": 0,
"gap": 1,
},
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"type": "arrow",
"updated": 1,
"version": 4,
"versionNonce": Any<Number>,
"width": 0,
"x": 77.017,
"y": 79,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 7`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "B",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [
"subgraph_group_B",
],
"height": 25,
"id": Any<String>,
"index": "a6",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "B",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "B",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 10,
"x": 78.015625,
"y": 5,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 8`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "A",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [
"subgraph_group_A",
],
"height": 25,
"id": Any<String>,
"index": "a7",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "A",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "A",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "top",
"width": 10,
"x": 419.6796875,
"y": 5,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 9`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "Alice",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [
"subgraph_group_A",
],
"height": 25,
"id": Any<String>,
"index": "a8",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "Alice",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "Alice",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 50,
"x": 399.6796875,
"y": 44.5,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 10`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "Bob",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [
"subgraph_group_B",
],
"height": 25,
"id": Any<String>,
"index": "a9",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "Bob",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "Bob",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 30,
"x": 68.015625,
"y": 44.5,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 11`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "Bob_Alice",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "aA",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "How are you?",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "How are you?",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 120,
"x": 187.7545,
"y": 44.5,
}
`;
exports[`Test Transform > should transform the elements correctly when linear elements have single point 12`] = `
{
"angle": 0,
"backgroundColor": "transparent",
"boundElements": null,
"containerId": "Bob_B",
"customData": undefined,
"fillStyle": "solid",
"fontFamily": 1,
"fontSize": 20,
"frameId": null,
"groupIds": [],
"height": 25,
"id": Any<String>,
"index": "aB",
"isDeleted": false,
"lineHeight": 1.25,
"link": null,
"locked": false,
"opacity": 100,
"originalText": "Friendship",
"roughness": 1,
"roundness": null,
"seed": Any<Number>,
"strokeColor": "#1e1e1e",
"strokeStyle": "solid",
"strokeWidth": 2,
"text": "Friendship",
"textAlign": "center",
"type": "text",
"updated": 1,
"version": 3,
"versionNonce": Any<Number>,
"verticalAlign": "middle",
"width": 100,
"x": 27.016999999999996,
"y": 66.5,
}
`;
exports[`Test Transform > should transform to labelled arrows when label provided for arrows 1`] = `
{
"angle": 0,

View File

@ -152,14 +152,14 @@ describe("Test Transform", () => {
strokeStyle: "dotted",
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
expect(excalidrawElements.length).toBe(4);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -235,14 +235,14 @@ describe("Test Transform", () => {
},
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(12);
expect(excalidrawElements.length).toBe(12);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -293,14 +293,14 @@ describe("Test Transform", () => {
},
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(8);
expect(excalidrawElements.length).toBe(8);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -338,13 +338,13 @@ describe("Test Transform", () => {
name: "My frame",
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elementsSkeleton,
opts,
);
expect(excaldrawElements.length).toBe(4);
expect(excalidrawElements.length).toBe(4);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchObject({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -383,11 +383,11 @@ describe("Test Transform", () => {
height: 100,
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elementsSkeleton,
opts,
);
const frame = excaldrawElements.find((ele) => ele.type === "frame")!;
const frame = excalidrawElements.find((ele) => ele.type === "frame")!;
expect(frame.width).toBe(800);
expect(frame.height).toBe(126);
});
@ -411,13 +411,13 @@ describe("Test Transform", () => {
},
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
const [arrow, text, rectangle, ellipse] = excaldrawElements;
expect(excalidrawElements.length).toBe(4);
const [arrow, text, rectangle, ellipse] = excalidrawElements;
expect(arrow).toMatchObject({
type: "arrow",
x: 255,
@ -466,7 +466,7 @@ describe("Test Transform", () => {
],
});
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -495,13 +495,13 @@ describe("Test Transform", () => {
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
const [arrow, text1, text2, text3] = excaldrawElements;
expect(excalidrawElements.length).toBe(4);
const [arrow, text1, text2, text3] = excalidrawElements;
expect(arrow).toMatchObject({
type: "arrow",
@ -551,7 +551,7 @@ describe("Test Transform", () => {
],
});
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -611,14 +611,14 @@ describe("Test Transform", () => {
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(5);
expect(excalidrawElements.length).toBe(5);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -660,14 +660,14 @@ describe("Test Transform", () => {
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
expect(excalidrawElements.length).toBe(4);
excaldrawElements.forEach((ele) => {
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
@ -714,13 +714,13 @@ describe("Test Transform", () => {
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(4);
const [, , arrow, text] = excaldrawElements;
expect(excalidrawElements.length).toBe(4);
const [, , arrow, text] = excalidrawElements;
expect(arrow).toMatchObject({
type: "arrow",
x: 255,
@ -765,12 +765,12 @@ describe("Test Transform", () => {
backgroundColor: "#bac8ff",
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(2);
const [arrow, rect] = excaldrawElements;
expect(excalidrawElements.length).toBe(2);
const [arrow, rect] = excalidrawElements;
expect((arrow as ExcalidrawArrowElement).endBinding).toStrictEqual({
elementId: "rect-1",
focus: 0,
@ -808,13 +808,13 @@ describe("Test Transform", () => {
height: 200,
},
];
const excaldrawElements = convertToExcalidrawElements(
const excalidrawElements = convertToExcalidrawElements(
elements as ExcalidrawElementSkeleton[],
opts,
);
expect(excaldrawElements.length).toBe(1);
expect(excaldrawElements[0]).toMatchSnapshot({
expect(excalidrawElements.length).toBe(1);
expect(excalidrawElements[0]).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
});
@ -840,4 +840,130 @@ describe("Test Transform", () => {
createdBy: "user01",
});
});
it("should transform the elements correctly when linear elements have single point", () => {
const elements: ExcalidrawElementSkeleton[] = [
{
id: "B",
type: "rectangle",
groupIds: ["subgraph_group_B"],
x: 0,
y: 0,
width: 166.03125,
height: 163,
label: {
groupIds: ["subgraph_group_B"],
text: "B",
fontSize: 20,
verticalAlign: "top",
},
},
{
id: "A",
type: "rectangle",
groupIds: ["subgraph_group_A"],
x: 364.546875,
y: 0,
width: 120.265625,
height: 114,
label: {
groupIds: ["subgraph_group_A"],
text: "A",
fontSize: 20,
verticalAlign: "top",
},
},
{
id: "Alice",
type: "rectangle",
groupIds: ["subgraph_group_A"],
x: 389.546875,
y: 35,
width: 70.265625,
height: 44,
strokeWidth: 2,
label: {
groupIds: ["subgraph_group_A"],
text: "Alice",
fontSize: 20,
},
link: null,
},
{
id: "Bob",
type: "rectangle",
groupIds: ["subgraph_group_B"],
x: 54.76953125,
y: 35,
width: 56.4921875,
height: 44,
strokeWidth: 2,
label: {
groupIds: ["subgraph_group_B"],
text: "Bob",
fontSize: 20,
},
link: null,
},
{
id: "Bob_Alice",
type: "arrow",
groupIds: [],
x: 111.262,
y: 57,
strokeWidth: 2,
points: [
[0, 0],
[272.985, 0],
],
label: {
text: "How are you?",
fontSize: 20,
groupIds: [],
},
roundness: {
type: 2,
},
start: {
id: "Bob",
},
end: {
id: "Alice",
},
},
{
id: "Bob_B",
type: "arrow",
groupIds: [],
x: 77.017,
y: 79,
strokeWidth: 2,
points: [[0, 0]],
label: {
text: "Friendship",
fontSize: 20,
groupIds: [],
},
roundness: {
type: 2,
},
start: {
id: "Bob",
},
end: {
id: "B",
},
},
];
const excalidrawElements = convertToExcalidrawElements(elements, opts);
expect(excalidrawElements.length).toBe(12);
excalidrawElements.forEach((ele) => {
expect(ele).toMatchSnapshot({
seed: expect.any(Number),
versionNonce: expect.any(Number),
id: expect.any(String),
});
});
});
});

View File

@ -405,11 +405,21 @@ const bindLinearElementToElement = (
}
}
// Safe check to early return for single point
if (linearElement.points.length < 2) {
return {
linearElement,
startBoundElement,
endBoundElement,
};
}
// Update start/end points by 0.5 so bindings don't overlap with start/end bound element coordinates.
const endPointIndex = linearElement.points.length - 1;
const delta = 0.5;
const newPoints = cloneJSON(linearElement.points) as [number, number][];
// left to right so shift the arrow towards right
if (
linearElement.points[endPointIndex][0] >

View File

@ -673,6 +673,8 @@ export const arrayToMapWithIndex = <T extends { id: string }>(
export const isTestEnv = () => import.meta.env.MODE === "test";
export const isDevEnv = () => import.meta.env.MODE === "development";
export const wrapEvent = <T extends Event>(name: EVENT, nativeEvent: T) => {
return new CustomEvent(name, {
detail: {