From a55f5665745aaa1986234a50353b3fea6608fff6 Mon Sep 17 00:00:00 2001 From: Marco Braak Date: Thu, 26 Dec 2024 21:46:56 +0100 Subject: [PATCH] Add tests (#872) * Test onCanMove * Test onCanMoveTo * generateHtmlElementsForTree: create li and div with correct classes * generateHtmlElementsForNode: create ul * Test mouseStop * Add test * Remove unused code * Test onDragStop --- src/test/dragAndDropHandler/index.test.ts | 365 ++++++++++++++++++++-- src/test/support/testUtil.ts | 29 +- 2 files changed, 363 insertions(+), 31 deletions(-) diff --git a/src/test/dragAndDropHandler/index.test.ts b/src/test/dragAndDropHandler/index.test.ts index b8f80fad..9728a55a 100644 --- a/src/test/dragAndDropHandler/index.test.ts +++ b/src/test/dragAndDropHandler/index.test.ts @@ -1,36 +1,54 @@ import { DragAndDropHandler } from "../../dragAndDropHandler"; import { GetTree } from "../../jqtreeMethodTypes"; -import { DragMethod, OnIsMoveHandle } from "../../jqtreeOptions"; +import { + DragMethod, + OnCanMove, + OnCanMoveTo, + OnIsMoveHandle, +} from "../../jqtreeOptions"; import { Node } from "../../node"; import NodeElement from "../../nodeElement"; import { generateHtmlElementsForTree } from "../support/testUtil"; interface CreateDragAndDropHandlerParams { getTree?: GetTree; + onCanMove?: OnCanMove; + onCanMoveTo?: OnCanMoveTo; onDragMove?: DragMethod; + onDragStop?: DragMethod; onIsMoveHandle?: OnIsMoveHandle; tree: Node; } const createDragAndDropHandler = ({ getTree, + onCanMove, + onCanMoveTo, onDragMove, + onDragStop, onIsMoveHandle, tree, }: CreateDragAndDropHandlerParams) => { const getScrollLeft = jest.fn(); const openNode = jest.fn(); const refreshElements = jest.fn(); - const triggerEvent = jest.fn(); - const elementForTree = generateHtmlElementsForTree(tree); + const treeElement = generateHtmlElementsForTree(tree); + + const triggerEvent = jest.fn( + (eventName: string, values?: Record) => { + const event = jQuery.Event(eventName, values); + jQuery(treeElement).trigger(event); + return event; + }, + ); const getNodeElementForNode = jest.fn( (node: Node) => new NodeElement({ getScrollLeft, node, - treeElement: elementForTree, + treeElement: treeElement, }), ); @@ -54,27 +72,32 @@ const createDragAndDropHandler = ({ return new NodeElement({ getScrollLeft, node: resultNode, - treeElement: elementForTree, + treeElement, }); } else { return null; } }); - return new DragAndDropHandler({ + const dragAndDropHandler = new DragAndDropHandler({ getNodeElement, getNodeElementForNode, getScrollLeft, getTree: getTree ?? jest.fn(() => tree), + onCanMove, + onCanMoveTo, onDragMove, + onDragStop, onIsMoveHandle, openFolderDelay: false, openNode, refreshElements, slide: false, - treeElement: elementForTree, + treeElement: treeElement, triggerEvent, }); + + return { dragAndDropHandler, triggerEvent }; }; beforeEach(() => { @@ -89,7 +112,7 @@ describe(".mouseCapture", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); expect(dragAndDropHandler.currentItem).toBeNull(); const positionInfo = { @@ -112,7 +135,7 @@ describe(".mouseCapture", () => { const element = document.createElement("div"); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); const positionInfo = { originalEvent: new Event("click"), @@ -125,14 +148,14 @@ describe(".mouseCapture", () => { expect(dragAndDropHandler.currentItem).toBeNull(); }); - it("capures the node when an element inside a node element is clicked", () => { + it("captures the node when an element inside a node element is clicked", () => { const tree = new Node(null, true); const node1 = new Node({ name: "node1" }); tree.addChild(node1); const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); const element = document.createElement("div"); (node2.element as HTMLElement).appendChild(element); @@ -155,7 +178,7 @@ describe(".mouseCapture", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); const element = document.createElement("input"); (node2.element as HTMLElement).appendChild(element); @@ -181,7 +204,7 @@ describe(".mouseCapture", () => { const onIsMoveHandle = jest.fn( (jQueryElement: JQuery) => jQueryElement.get(0) === node1.element, ); - const dragAndDropHandler = createDragAndDropHandler({ + const { dragAndDropHandler } = createDragAndDropHandler({ onIsMoveHandle, tree, }); @@ -207,7 +230,7 @@ describe(".mouseCapture", () => { tree.addChild(node2); const onIsMoveHandle = jest.fn(() => false); - const dragAndDropHandler = createDragAndDropHandler({ + const { dragAndDropHandler } = createDragAndDropHandler({ onIsMoveHandle, tree, }); @@ -224,6 +247,66 @@ describe(".mouseCapture", () => { expect(onIsMoveHandle).toHaveBeenCalled(); }); + + it("doesn't capture when onCanMove returns false", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const onCanMove = jest.fn(() => false); + + const { dragAndDropHandler } = createDragAndDropHandler({ + onCanMove, + tree, + }); + + const element = document.createElement("div"); + (node2.element as HTMLElement).appendChild(element); + + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 30, + target: element, + }; + + expect(dragAndDropHandler.mouseCapture(positionInfo)).toBeFalse(); + expect(dragAndDropHandler.currentItem).toBeNull(); + + expect(onCanMove).toHaveBeenCalledWith(node2); + }); + + it("captures when onCanMove returns true", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const onCanMove = jest.fn(() => true); + + const { dragAndDropHandler } = createDragAndDropHandler({ + onCanMove, + tree, + }); + + const element = document.createElement("div"); + (node2.element as HTMLElement).appendChild(element); + + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 30, + target: element, + }; + + expect(dragAndDropHandler.mouseCapture(positionInfo)).toBeTrue(); + expect(dragAndDropHandler.currentItem?.node).toEqual(node2); + + expect(onCanMove).toHaveBeenCalledWith(node2); + }); }); describe(".mouseStart", () => { @@ -234,7 +317,7 @@ describe(".mouseStart", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Set current item const positionInfo = { @@ -260,7 +343,7 @@ describe(".mouseStart", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Set current item const positionInfo = { originalEvent: new Event("click"), @@ -284,7 +367,7 @@ describe(".mouseStart", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Set current item const positionInfo = { @@ -309,7 +392,7 @@ describe(".mouseStart", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); const positionInfo = { originalEvent: new Event("click"), @@ -323,6 +406,156 @@ describe(".mouseStart", () => { }); }); +describe(".mouseStop", () => { + it("triggers a tree.move event", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const { dragAndDropHandler, triggerEvent } = createDragAndDropHandler({ + tree, + }); + + // Capture + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 10, + target: node1.element as HTMLElement, + }; + + dragAndDropHandler.mouseCapture(positionInfo); + expect(dragAndDropHandler.currentItem?.node).toBe(node1); + + // Start + expect(dragAndDropHandler.mouseStart(positionInfo)).toBeTrue(); + expect(dragAndDropHandler.isDragging).toBeTrue(); + + // Drag + const dragPositionInfo = { + originalEvent: new Event("mousemove"), + pageX: 15, + pageY: 30, + target: node2.element as HTMLElement, + }; + + dragAndDropHandler.mouseDrag(dragPositionInfo); + expect(dragAndDropHandler.hoveredArea?.node).toEqual(node2); + + // Stop + dragAndDropHandler.mouseStop(dragPositionInfo); + + expect(triggerEvent).toHaveBeenCalledWith( + "tree.move", + expect.objectContaining({ + move_info: { + do_move: expect.any(Function) as unknown, + moved_node: node1, + original_event: dragPositionInfo.originalEvent, + position: "inside", + previous_parent: tree, + target_node: node2, + }, + }), + ); + }); + + it("calls tree.moveNode", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const mockMoveNode = jest.spyOn(tree, "moveNode"); + + const { dragAndDropHandler } = createDragAndDropHandler({ + tree, + }); + + // Capture + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 10, + target: node1.element as HTMLElement, + }; + + dragAndDropHandler.mouseCapture(positionInfo); + expect(dragAndDropHandler.currentItem?.node).toBe(node1); + + // Start + expect(dragAndDropHandler.mouseStart(positionInfo)).toBeTrue(); + expect(dragAndDropHandler.isDragging).toBeTrue(); + + // Drag + const dragPositionInfo = { + originalEvent: new Event("mousemove"), + pageX: 15, + pageY: 30, + target: node2.element as HTMLElement, + }; + + dragAndDropHandler.mouseDrag(dragPositionInfo); + expect(dragAndDropHandler.hoveredArea?.node).toEqual(node2); + + // Stop + dragAndDropHandler.mouseStop(dragPositionInfo); + + expect(mockMoveNode).toHaveBeenCalledWith(node1, node2, "inside"); + }); + + it("calls onDragStop when there is no hovered area", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const onDragStop = jest.fn(); + + const { dragAndDropHandler } = createDragAndDropHandler({ + onDragStop, + tree, + }); + + // Start dragging + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 10, + target: node1.element as HTMLElement, + }; + + dragAndDropHandler.mouseCapture(positionInfo); + + dragAndDropHandler.mouseStart(positionInfo); + expect(dragAndDropHandler.isDragging).toBeTrue(); + + // Move mouse + dragAndDropHandler.mouseDrag({ + originalEvent: new Event("mousemove"), + pageX: 15, + pageY: 30, + target: node2.element as HTMLElement, + }); + + // Stop + const originalEvent = new Event("mousemove"); + + dragAndDropHandler.mouseStop({ + originalEvent, + pageX: 300, + pageY: 300, + target: document.body, + }); + + expect(onDragStop).toHaveBeenCalledWith(node1, originalEvent); + }); +}); + describe(".mouseDrag", () => { it("moves the drag element and returns true", () => { const tree = new Node(null, true); @@ -331,7 +564,7 @@ describe(".mouseDrag", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Start dragging const positionInfo = { @@ -370,7 +603,7 @@ describe(".mouseDrag", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Start dragging const positionInfo = { @@ -404,6 +637,42 @@ describe(".mouseDrag", () => { ); }); + it("creates a border drop hint", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); + + // Start dragging + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 10, + target: node1.element as HTMLElement, + }; + + dragAndDropHandler.mouseCapture(positionInfo); + + dragAndDropHandler.mouseStart(positionInfo); + expect(dragAndDropHandler.isDragging).toBeTrue(); + expect(dragAndDropHandler.hoveredArea).toBeNull(); + + // Move mouse + dragAndDropHandler.mouseDrag({ + originalEvent: new Event("mousemove"), + pageX: 15, + pageY: 30, + target: node2.element as HTMLElement, + }); + + expect( + node2.element?.querySelector(".jqtree-border"), + ).toBeInTheDocument(); + }); + it("returns false when dragging hasn't started", () => { const tree = new Node(null, true); const node1 = new Node({ name: "node1" }); @@ -411,7 +680,7 @@ describe(".mouseDrag", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); const dragResult = dragAndDropHandler.mouseDrag({ originalEvent: new Event("mousemove"), @@ -429,7 +698,7 @@ describe(".mouseDrag", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ + const { dragAndDropHandler } = createDragAndDropHandler({ tree, }); @@ -464,7 +733,7 @@ describe(".mouseDrag", () => { const onDragMove = jest.fn(); - const dragAndDropHandler = createDragAndDropHandler({ + const { dragAndDropHandler } = createDragAndDropHandler({ onDragMove, tree, }); @@ -498,6 +767,49 @@ describe(".mouseDrag", () => { positionInfoForDragging.originalEvent, ); }); + + it("doesn't create a drop hint when onCanMoveTo returns false", () => { + const tree = new Node(null, true); + const node1 = new Node({ name: "node1" }); + tree.addChild(node1); + const node2 = new Node({ name: "node2" }); + tree.addChild(node2); + + const onCanMoveTo = jest.fn(() => false); + + const { dragAndDropHandler } = createDragAndDropHandler({ + onCanMoveTo, + tree, + }); + + // Start dragging + const positionInfo = { + originalEvent: new Event("click"), + pageX: 10, + pageY: 10, + target: node1.element as HTMLElement, + }; + + dragAndDropHandler.mouseCapture(positionInfo); + + dragAndDropHandler.mouseStart(positionInfo); + expect(dragAndDropHandler.isDragging).toBeTrue(); + + // Move mouse + dragAndDropHandler.mouseDrag({ + originalEvent: new Event("mousemove"), + pageX: 15, + pageY: 30, + target: node2.element as HTMLElement, + }); + + expect(onCanMoveTo).toHaveBeenCalledWith(node1, node2, "inside"); + + // Still sets hoveredArea to the new node + expect(dragAndDropHandler.hoveredArea?.node).toEqual(node2); + + expect(node2.element?.querySelector(".jqtree-border")).toBeNull(); + }); }); describe(".refresh", () => { @@ -508,7 +820,7 @@ describe(".refresh", () => { const node2 = new Node({ name: "node2" }); tree.addChild(node2); - const dragAndDropHandler = createDragAndDropHandler({ tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ tree }); // Set current item const positionInfo = { @@ -549,7 +861,10 @@ describe(".refresh", () => { const getTree = jest.fn(() => null); - const dragAndDropHandler = createDragAndDropHandler({ getTree, tree }); + const { dragAndDropHandler } = createDragAndDropHandler({ + getTree, + tree, + }); // Set current item const positionInfo = { diff --git a/src/test/support/testUtil.ts b/src/test/support/testUtil.ts index 54f66085..8aca1854 100644 --- a/src/test/support/testUtil.ts +++ b/src/test/support/testUtil.ts @@ -47,18 +47,35 @@ export const mockLayout = (element: HTMLElement, rect: Rect) => { export const generateHtmlElementsForTree = (tree: Node) => { let y = 0; + const createNodeElement = (node: Node) => { + const isTree = node.tree === node; + + if (isTree) { + const element = document.createElement("ul"); + element.className = "jqtree-tree"; + return element; + } else { + return document.createElement("li"); + } + }; + function generateHtmlElementsForNode( node: Node, parentElement: HTMLElement, x: number, ) { const isTree = node.tree === node; - const element = document.createElement("div"); - parentElement.append(element); + const nodeElement = createNodeElement(node); + + parentElement.append(nodeElement); if (!isTree) { - mockLayout(element, { height: 20, width: 100 - x, x, y }); - node.element = element; + const divElement = document.createElement("div"); + divElement.className = "jqtree-element"; + nodeElement.append(divElement); + + mockLayout(nodeElement, { height: 20, width: 100 - x, x, y }); + node.element = nodeElement; y += 20; } @@ -66,13 +83,13 @@ export const generateHtmlElementsForTree = (tree: Node) => { for (const child of node.children) { generateHtmlElementsForNode( child, - element, + nodeElement, isTree ? x : x + 10, ); } } - return element; + return nodeElement; } const treeElement = generateHtmlElementsForNode(tree, document.body, 0);