fix: fail gracefully during restore (#10673)
* fix: fail gracefully during restore * tests
This commit is contained in:
@@ -136,6 +136,7 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
||||
existingElementsMap: Readonly<ElementsMap> | null | undefined,
|
||||
startOrEnd: "start" | "end",
|
||||
): FixedPointBinding | null => {
|
||||
try {
|
||||
if (!binding) {
|
||||
return null;
|
||||
}
|
||||
@@ -228,6 +229,9 @@ const repairBinding = <T extends ExcalidrawArrowElement>(
|
||||
}
|
||||
|
||||
console.error(`could not repair binding for element`);
|
||||
} catch (error) {
|
||||
console.error("Error repairing binding:", error);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -639,8 +643,9 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
||||
if (element.type === "selection") {
|
||||
return elements;
|
||||
}
|
||||
|
||||
let migratedElement: ExcalidrawElement | null = restoreElement(
|
||||
let migratedElement: ExcalidrawElement | null;
|
||||
try {
|
||||
migratedElement = restoreElement(
|
||||
element,
|
||||
targetElementsMap,
|
||||
existingElementsMap,
|
||||
@@ -648,6 +653,10 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
||||
deleteInvisibleElements: opts?.deleteInvisibleElements,
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error restoring element:", error);
|
||||
migratedElement = null;
|
||||
}
|
||||
if (migratedElement) {
|
||||
const localElement = existingElementsMap?.get(element.id);
|
||||
|
||||
|
||||
@@ -1,5 +1,52 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`repairing bindings > should strip arrow binding if repair throws 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": "id-arrow01",
|
||||
"index": "a0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
100,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`restoreElements > should restore arrow element correctly 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
@@ -381,3 +428,50 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`restoreElements > should strip arrow binding if repair throws 1`] = `
|
||||
{
|
||||
"angle": 0,
|
||||
"backgroundColor": "transparent",
|
||||
"boundElements": [],
|
||||
"customData": undefined,
|
||||
"elbowed": false,
|
||||
"endArrowhead": null,
|
||||
"endBinding": null,
|
||||
"fillStyle": "solid",
|
||||
"frameId": null,
|
||||
"groupIds": [],
|
||||
"height": 100,
|
||||
"id": "id-arrow01",
|
||||
"index": "a0",
|
||||
"isDeleted": false,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"opacity": 100,
|
||||
"points": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
],
|
||||
[
|
||||
100,
|
||||
100,
|
||||
],
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
"type": "arrow",
|
||||
"updated": 1,
|
||||
"version": 2,
|
||||
"versionNonce": Any<Number>,
|
||||
"width": 100,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -200,6 +200,36 @@ describe("restoreElements", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should strip element if restore fails", () => {
|
||||
const rect1 = API.createElement({
|
||||
type: "rectangle",
|
||||
boundElements: [],
|
||||
});
|
||||
const rect2 = API.createElement({
|
||||
type: "rectangle",
|
||||
boundElements: [],
|
||||
});
|
||||
|
||||
// define getter for a property we access during restoreElement that throws
|
||||
Object.defineProperty(rect2, "seed", {
|
||||
get: () => {
|
||||
throw new Error("FORBIDDEN!");
|
||||
},
|
||||
});
|
||||
|
||||
const restoredElements = restore.restoreElements([rect1, rect2], null);
|
||||
|
||||
expect(restoredElements.length).toBe(1);
|
||||
|
||||
expect(restoredElements).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: rect1.id,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("should remove imperceptibly small elements", () => {
|
||||
const arrowElement = API.createElement({
|
||||
type: "arrow",
|
||||
@@ -717,6 +747,58 @@ describe("restoreAppState", () => {
|
||||
});
|
||||
|
||||
describe("repairing bindings", () => {
|
||||
it("should strip arrow binding if repair throws", () => {
|
||||
const container = API.createElement({
|
||||
type: "rectangle",
|
||||
boundElements: [],
|
||||
});
|
||||
const arrowElement = API.createElement({
|
||||
type: "arrow",
|
||||
id: "id-arrow01",
|
||||
endBinding: {
|
||||
elementId: container.id,
|
||||
fixedPoint: [0.5, 0.5],
|
||||
mode: "inside",
|
||||
},
|
||||
});
|
||||
|
||||
Object.assign(container, {
|
||||
boundElements: [{ type: "arrow", id: arrowElement.id }],
|
||||
});
|
||||
// make invalid binding -> throws during repair
|
||||
Object.assign(arrowElement, {
|
||||
endBinding: {
|
||||
// invalid reference
|
||||
elementId: 42,
|
||||
},
|
||||
});
|
||||
|
||||
const restoredElements = restore.restoreElements(
|
||||
[arrowElement, container],
|
||||
null,
|
||||
);
|
||||
|
||||
expect(restoredElements).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: arrowElement.id,
|
||||
endBinding: null,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: container.id,
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
const restoredArrow = restoredElements[0] as ExcalidrawLinearElement;
|
||||
|
||||
expect(restoredArrow).toMatchSnapshot({
|
||||
seed: expect.any(Number),
|
||||
versionNonce: expect.any(Number),
|
||||
// endBinding: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it("should repair container boundElements when repair is true", () => {
|
||||
const container = API.createElement({
|
||||
type: "rectangle",
|
||||
|
||||
Reference in New Issue
Block a user