diff --git a/src/features/boards/board.tsx b/src/features/boards/board.tsx index 845f96d..9912c2f 100644 --- a/src/features/boards/board.tsx +++ b/src/features/boards/board.tsx @@ -418,6 +418,7 @@ export function placeInAvailableSpace( : Math.abs(nodeToPlace.y - nodeToCheck.y); // If we're too close to this node, move further + // Keep in mind positions start at top right, so "down" means increasing Y if (distance < radius) { if (direction === Direction.Right) { nodeToPlace.x = nodeToCheck.x + radius; diff --git a/tests/features/board.test.ts b/tests/features/board.test.ts new file mode 100644 index 0000000..6aa0cad --- /dev/null +++ b/tests/features/board.test.ts @@ -0,0 +1,108 @@ +import { + NodePosition, + placeInAvailableSpace, + setupUniqueIds, + unwrapNodeRef +} from "features/boards/board"; +import { beforeEach, describe, expect, test } from "vitest"; +import { Ref, ref } from "vue"; +import "../utils"; +import { Direction } from "util/common"; + +describe("Unwraps node refs", () => { + test("Static value", () => expect(unwrapNodeRef(100, {})).toBe(100)); + test("Ref value", () => expect(unwrapNodeRef(ref(100), {})).toBe(100)); + test("0 param function value", () => expect(unwrapNodeRef(() => 100, {})).toBe(100)); + test("1 param function value", () => { + const actualNode = { foo: "bar" }; + expect( + unwrapNodeRef(function (node) { + if (node === actualNode) { + return true; + } + return false; + }, actualNode) + ).toBe(true); + }); +}); + +describe("Set up unique IDs", () => { + let nodes: Ref<{ id: number }[]>, nextId: Ref; + beforeEach(() => { + nodes = ref([]); + nextId = setupUniqueIds(nodes); + }); + test("Starts at 0", () => expect(nextId?.value).toBe(0)); + test("Calculates initial value properly", () => { + nodes.value = [{ id: 0 }, { id: 1 }, { id: 2 }]; + expect(nextId.value).toBe(3); + }); + test("Non consecutive IDs", () => { + nodes.value = [{ id: -5 }, { id: 0 }, { id: 200 }]; + expect(nextId.value).toBe(201); + }); + test("After modification", () => { + nodes.value = [{ id: 0 }, { id: 1 }, { id: 2 }]; + nodes.value.push({ id: nextId.value }); + expect(nextId.value).toBe(4); + }); +}); + +describe("Place in available space", () => { + let nodes: Ref, node: NodePosition; + beforeEach(() => { + nodes = ref([]); + node = { x: 10, y: 20 }; + }); + test("No nodes", () => { + placeInAvailableSpace(node, nodes.value); + expect(node).toMatchObject({ x: 10, y: 20 }); + }); + test("Moves node", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value); + expect(node).not.toMatchObject({ x: 10, y: 20 }); + }); + describe("Respects radius", () => { + test("Positions radius away", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).toMatchObject({ x: 42, y: 20 }); + }); + test("Ignores node already radius away", () => { + nodes.value = [{ x: 42, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).toMatchObject({ x: 10, y: 20 }); + }); + test("Doesn't ignore node just under radius away", () => { + nodes.value = [{ x: 41, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 32); + expect(node).not.toMatchObject({ x: 10, y: 20 }); + }); + }); + describe("Respects direction", () => { + test("Goes left", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Left); + expect(node).toMatchObject({ x: 0, y: 20 }); + }); + test("Goes up", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Up); + expect(node).toMatchObject({ x: 10, y: 10 }); + }); + test("Goes down", () => { + nodes.value = [{ x: 10, y: 20 }]; + placeInAvailableSpace(node, nodes.value, 10, Direction.Down); + expect(node).toMatchObject({ x: 10, y: 30 }); + }); + }); + test("Finds hole", () => { + nodes.value = [ + { x: 10, y: 20 }, + { x: 30, y: 20 } + ]; + placeInAvailableSpace(node, nodes.value, 10); + expect(node).toMatchObject({ x: 20, y: 20 }); + }); +});