From 7147462cbac0bbaeb83df928426317dedaee0e32 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sat, 2 Dec 2023 02:22:38 -0500 Subject: [PATCH 1/2] fix: revamp all cell selection range with key combos the previous key combo were incorrect, the new combo is the following - `Shift+Home` will select from current cell position to first cell horizontally - `Shift+End` will select from current cell position until the last cell horizontally - `Ctrl+Shift+Home` will select everything that is on the left side of current cell position and everything on top current position (horizontally left until first cell & vertically left until first row) - `Ctrl+Shift+End` will select everything that is on the right side of current cell position and everything on bottom current position (horizontally right until last cell & vertically right until last row) --- .../__tests__/slickCellSelectionModel.spec.ts | 99 +++++++++-- .../src/extensions/slickCellSelectionModel.ts | 31 +++- packages/utils/src/utils.spec.ts | 21 +++ packages/utils/src/utils.ts | 4 + test/cypress/e2e/example17.cy.ts | 8 +- test/cypress/e2e/example19.cy.ts | 165 ++++++++++++++---- test/cypress/support/commands.ts | 13 +- test/cypress/support/drag.ts | 10 +- 8 files changed, 285 insertions(+), 66 deletions(-) diff --git a/packages/common/src/extensions/__tests__/slickCellSelectionModel.spec.ts b/packages/common/src/extensions/__tests__/slickCellSelectionModel.spec.ts index 1d692a59a..71e5084da 100644 --- a/packages/common/src/extensions/__tests__/slickCellSelectionModel.spec.ts +++ b/packages/common/src/extensions/__tests__/slickCellSelectionModel.spec.ts @@ -10,11 +10,13 @@ const NB_ITEMS = 200; const CALCULATED_PAGE_ROW_COUNT = 23; // pageRowCount with our mocked sizes is 23 => ((600 - 17) / 25) jest.mock('flatpickr', () => { }); -const addVanillaEventPropagation = function (event, commandKey = '', keyName = '') { +const addVanillaEventPropagation = function (event, commandKeys: string[] = [], keyName = '') { Object.defineProperty(event, 'isPropagationStopped', { writable: true, configurable: true, value: jest.fn() }); Object.defineProperty(event, 'isImmediatePropagationStopped', { writable: true, configurable: true, value: jest.fn() }); - if (commandKey) { - Object.defineProperty(event, commandKey, { writable: true, configurable: true, value: true }); + if (commandKeys.length) { + for (const commandKey of commandKeys) { + Object.defineProperty(event, commandKey, { writable: true, configurable: true, value: true }); + } } if (keyName) { Object.defineProperty(event, 'key', { writable: true, configurable: true, value: keyName }); @@ -38,6 +40,12 @@ const dataViewStub = { getPagingInfo: () => ({ pageSize: 0 }), }; +const mockColumns = [ + { id: 'firstName', field: 'firstName' }, + { id: 'lastName', field: 'lastName' }, + { id: 'age', field: 'age' }, +] + const gridStub = { canCellBeSelected: jest.fn(), getActiveCell: jest.fn(), @@ -46,12 +54,13 @@ const gridStub = { getCellFromEvent: jest.fn(), getCellFromPoint: jest.fn(), getCellNodeBox: jest.fn(), + getColumns: () => mockColumns, getData: () => dataViewStub, getDataLength: jest.fn(), getEditorLock: () => getEditorLockMock, getOptions: () => mockGridOptions, getUID: () => GRID_UID, - getScrollbarDimensions: () => ({ height: 17, width: 17}), + getScrollbarDimensions: () => ({ height: 17, width: 17 }), getViewportNode: jest.fn(), focus: jest.fn(), registerPlugin: jest.fn(), @@ -229,7 +238,7 @@ describe('CellSelectionModel Plugin', () => { plugin.setSelectedRanges(mockRanges); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'ArrowLeft'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'ArrowLeft'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); expect(setSelectRangeSpy).toHaveBeenCalledWith([{ @@ -250,7 +259,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: 3, toCell: 3, toRow: 4 } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'ArrowRight'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'ArrowRight'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); expect(setSelectRangeSpy).toHaveBeenCalledWith([{ @@ -268,7 +277,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: 3, toCell: 3, toRow: 4 } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'ArrowUp'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'ArrowUp'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); expect(setSelectRangeSpy).toHaveBeenCalledWith([{ @@ -286,7 +295,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: 3, toCell: 3, toRow: 4, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'ArrowDown'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'ArrowDown'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); expect(setSelectRangeSpy).toHaveBeenCalledWith([{ @@ -308,7 +317,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: 3, toCell: 3, toRow: 4, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'ArrowDown'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'ArrowDown'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ @@ -337,7 +346,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 4, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'PageDown'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'PageDown'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ @@ -364,7 +373,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 4, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'PageDown'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'PageDown'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ @@ -391,7 +400,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'PageUp'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'PageUp'); gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ @@ -418,7 +427,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 4, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'PageUp'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'PageUp'); gridStub.onKeyDown.notify({ cell: 2, row: 3, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ @@ -432,7 +441,61 @@ describe('CellSelectionModel Plugin', () => { expect(scrollCellSpy).toHaveBeenCalledWith(firstRowIndex, 2, false); }); - it('should call "setSelectedRanges" with Slick Range from current position to row index 0 when using Shift+Home key combo when triggered by "onKeyDown"', () => { + it('should call "setSelectedRanges" with Slick Range from current position to row index 0 horizontally when using Shift+Home key combo when triggered by "onKeyDown"', () => { + const notifyingRowNumber = 100; + const expectedRowZeroIdx = 0; + jest.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber }); + jest.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true); + const scrollCellSpy = jest.spyOn(gridStub, 'scrollCellIntoView'); + + plugin.init(gridStub); + plugin.setSelectedRanges([ + { fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false }, + { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false } + ] as unknown as SlickRange[]); + const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'Home'); + gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub); + + const expectedRangeCalled = [ + { fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.toBeFunction(), } as unknown as SlickRange, + { + fromCell: expectedRowZeroIdx, fromRow: notifyingRowNumber, toCell: 2, toRow: 100, + contains: expect.toBeFunction(), toString: expect.toBeFunction(), isSingleCell: expect.toBeFunction(), isSingleRow: expect.toBeFunction(), + }, + ]; + expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled); + expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false); + }); + + it('should call "setSelectedRanges" with Slick Range from current position to same row last cell index horizontally when using Shift+End key combo when triggered by "onKeyDown"', () => { + const notifyingRowNumber = 100; + const columnsLn = mockColumns.length; + jest.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber }); + jest.spyOn(gridStub, 'canCellBeSelected').mockReturnValue(true); + const scrollCellSpy = jest.spyOn(gridStub, 'scrollCellIntoView'); + + plugin.init(gridStub); + plugin.setSelectedRanges([ + { fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: () => false }, + { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false } + ] as unknown as SlickRange[]); + const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['shiftKey'], 'End'); + gridStub.onKeyDown.notify({ cell: 1, row: 101, grid: gridStub }, keyDownEvent, gridStub); + + const expectedRangeCalled = [ + { fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.toBeFunction(), } as unknown as SlickRange, + { + fromCell: columnsLn - 1, fromRow: notifyingRowNumber, toCell: 2, toRow: 100, + contains: expect.toBeFunction(), toString: expect.toBeFunction(), isSingleCell: expect.toBeFunction(), isSingleRow: expect.toBeFunction(), + }, + ]; + expect(setSelectRangeSpy).toHaveBeenCalledWith(expectedRangeCalled); + expect(scrollCellSpy).toHaveBeenCalledWith(100, 2, false); + }); + + it('should call "setSelectedRanges" with Slick Range from current position to cell,row index 0 when using Ctrl+Shift+Home key combo when triggered by "onKeyDown"', () => { const notifyingRowNumber = 100; const expectedRowZeroIdx = 0; jest.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber }); @@ -445,13 +508,13 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'Home'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'Home'); gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ { fromCell: 1, fromRow: 99, toCell: 3, toRow: 120, contains: expect.toBeFunction(), } as unknown as SlickRange, { - fromCell: 2, fromRow: expectedRowZeroIdx, toCell: 2, toRow: 100, + fromCell: 0, fromRow: expectedRowZeroIdx, toCell: 2, toRow: 100, contains: expect.toBeFunction(), toString: expect.toBeFunction(), isSingleCell: expect.toBeFunction(), isSingleRow: expect.toBeFunction(), }, ]; @@ -459,7 +522,7 @@ describe('CellSelectionModel Plugin', () => { expect(scrollCellSpy).toHaveBeenCalledWith(expectedRowZeroIdx, 2, false); }); - it('should call "setSelectedRanges" with Slick Range from current position to last row index when using Shift+End key combo when triggered by "onKeyDown"', () => { + it('should call "setSelectedRanges" with Slick Range from current position to last row index when using Ctrl+Shift+End key combo when triggered by "onKeyDown"', () => { const notifyingRowNumber = 100; const expectedLastRowIdx = NB_ITEMS - 1; jest.spyOn(gridStub, 'getActiveCell').mockReturnValue({ cell: 2, row: notifyingRowNumber }); @@ -472,7 +535,7 @@ describe('CellSelectionModel Plugin', () => { { fromCell: 2, fromRow: notifyingRowNumber, toCell: 3, toRow: 120, contains: () => false } ] as unknown as SlickRange[]); const setSelectRangeSpy = jest.spyOn(plugin, 'setSelectedRanges'); - const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), 'shiftKey', 'End'); + const keyDownEvent = addVanillaEventPropagation(new Event('keydown'), ['ctrlKey', 'shiftKey'], 'End'); gridStub.onKeyDown.notify({ cell: 2, row: 101, grid: gridStub }, keyDownEvent, gridStub); const expectedRangeCalled = [ diff --git a/packages/common/src/extensions/slickCellSelectionModel.ts b/packages/common/src/extensions/slickCellSelectionModel.ts index 96ef8e41d..9a7ffdea6 100644 --- a/packages/common/src/extensions/slickCellSelectionModel.ts +++ b/packages/common/src/extensions/slickCellSelectionModel.ts @@ -1,3 +1,5 @@ +import { isDefined } from '@slickgrid-universal/utils'; + import type { CellRange, OnActiveCellChangedEventArgs, SlickDataView, SlickEventHandler, SlickGrid, SlickNamespace, SlickRange } from '../interfaces/index'; import { SlickCellRangeSelector } from './index'; @@ -159,9 +161,12 @@ export class SlickCellSelectionModel { protected handleActiveCellChange(_e: Event, args: OnActiveCellChangedEventArgs) { this._prevSelectedRow = undefined; - if (this._addonOptions?.selectActiveCell && args.row !== null && args.cell !== null) { + const isCellDefined = isDefined(args.cell); + const isRowDefined = isDefined(args.row); + + if (this._addonOptions?.selectActiveCell && isRowDefined && isCellDefined) { this.setSelectedRanges([new Slick.Range(args.row, args.cell)]); - } else if (!this._addonOptions?.selectActiveCell) { + } else if (!this._addonOptions?.selectActiveCell || (!isRowDefined && !isCellDefined)) { // clear the previous selection once the cell changes this.setSelectedRanges([]); } @@ -186,8 +191,8 @@ export class SlickCellSelectionModel { protected handleKeyDown(e: KeyboardEvent) { let ranges: CellRange[]; let last: SlickRange; + const colLn = this._grid.getColumns().length; const active = this._grid.getActiveCell(); - const metaKey = e.ctrlKey || e.metaKey; let dataLn = 0; if (this._dataView) { @@ -196,7 +201,7 @@ export class SlickCellSelectionModel { dataLn = this._grid.getDataLength(); } - if (active && e.shiftKey && !metaKey && !e.altKey && this.isKeyAllowed(e.key)) { + if (active && (e.shiftKey || e.ctrlKey) && !e.altKey && this.isKeyAllowed(e.key)) { ranges = this.getSelectedRanges().slice(); if (!ranges.length) { ranges.push(new Slick.Range(active.row, active.cell)); @@ -216,9 +221,10 @@ export class SlickCellSelectionModel { const dirRow = active.row === last.fromRow ? 1 : -1; const dirCell = active.cell === last.fromCell ? 1 : -1; const isSingleKeyMove = e.key.startsWith('Arrow'); + let toCell: undefined | number; let toRow = 0; - if (isSingleKeyMove) { + if (isSingleKeyMove && !e.ctrlKey) { // single cell move: (Arrow{Up/ArrowDown/ArrowLeft/ArrowRight}) if (e.key === 'ArrowLeft') { dCell -= dirCell; @@ -239,9 +245,17 @@ export class SlickCellSelectionModel { this._prevSelectedRow = active.row; } - if (e.key === 'Home') { + if (e.shiftKey && !e.ctrlKey && e.key === 'Home') { + toCell = 0; + toRow = active.row; + } else if (e.shiftKey && !e.ctrlKey && e.key === 'End') { + toCell = colLn - 1; + toRow = active.row; + } else if (e.ctrlKey && e.shiftKey && e.key === 'Home') { + toCell = 0; toRow = 0; - } else if (e.key === 'End') { + } else if (e.ctrlKey && e.shiftKey && e.key === 'End') { + toCell = colLn - 1; toRow = dataLn - 1; } else if (e.key === 'PageUp') { if (this._prevSelectedRow >= 0) { @@ -262,7 +276,8 @@ export class SlickCellSelectionModel { } // define new selection range - const newLast = new Slick.Range(active.row, active.cell, toRow, active.cell + dirCell * dCell); + toCell ??= active.cell + dirCell * dCell; + const newLast = new Slick.Range(active.row, active.cell, toRow, toCell); if (this.removeInvalidRanges([newLast]).length) { ranges.push(newLast); const viewRow = dirRow > 0 ? newLast.toRow : newLast.fromRow; diff --git a/packages/utils/src/utils.spec.ts b/packages/utils/src/utils.spec.ts index e7b94d901..bea1a0bce 100644 --- a/packages/utils/src/utils.spec.ts +++ b/packages/utils/src/utils.spec.ts @@ -9,6 +9,7 @@ import { objectAssignAndExtend, emptyObject, hasData, + isDefined, isEmptyObject, isNumber, isPrimitiveValue, @@ -98,6 +99,26 @@ describe('Service/Utilies', () => { }); }); + describe('isDefined method', () => { + it('should be truthy when comparing against any defined variable', () => { + const result1 = isDefined({ firstName: 'John', lastName: 'Doe' }); + const result2 = isDefined('hello'); + + expect(result1).toBeTruthy(); + expect(result2).toBeTruthy(); + }); + + it('should be falsy when comparing against empty string, null or undefined', () => { + const result1 = isDefined(''); + const result2 = isDefined(null); + const result3 = isDefined(undefined); + + expect(result1).toBeFalsy(); + expect(result2).toBeFalsy(); + expect(result3).toBeFalsy(); + }); + }); + describe('isEmptyObject method', () => { it('should return True when comparing against an object that has properties', () => { const result = isEmptyObject({ firstName: 'John', lastName: 'Doe' }); diff --git a/packages/utils/src/utils.ts b/packages/utils/src/utils.ts index 786cb64d6..852acc0fa 100644 --- a/packages/utils/src/utils.ts +++ b/packages/utils/src/utils.ts @@ -195,6 +195,10 @@ export function isEmptyObject(obj: any): boolean { return Object.entries(obj).length === 0; } +export function isDefined(value: T | undefined | null): value is T { + return value !== undefined && value !== null && value !== ''; +} + /** * Simple object check. * @param item diff --git a/test/cypress/e2e/example17.cy.ts b/test/cypress/e2e/example17.cy.ts index f22dc7ba5..7106e5114 100644 --- a/test/cypress/e2e/example17.cy.ts +++ b/test/cypress/e2e/example17.cy.ts @@ -29,7 +29,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { }); it('should select border shown in cell selection model, and hidden in row selection model when dragging', { scrollBehavior: false }, () => { - cy.getCell(0, 1, '', { parentSelector: '.grid17-1', rowHeight: CELL_HEIGHT }) + cy.getNthCell(0, 1, '', { parentSelector: '.grid17-1', rowHeight: CELL_HEIGHT }) .as('cell1') .dragStart(); cy.get('.grid17-1 .slick-range-decorator').should('be.exist').and('have.css', 'border-color').and('not.equal', 'none'); @@ -39,7 +39,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { cy.get('.grid17-1 .slick-range-decorator').should('not.be.exist'); cy.get('.grid17-1 .slick-cell.selected').should('have.length', 6); - cy.getCell(0, 1, '', { parentSelector: '.grid17-2', rowHeight: CELL_HEIGHT }) + cy.getNthCell(0, 1, '', { parentSelector: '.grid17-2', rowHeight: CELL_HEIGHT }) .as('cell2') .dragStart(); cy.get('.grid17-2 .slick-range-decorator').should('be.exist').and('have.css', 'border-style').and('equal', 'none'); @@ -89,7 +89,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { function getIntervalUntilRow12Displayed(selector, px, rowNumber = 12) { const viewportSelector = (`${selector} .slick-viewport:first`); - cy.getCell(0, 1, '', { parentSelector: selector, rowHeight: CELL_HEIGHT }) + cy.getNthCell(0, 1, '', { parentSelector: selector, rowHeight: CELL_HEIGHT }) .dragStart(); return cy.get(viewportSelector).invoke('scrollTop').then(scrollBefore => { @@ -276,7 +276,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { }); function testDragInGrouping(selector) { - cy.getCell(7, 0, 'bottomRight', { parentSelector: selector, rowHeight: CELL_HEIGHT }) + cy.getNthCell(7, 0, 'bottomRight', { parentSelector: selector, rowHeight: CELL_HEIGHT }) .dragStart(); cy.get(`${selector} .slick-viewport:last`).as('viewport').invoke('scrollTop').then(scrollBefore => { cy.dragOutside('bottom', 400, CELL_HEIGHT * 12, { parentSelector: selector }); diff --git a/test/cypress/e2e/example19.cy.ts b/test/cypress/e2e/example19.cy.ts index a575f86ab..1ed0687f2 100644 --- a/test/cypress/e2e/example19.cy.ts +++ b/test/cypress/e2e/example19.cy.ts @@ -23,54 +23,66 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { }); describe('with Pagination of size 20', () => { - it('should click on cell B14 then Shift+End w/selection B14-19', () => { + it('should click on cell B14 then Ctrl+Shift+End with selection B14-CV19', () => { cy.getCell(14, 2, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_B14') .click(); cy.get('@cell_B14') - .type('{shift}{end}'); + .type('{ctrl}{shift}{end}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":14,"fromCell":2,"toRow":19,"toCell":100}'); + }); + + it('should click on cell CP3 then Ctrl+Shift+Home with selection A0-CP3', () => { + cy.getCell(3, 94, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CP3') + .click(); + + cy.get('@cell_CP3') + .type('{ctrl}{shift}{home}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":14,"fromCell":2,"toRow":19,"toCell":2}'); + .should('have.text', '{"fromRow":0,"fromCell":0,"toRow":3,"toCell":94}'); }); - it('should click on cell C19 then Shift+End w/selection C0-19', () => { - cy.getCell(19, 2, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) - .as('cell_C19') + it('should click on cell CE7 then Shift+Home with selection A0-CE7', () => { + cy.getCell(3, 83, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CE7') .click(); - cy.get('@cell_C19') + cy.get('@cell_CE7') .type('{shift}{home}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":0,"fromCell":2,"toRow":19,"toCell":2}'); + .should('have.text', '{"fromRow":3,"fromCell":0,"toRow":3,"toCell":83}'); }); - it('should click on cell E3 then Shift+PageDown multiple times with current page selection starting at E3 w/selection E3-19', () => { - cy.getCell(3, 5, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) - .as('cell_E3') + it('should click on cell CG3 then Shift+PageDown multiple times with current page selection starting at E3 with selection E3-19', () => { + cy.getCell(3, 85, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CG3') .click(); - cy.get('@cell_E3') + cy.get('@cell_CG3') .type('{shift}{pagedown}{pagedown}{pagedown}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":3,"fromCell":5,"toRow":19,"toCell":5}'); + .should('have.text', '{"fromRow":3,"fromCell":85,"toRow":19,"toCell":85}'); }); - it('should change to 2nd page then click on cell D41 then Shift+PageUp multiple times with current page selection w/selection D25-41', () => { + it('should change to 2nd page then click on cell CF35 then Shift+PageUp multiple times with current page selection with selection D25-41', () => { cy.get('.slick-pagination .icon-seek-next').click(); - cy.getCell(15, 4, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) - .as('cell_D41') + cy.getCell(15, 84, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CF35') .click(); - cy.get('@cell_D41') + cy.get('@cell_CF35') .type('{shift}{pageup}{pageup}{pageup}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":0,"fromCell":4,"toRow":15,"toCell":4}'); + .should('have.text', '{"fromRow":0,"fromCell":84,"toRow":15,"toCell":84}'); }); }); @@ -80,6 +92,18 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .click(); }); + it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { + cy.getCell(5, 95, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CR95') + .click(); + + cy.get('@cell_CR95') + .type('{ctrl}{home}'); + + cy.get('#selectionRange') + .should('have.text', ''); + }); + it('should click on cell B10 and ArrowUp 3 times and ArrowDown 1 time and expect cell selection B8-B10', () => { cy.getCell(10, 2, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_B10') @@ -95,7 +119,7 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .should('have.text', '{"fromRow":8,"fromCell":2,"toRow":10,"toCell":2}'); }); - it('should click on cell D10 then PageDown 2 times w/selection D10-D50 (or D10-D52)', () => { + it('should click on cell D10 then PageDown 2 times with selection D10-D50 (or D10-D52)', () => { // 52 is because of a page row count found to be 21 for current browser resolution set in Cypress => 21*2+10 = 52 cy.getCell(10, 4, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') @@ -108,7 +132,7 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .should('contains', /{"fromRow":10,"fromCell":4,"toRow":5[0-2],"toCell":4}/); }); - it('should click on cell D10 then PageDown 3 times then PageUp 1 time w/selection D10-D50 (or D10-D52)', () => { + it('should click on cell D10 then PageDown 3 times then PageUp 1 time with selection D10-D50 (or D10-D52)', () => { cy.getCell(10, 4, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) .as('cell_D10') .click(); @@ -120,28 +144,109 @@ describe('Example 19 - ExcelCopyBuffer with Cell Selection', () => { .should('contains', /{"fromRow":10,"fromCell":4,"toRow":5[0-2],"toCell":4}/); }); - it('should click on cell E12 then End key w/selection E52-E99', () => { - cy.getCell(52, 5, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) - .as('cell_E52') + it('should click on cell E46 then Shift+End key with full row horizontal selection E46-CV46', () => { + cy.getCell(46, 5, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_E46') + .click(); + + cy.get('@cell_E46') + .type('{shift}{end}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":46,"fromCell":5,"toRow":46,"toCell":100}'); + }); + + it('should click on cell CP54 then Ctrl+Shift+End keys with selection E46-CV99', () => { + cy.getCell(54, 94, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CP54') + .click(); + + cy.get('@cell_CP54') + .type('{ctrl}{shift}{end}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":54,"fromCell":94,"toRow":99,"toCell":100}'); + }); + + it('should click on cell CP95 then Ctrl+Shift+Home keys with selection C0-CP95', () => { + cy.getCell(95, 98, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CP95') + .click(); + + cy.get('@cell_CP95') + .type('{ctrl}{shift}{home}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":0,"fromCell":0,"toRow":95,"toCell":98}'); + }); + + it('should click on cell CR5 then Ctrl+Home keys and expect to scroll back to cell A0 without any selection range', () => { + cy.getCell(5, 95, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CR95') + .click(); + + cy.get('@cell_CR95') + .type('{ctrl}{home}'); + + cy.get('#selectionRange') + .should('have.text', ''); + }); + }); + + describe('with Pagination', () => { + it('should hide Pagination', () => { + cy.get('[data-text="toggle-pagination-btn"]') + .click(); + }); + + it('should click on cell B14 then Shift+End with selection B14-24', () => { + cy.getCell(14, 2, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_B14') .click(); - cy.get('@cell_E52') + cy.get('@cell_B14') .type('{shift}{end}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":52,"fromCell":5,"toRow":99,"toCell":5}'); + .should('have.text', '{"fromRow":14,"fromCell":2,"toRow":14,"toCell":100}'); }); - it('should click on cell C85 then End key w/selection C0-C85', () => { - cy.getCell(85, 3, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) - .as('cell_C85') + it('should click on cell CS14 then Shift+Home with selection A14-CS14', () => { + cy.getCell(14, 97, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CS14') .click(); - cy.get('@cell_C85') + cy.get('@cell_CS14') .type('{shift}{home}'); cy.get('#selectionRange') - .should('have.text', '{"fromRow":0,"fromCell":3,"toRow":85,"toCell":3}'); + .should('have.text', '{"fromRow":14,"fromCell":0,"toRow":14,"toCell":97}'); + }); + + it('should click on cell CN3 then Shift+PageDown multiple times with current page selection starting at E3 w/selection E3-19', () => { + cy.getCell(3, 95, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CN3') + .click(); + + cy.get('@cell_CN3') + .type('{shift}{pagedown}{pagedown}{pagedown}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":3,"fromCell":95,"toRow":19,"toCell":95}'); + }); + + it('should change to 2nd page then click on cell CN41 then Shift+PageUp multiple times with current page selection w/selection D25-41', () => { + cy.get('.slick-pagination .seek-next').click(); + + cy.getCell(15, 92, '', { parentSelector: '.grid19', rowHeight: GRID_ROW_HEIGHT }) + .as('cell_CN41') + .click(); + + cy.get('@cell_CN41') + .type('{shift}{pageup}{pageup}{pageup}'); + + cy.get('#selectionRange') + .should('have.text', '{"fromRow":0,"fromCell":92,"toRow":15,"toCell":92}'); }); }); }); diff --git a/test/cypress/support/commands.ts b/test/cypress/support/commands.ts index 975e2ae79..f9a35856c 100644 --- a/test/cypress/support/commands.ts +++ b/test/cypress/support/commands.ts @@ -33,6 +33,7 @@ declare global { // triggerHover: (elements: NodeListOf) => void; convertPosition(viewport: string): Chainable | { x: string; y: string; }>; getCell(row: number, col: number, viewport?: string, options?: { parentSelector?: string, rowHeight?: number; }): Chainable>; + getNthCell(row: number, nthCol: number, viewport?: string, options?: { parentSelector?: string, rowHeight?: number; }): Chainable>; } } } @@ -40,10 +41,18 @@ declare global { // convert position like 'topLeft' to the object { x: 'left|right', y: 'top|bottom' } Cypress.Commands.add('convertPosition', (viewport = 'topLeft') => cy.wrap(convertPosition(viewport))); -Cypress.Commands.add('getCell', (row: number, col: number, viewport = 'topLeft', { parentSelector = '', rowHeight = 35 } = {}) => { +Cypress.Commands.add('getCell', (row, col, viewport = 'topLeft', { parentSelector = '', rowHeight = 35 } = {}) => { const position = convertPosition(viewport); const canvasSelectorX = position.x ? `.grid-canvas-${position.x}` : ''; const canvasSelectorY = position.y ? `.grid-canvas-${position.y}` : ''; - return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top:${row * rowHeight}px"] > .slick-cell:nth(${col})`); + return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top:${row * rowHeight}px"] > .slick-cell.l${col}.r${col}`); +}); + +Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentSelector = '', rowHeight = 35 } = {}) => { + const position = convertPosition(viewport); + const canvasSelectorX = position.x ? `.grid-canvas-${position.x}` : ''; + const canvasSelectorY = position.y ? `.grid-canvas-${position.y}` : ''; + + return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style="top:${row * rowHeight}px"] > .slick-cell:nth(${nthCol})`); }); diff --git a/test/cypress/support/drag.ts b/test/cypress/support/drag.ts index 329676524..eb6fcc450 100644 --- a/test/cypress/support/drag.ts +++ b/test/cypress/support/drag.ts @@ -5,7 +5,7 @@ declare global { namespace Cypress { interface Chainable { // triggerHover: (elements: NodeListOf) => void; - dragOutside(viewport?: string, ms?: number, px?: number, options?: { parentSelector?: string, scrollbarDimension?: number; }): Chainable; + dragOutside(viewport?: string, ms?: number, px?: number, options?: { parentSelector?: string, scrollbarDimension?: number; rowHeight?: number; }): Chainable; dragStart(options?: { cellWidth?: number; cellHeight?: number; }): Chainable; dragCell(addRow: number, addCell: number, options?: { cellWidth?: number; cellHeight?: number; }): Chainable; dragEnd(gridSelector?: string): Chainable; @@ -13,13 +13,15 @@ declare global { } } +// @ts-ignore Cypress.Commands.add('dragStart', { prevSubject: true }, (subject, { cellWidth = 90, cellHeight = 35 } = {}) => { return cy.wrap(subject).click({ force: true }) .trigger('mousedown', { which: 1 } as any, { force: true }) .trigger('mousemove', cellWidth / 3, cellHeight / 3); }); -// use a different command name than 'drag' so that it doesn't conflict with the '@4tw/cypress-drag-drop' lib +// use a different command name than "drag" so that it doesn't conflict with the "@4tw/cypress-drag-drop" lib +// @ts-ignore Cypress.Commands.add('dragCell', { prevSubject: true }, (subject, addRow, addCell, { cellWidth = 90, cellHeight = 35 } = {}) => { return cy.wrap(subject).trigger('mousemove', cellWidth * (addCell + 0.5), cellHeight * (addRow + 0.5), { force: true }); }); @@ -57,7 +59,7 @@ Cypress.Commands.add('dragEnd', { prevSubject: 'optional' }, (_subject, gridSele export function getScrollDistanceWhenDragOutsideGrid(selector: string, viewport: string, dragDirection: string, fromRow: number, fromCol: number, px = 140) { return (cy as any).convertPosition(viewport).then((_viewportPosition: { x: number; y: number; }) => { const viewportSelector = `${selector} .slick-viewport-${_viewportPosition.x}.slick-viewport-${_viewportPosition.y}`; - (cy as any).getCell(fromRow, fromCol, viewport, { parentSelector: selector }) + (cy as any).getNthCell(fromRow, fromCol, viewport, { parentSelector: selector }) .dragStart(); return cy.get(viewportSelector).then($viewport => { const scrollTopBefore = $viewport.scrollTop(); @@ -77,4 +79,4 @@ export function getScrollDistanceWhenDragOutsideGrid(selector: string, viewport: }); }); }); -} \ No newline at end of file +} From 801a5c5507dda55b395c64e693a0375a794983c6 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sat, 2 Dec 2023 13:00:31 -0500 Subject: [PATCH 2/2] chore: update SlickGrid to latest with bug fixes --- package.json | 2 +- packages/common/package.json | 2 +- packages/vanilla-bundle/package.json | 2 +- pnpm-lock.yaml | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 5875bc5ab..c089f40c8 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "rimraf": "^5.0.5", "rxjs": "^7.8.1", "servor": "^4.0.2", - "slickgrid": "^4.1.5", + "slickgrid": "^4.1.6", "sortablejs": "^1.15.1", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", diff --git a/packages/common/package.json b/packages/common/package.json index 7b64394e2..937968115 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -81,7 +81,7 @@ "flatpickr": "^4.6.13", "moment-mini": "^2.29.4", "multiple-select-vanilla": "^0.6.3", - "slickgrid": "^4.1.5", + "slickgrid": "^4.1.6", "sortablejs": "^1.15.1", "un-flatten-tree": "^2.0.12" }, diff --git a/packages/vanilla-bundle/package.json b/packages/vanilla-bundle/package.json index 114c3c29c..e2c73f34c 100644 --- a/packages/vanilla-bundle/package.json +++ b/packages/vanilla-bundle/package.json @@ -66,7 +66,7 @@ "@slickgrid-universal/utils": "workspace:~", "dequal": "^2.0.3", "flatpickr": "^4.6.13", - "slickgrid": "^4.1.5", + "slickgrid": "^4.1.6", "sortablejs": "^1.15.1", "whatwg-fetch": "^3.6.19" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca444c36d..b9c807a11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,8 +93,8 @@ importers: specifier: ^4.0.2 version: 4.0.2 slickgrid: - specifier: ^4.1.5 - version: 4.1.5 + specifier: ^4.1.6 + version: 4.1.6 sortablejs: specifier: ^1.15.1 version: 1.15.1 @@ -232,8 +232,8 @@ importers: specifier: ^0.6.3 version: 0.6.3 slickgrid: - specifier: ^4.1.5 - version: 4.1.5 + specifier: ^4.1.6 + version: 4.1.6 sortablejs: specifier: ^1.15.1 version: 1.15.1 @@ -524,8 +524,8 @@ importers: specifier: ^4.6.13 version: 4.6.13 slickgrid: - specifier: ^4.1.5 - version: 4.1.5 + specifier: ^4.1.6 + version: 4.1.6 sortablejs: specifier: ^1.15.1 version: 1.15.1 @@ -8479,8 +8479,8 @@ packages: is-fullwidth-code-point: 3.0.0 dev: true - /slickgrid@4.1.5: - resolution: {integrity: sha512-riHpgidZ2qOinsthGCewFhAYbo7LX/rpXd9B6rGWaVqap1sbL8viGbe8ZCXu2SZvwaGC+LqkGisW77UzMOLoVg==} + /slickgrid@4.1.6: + resolution: {integrity: sha512-4OeCzo2JC9xRXi3P0yEM2Md20Cve3XOVn1K6+9W9TQ6mjfWtJOhe6rUjl1RqloHHYTeiO7OOqPZT61FZVMH1Ew==} dependencies: sortablejs: 1.15.1