/*
* 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 = (
);
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);
}
});