/** * Copyright 2016 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 * as Classes from "./common/classes"; import { Clipboard } from "./common/clipboard"; import { Utils } from "./common/utils"; import { ElementHarness } from "./harness"; import { createTableOfSize } from "./mocks/table"; import { RegionCardinality, Regions, SelectionModes } from "."; describe("Selection", () => { const COLUMN_TH_SELECTOR = `.${Classes.TABLE_QUADRANT_MAIN} .${Classes.TABLE_COLUMN_HEADERS} .${Classes.TABLE_HEADER}`; const ROW_TH_SELECTOR = `.${Classes.TABLE_QUADRANT_MAIN} .${Classes.TABLE_ROW_HEADERS} .${Classes.TABLE_HEADER}`; const CELL_SELECTOR = `.${Classes.TABLE_QUADRANT_MAIN} .${Classes.rowCellIndexClass( 2, )}.${Classes.columnCellIndexClass(0)}`; it("Selects a single column on click", () => { const onSelection = sinon.spy(); const onFocusedCell = sinon.spy(); const { container } = render( createTableOfSize(3, 7, {}, { enableFocusedCell: true, onFocusedCell, onSelection }), ); const table = new ElementHarness(container); table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.called).to.equal(true); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]]); expect(onFocusedCell.called).to.equal(true); expect(onFocusedCell.lastCall.args).to.deep.equal([{ col: 0, focusSelectionIndex: 0, row: 0 }]); }); // skip: requires real browser layout engine (jsdom limitation — initKeyboardEvent not supported) it.skip("Copies selected cells when keys are pressed", () => { const onCopy = sinon.spy(); const getCellClipboardData = Utils.toBase26CellName; const copyCellsStub = sinon.stub(Clipboard, "copyCells").returns(Promise.resolve()); const { container } = render(createTableOfSize(3, 7, {}, { getCellClipboardData, onCopy })); const table = new ElementHarness(container); table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); table.find(COLUMN_TH_SELECTOR).focus(); table.find(COLUMN_TH_SELECTOR).keyboard("keydown", "C", true); // wait for copyCells to finish setTimeout(() => { expect(copyCellsStub.lastCall.args).to.deep.equal([ [["A1"], ["A2"], ["A3"], ["A4"], ["A5"], ["A6"], ["A7"]], ]); expect(onCopy.lastCall.args).to.deep.equal([true]); }, 100); }); it("De-selects on table body click", () => { const onSelection = sinon.spy(); const { container } = render( createTableOfSize( 3, 7, {}, { onSelection, selectionModes: SelectionModes.COLUMNS_ONLY, }, ), ); const table = new ElementHarness(container); table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.called).to.equal(true); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]]); onSelection.resetHistory(); table .find(`.${Classes.rowCellIndexClass(1)}.${Classes.columnCellIndexClass(1)}`) .mouse("mousedown") .mouse("mouseup"); expect(onSelection.called).to.equal(true); expect(onSelection.lastCall.args).to.deep.equal([[]]); }); it("Row selection works when enabled", () => { const onSelection = sinon.spy(); const selectionModes = [RegionCardinality.FULL_COLUMNS, RegionCardinality.FULL_ROWS]; const { container } = render(createTableOfSize(3, 7, {}, { onSelection, selectionModes })); const table = new ElementHarness(container); // select a column to ensure it deselects when we click the row table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); onSelection.resetHistory(); // select a row table.find(ROW_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.called, "onSelection should be called on select").to.equal(true); expect(onSelection.lastCall.args, "selected region should be first row").to.deep.equal([[Regions.row(0)]]); onSelection.resetHistory(); // deselects on cmd+click table.find(ROW_TH_SELECTOR).mouse("mousedown", { metaKey: true }).mouse("mouseup"); expect(onSelection.called, "onSelection should be called on deselect").to.equal(true, "cmd+click to deselect"); expect(onSelection.lastCall.args, "selected region should be empty").to.deep.equal([[]]); onSelection.resetHistory(); }); it("Column selection works when enabled", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); // initial selection table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.called).to.equal(true, "first select"); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]]); onSelection.resetHistory(); // deselects on cmd+click table.find(COLUMN_TH_SELECTOR).mouse("mousedown", { metaKey: true }).mouse("mouseup"); expect(onSelection.called).to.equal(true, "cmd+click to deselect"); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[]]); onSelection.resetHistory(); // re-select table.find(COLUMN_TH_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]], "second select"); onSelection.resetHistory(); // clears even with meta key const isMetaKeyDown = true; table.find(COLUMN_TH_SELECTOR).mouse("mousedown", 0, 0, isMetaKeyDown).mouse("mouseup", 0, 0, isMetaKeyDown); expect(onSelection.called).to.equal(true); expect(onSelection.lastCall.args).to.deep.equal([[]], "meta key clear"); }); it("Keeps same column selected and reinvokes onSelection when clicked again", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); // leaves the selection in place on re-click const column = table.find(COLUMN_TH_SELECTOR); column.mouse("mousedown").mouse("mouseup"); column.mouse("mousedown").mouse("mouseup"); expect(onSelection.callCount).to.equal(2); }); it("Keeps same row selected and reinvokes onSelection when clicked again", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); // leaves the selection in place on re-click const row = table.find(ROW_TH_SELECTOR); row.mouse("mousedown").mouse("mouseup"); row.mouse("mousedown").mouse("mouseup"); expect(onSelection.callCount).to.equal(2); }); it("Transforms regions on selections", () => { const selectedRegionTransform = () => { return Regions.row(1); }; const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection, selectedRegionTransform })); const table = new ElementHarness(container); // clicking adds transformed selection table.find(CELL_SELECTOR).mouse("mousedown").mouse("mouseup"); expect(onSelection.called).to.be.true; expect(onSelection.lastCall.args).to.deep.equal([[Regions.row(1)]]); }); it("Accepts controlled selection", () => { const { container } = render(createTableOfSize(3, 7, {}, { selectedRegions: [Regions.row(0)] })); const table = new ElementHarness(container); const selectionRegion = table.find(`.${Classes.TABLE_SELECTION_REGION}`); expect(selectionRegion.element).to.exist; }); // TODO fix these tests on CircleCI. // // These tests pass locally. They are disabled because in the linux // headless browser, the flexbox layout causes the header cells to stack // vertically instead of in a row. This causes the locator to fail // calculating the correct column index because it assumes a row layout // for the header cells. // // (bdwyer) I have manually tested the rendering of the table on linux on // the actual CircleCI node in both chrome and firefox, and everything // looks/works fine. So, for now, I just disable the tests and note the // issue in #126. it.skip("Meta key is additive selection", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); table.find(COLUMN_TH_SELECTOR, 0).mouse("mousedown").mouse("mouseup"); expect(onSelection.called).to.equal(true, "first select called"); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]]); onSelection.resetHistory(); const isMetaKeyDown = true; table.find(COLUMN_TH_SELECTOR, 1).mouse("mousedown", 0, 0, isMetaKeyDown).mouse("mouseup", 0, 0, isMetaKeyDown); expect(onSelection.called).to.equal(true, "second select called"); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0), Regions.column(1)]]); }); it.skip("Drag select creates multiple selections", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); table.find(COLUMN_TH_SELECTOR).mouse("mousedown"); table.find(COLUMN_TH_SELECTOR, 1).mouse("mousemove").mouse("mouseup"); expect(onSelection.called).to.equal(true); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0, 1)]]); }); it("Meta-click for initial selection works", () => { const onSelection = sinon.spy(); const { container } = render(createTableOfSize(3, 7, {}, { onSelection })); const table = new ElementHarness(container); // initial selection const isMetaKeyDown = true; table.find(COLUMN_TH_SELECTOR).mouse("mousedown", 0, 0, isMetaKeyDown).mouse("mouseup", 0, 0, isMetaKeyDown); expect(onSelection.called).to.equal(true, "first select"); expect(onSelection.lastCall.args).to.have.lengthOf(1); expect(onSelection.lastCall.args).to.deep.equal([[Regions.column(0)]]); }); });