diff options
| author | kj_sh604 | 2026-03-15 16:19:35 -0400 |
|---|---|---|
| committer | kj_sh604 | 2026-03-15 16:19:35 -0400 |
| commit | 6ec259a0e71174651bae95d4628138bf6fd68742 (patch) | |
| tree | 5e33c6a5ec091ecabfcb257fdc7b6a88ed8754ac /packages/excalidraw/element/flowchart.test.tsx | |
| parent | 16c8578b15c727f22921f8a80a56ee4d4e7f2272 (diff) | |
refactor: packages/
Diffstat (limited to 'packages/excalidraw/element/flowchart.test.tsx')
| -rw-r--r-- | packages/excalidraw/element/flowchart.test.tsx | 403 |
1 files changed, 403 insertions, 0 deletions
diff --git a/packages/excalidraw/element/flowchart.test.tsx b/packages/excalidraw/element/flowchart.test.tsx new file mode 100644 index 0000000..d47c850 --- /dev/null +++ b/packages/excalidraw/element/flowchart.test.tsx @@ -0,0 +1,403 @@ +import { render, unmountComponent } from "../tests/test-utils"; +import { reseed } from "../random"; +import { UI, Keyboard, Pointer } from "../tests/helpers/ui"; +import { Excalidraw } from "../index"; +import { API } from "../tests/helpers/api"; +import { KEYS } from "../keys"; + +unmountComponent(); + +const { h } = window; +const mouse = new Pointer("mouse"); + +beforeEach(async () => { + localStorage.clear(); + reseed(7); + mouse.reset(); + + await render(<Excalidraw handleKeyboardGlobally={true} />); + h.state.width = 1000; + h.state.height = 1000; + + // The bounds of hand-drawn linear elements may change after flipping, so + // removing this style for testing + UI.clickTool("arrow"); + UI.clickByTitle("Architect"); + UI.clickTool("selection"); +}); + +describe("flow chart creation", () => { + beforeEach(() => { + API.clearSelection(); + const rectangle = API.createElement({ + type: "rectangle", + width: 200, + height: 100, + }); + + API.setElements([rectangle]); + API.setSelectedElements([rectangle]); + }); + + // multiple at once + it("create multiple successor nodes at once", () => { + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(5); + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(3); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(2); + }); + + it("when directions are changed, only the last same directions will apply", () => { + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + + Keyboard.keyPress(KEYS.ARROW_LEFT); + + Keyboard.keyPress(KEYS.ARROW_UP); + Keyboard.keyPress(KEYS.ARROW_UP); + Keyboard.keyPress(KEYS.ARROW_UP); + }); + + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(7); + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(4); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(3); + }); + + it("when escaped, no nodes will be created", () => { + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_UP); + Keyboard.keyPress(KEYS.ARROW_DOWN); + }); + + Keyboard.keyPress(KEYS.ESCAPE); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(1); + }); + + it("create nodes one at a time", () => { + const initialNode = h.elements[0]; + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(3); + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(2); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(1); + + const firstChildNode = h.elements.filter( + (el) => el.type === "rectangle" && el.id !== initialNode.id, + )[0]; + expect(firstChildNode).not.toBe(null); + expect(firstChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]); + + API.setSelectedElements([initialNode]); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(5); + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(3); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(2); + + const secondChildNode = h.elements.filter( + (el) => + el.type === "rectangle" && + el.id !== initialNode.id && + el.id !== firstChildNode.id, + )[0]; + expect(secondChildNode).not.toBe(null); + expect(secondChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]); + + API.setSelectedElements([initialNode]); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.length).toBe(7); + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(4); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(3); + + const thirdChildNode = h.elements.filter( + (el) => + el.type === "rectangle" && + el.id !== initialNode.id && + el.id !== firstChildNode.id && + el.id !== secondChildNode.id, + )[0]; + + expect(thirdChildNode).not.toBe(null); + expect(thirdChildNode.id).toBe(Object.keys(h.state.selectedElementIds)[0]); + + expect(firstChildNode.x).toBe(secondChildNode.x); + expect(secondChildNode.x).toBe(thirdChildNode.x); + }); +}); + +describe("flow chart navigation", () => { + it("single node at each level", () => { + /** + * ▨ -> ▨ -> ▨ -> ▨ -> ▨ + */ + + API.clearSelection(); + const rectangle = API.createElement({ + type: "rectangle", + width: 200, + height: 100, + }); + + API.setElements([rectangle]); + API.setSelectedElements([rectangle]); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + expect(h.elements.filter((el) => el.type === "rectangle").length).toBe(5); + expect(h.elements.filter((el) => el.type === "arrow").length).toBe(4); + + // all the way to the left, gets us to the first node + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rectangle.id]).toBe(true); + + // all the way to the right, gets us to the last node + const rightMostNode = h.elements[h.elements.length - 2]; + expect(rightMostNode); + expect(rightMostNode.type).toBe("rectangle"); + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true); + }); + + it("multiple nodes at each level", () => { + /** + * from the perspective of the first node, there're four layers, and + * there are four nodes at the second layer + * + * -> ▨ + * ▨ -> ▨ -> ▨ -> ▨ -> ▨ + * -> ▨ + * -> ▨ + */ + + API.clearSelection(); + const rectangle = API.createElement({ + type: "rectangle", + width: 200, + height: 100, + }); + + API.setElements([rectangle]); + API.setSelectedElements([rectangle]); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + const secondNode = h.elements[1]; + const rightMostNode = h.elements[h.elements.length - 2]; + + API.setSelectedElements([rectangle]); + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + API.setSelectedElements([rectangle]); + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + API.setSelectedElements([rectangle]); + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + API.setSelectedElements([rectangle]); + + // because of same level cycling, + // going right five times should take us back to the second node again + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[secondNode.id]).toBe(true); + + // from the second node, going right three times should take us to the rightmost node + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true); + + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rectangle.id]).toBe(true); + }); + + it("take the most obvious link when possible", () => { + /** + * ▨ → ▨ ▨ → ▨ + * ↓ ↑ + * ▨ → ▨ + */ + + API.clearSelection(); + const rectangle = API.createElement({ + type: "rectangle", + width: 200, + height: 100, + }); + + API.setElements([rectangle]); + API.setSelectedElements([rectangle]); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_DOWN); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_UP); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + Keyboard.withModifierKeys({ ctrl: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.CTRL_OR_CMD); + + // last node should be the one that's selected + const rightMostNode = h.elements[h.elements.length - 2]; + expect(rightMostNode.type).toBe("rectangle"); + expect(h.state.selectedElementIds[rightMostNode.id]).toBe(true); + + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + Keyboard.keyPress(KEYS.ARROW_LEFT); + }); + Keyboard.keyUp(KEYS.ALT); + + expect(h.state.selectedElementIds[rectangle.id]).toBe(true); + + // going any direction takes us to the predecessor as well + const predecessorToRightMostNode = h.elements[h.elements.length - 4]; + expect(predecessorToRightMostNode.type).toBe("rectangle"); + + API.setSelectedElements([rightMostNode]); + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_RIGHT); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true); + expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe( + true, + ); + API.setSelectedElements([rightMostNode]); + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_UP); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true); + expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe( + true, + ); + API.setSelectedElements([rightMostNode]); + Keyboard.withModifierKeys({ alt: true }, () => { + Keyboard.keyPress(KEYS.ARROW_DOWN); + }); + Keyboard.keyUp(KEYS.ALT); + expect(h.state.selectedElementIds[rightMostNode.id]).not.toBe(true); + expect(h.state.selectedElementIds[predecessorToRightMostNode.id]).toBe( + true, + ); + }); +}); |
