/* * Copyright 2017 Palantir Technologies, Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { render } from "@testing-library/react"; import sinon from "sinon"; import { describe, expect, it } from "@blueprintjs/test-commons/vitest"; import { Regions } from "../"; import { FocusMode } from "../common/cellTypes"; import { ElementHarness } from "../harness"; import { DragReorderable } from "./reorderable"; const ELEMENT_CLASS = "element"; const ELEMENT_SELECTOR = `.${ELEMENT_CLASS}`; const OLD_INDEX = 0; const NEW_INDEX = 1; const SINGLE_LENGTH = 1; const MULTI_LENGTH = 2; const GUIDE_INDEX_SINGLE_CASE = NEW_INDEX + SINGLE_LENGTH; const GUIDE_INDEX_MULTI_CASE = NEW_INDEX + MULTI_LENGTH; describe("DragReorderable", () => { const children = (
Zero
One
Two
Three
Four
); describe("has no effect if", () => { it("clicked region is invalid", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(-1)); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); expect(callbacks.onReordering.called).to.be.false; expect(callbacks.onReordered.called).to.be.false; expect(callbacks.onSelection.called).to.be.false; }); it("an existing selection contains the clicked region but has a different cardinality (e.g. FULL_TABLE)", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(OLD_INDEX); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); expect(callbacks.onReordering.called).to.be.false; expect(callbacks.onReordered.called).to.be.false; expect(callbacks.onSelection.called).to.be.false; }); it("disabled=true", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_SINGLE_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); expect(callbacks.onReordering.called).to.be.false; expect(callbacks.onReordered.called).to.be.false; expect(callbacks.onSelection.called).to.be.false; }); }); describe("general behavior", () => { it("selects the clicked region if the clicked region is not currently selected", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(OLD_INDEX); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); expect(callbacks.onReordering.called).to.be.true; expect(callbacks.onReordered.called).to.be.true; expect(callbacks.onSelection.called).to.be.true; }); it("invokes callbacks appropriately throughout the drag-reorder interaction", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_SINGLE_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove"); expect(callbacks.onReordering.calledWith(OLD_INDEX, NEW_INDEX, SINGLE_LENGTH)).to.be.true; expect(callbacks.onReordered.called).to.be.false; expect(callbacks.onSelection.called).to.be.false; element.mouse("mouseup"); expect(callbacks.onReordering.callCount).to.equal(2); // called on drag end too expect(callbacks.onReordered.calledWith(OLD_INDEX, NEW_INDEX, SINGLE_LENGTH)).to.be.true; expect(callbacks.onSelection.calledWith([Regions.column(NEW_INDEX)])).to.be.true; }); it("reorders a selection of one single element", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_SINGLE_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove"); expect(callbacks.onReordering.calledWith(OLD_INDEX, NEW_INDEX, SINGLE_LENGTH)).to.be.true; element.mouse("mouseup"); expect(callbacks.onReordered.calledWith(OLD_INDEX, NEW_INDEX, SINGLE_LENGTH)).to.be.true; expect(callbacks.onSelection.calledWith([Regions.column(NEW_INDEX)])).to.be.true; }); it("reorders a selection of multiple contiguous elements", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_MULTI_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove"); expect(callbacks.onReordering.calledWith(OLD_INDEX, NEW_INDEX, MULTI_LENGTH)).to.be.true; element.mouse("mouseup"); expect(callbacks.onReordered.calledWith(OLD_INDEX, NEW_INDEX, MULTI_LENGTH)).to.be.true; expect(callbacks.onSelection.calledWith([Regions.column(NEW_INDEX, NEW_INDEX + MULTI_LENGTH - 1)])).to.be .true; }); it("for a disjoint selection, reorders just the clicked region and clears all other selections", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_MULTI_CASE); // try moving a contiguous multi-column selection const selectedRegions = [ Regions.column(OLD_INDEX, OLD_INDEX + MULTI_LENGTH - 1), Regions.column(NEW_INDEX), Regions.column(NEW_INDEX + 1), ]; const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove"); expect(callbacks.onReordering.calledWith(OLD_INDEX, NEW_INDEX, MULTI_LENGTH)).to.be.true; element.mouse("mouseup"); expect(callbacks.onReordered.calledWith(OLD_INDEX, NEW_INDEX, MULTI_LENGTH)).to.be.true; expect(callbacks.onSelection.calledWith([Regions.column(NEW_INDEX, NEW_INDEX + MULTI_LENGTH - 1)])).to.be .true; }); it("does not invoke callbacks if nothing was reordered after drag", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(OLD_INDEX)); callbacks.locateDrag.returns(OLD_INDEX); // same index const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); expect(callbacks.onSelection.called).to.be.false; expect(callbacks.onFocusedRegion.called).to.be.false; }); }); describe("focused cell", () => { it("moves the focused cell into the region on mousedown if region was not already selected", () => { const SELECTED_INDEX = NEW_INDEX; const UNSELECTED_INDEX = OLD_INDEX; const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(UNSELECTED_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_SINGLE_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, UNSELECTED_INDEX); element.mouse("mousedown"); expect(callbacks.onFocusedRegion.called).to.be.true; expect(callbacks.onFocusedRegion.firstCall.args[0]).to.deep.equal({ col: UNSELECTED_INDEX, focusSelectionIndex: 0, row: 0, type: FocusMode.CELL, }); }); it("moves the focused cell into the newly selected region on drag end", () => { const callbacks = initCallbackStubs(); callbacks.locateClick.returns(Regions.column(NEW_INDEX)); callbacks.locateDrag.returns(GUIDE_INDEX_SINGLE_CASE); const { container } = render( {children} , ); const reorderable = new ElementHarness(container); const element = reorderable.find(ELEMENT_SELECTOR, OLD_INDEX); element.mouse("mousedown").mouse("mousemove").mouse("mouseup"); // called once on mousedown and again on mouseup expect(callbacks.onFocusedRegion.calledTwice).to.be.true; expect(callbacks.onFocusedRegion.secondCall.args[0]).to.deep.equal({ col: NEW_INDEX, focusSelectionIndex: 0, row: 0, type: FocusMode.CELL, }); }); }); function initCallbackStubs() { return { locateClick: sinon.stub(), locateDrag: sinon.stub(), onFocusedRegion: sinon.stub(), onReordered: sinon.stub(), onReordering: sinon.stub(), onSelection: sinon.stub(), }; } // create these wrapper functions to avoid errors with `this` binding when passing // Regions.column / Regions.row as callbacks directly function toFullColumnRegion(index1: number, index2?: number) { return Regions.column(index1, index2); } });