From dbd07fa1894a9ca3274826fb23f550904fb5f76a Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 28 Aug 2026 18:49:15 -0400 Subject: [PATCH 1/3] fix(selection): preserve live previews for multi-row drag selection --- .../aurelia/test/cypress/e2e/example48.cy.ts | 34 ++++++++++ demos/react/test/cypress/e2e/example48.cy.ts | 34 ++++++++++ demos/vue/test/cypress/e2e/example48.cy.ts | 34 ++++++++++ .../test/cypress/e2e/example48.cy.ts | 34 ++++++++++ .../slickHybridSelectionModel.spec.ts | 67 +++++++++++++------ .../extensions/slickHybridSelectionModel.ts | 21 +++++- test/cypress/e2e/example37.cy.ts | 34 ++++++++++ 7 files changed, 236 insertions(+), 22 deletions(-) diff --git a/demos/aurelia/test/cypress/e2e/example48.cy.ts b/demos/aurelia/test/cypress/e2e/example48.cy.ts index fa612292a..d8687dda0 100644 --- a/demos/aurelia/test/cypress/e2e/example48.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example48.cy.ts @@ -314,5 +314,39 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').contains(/"fromRow":2,"fromCell":0,"toRow":2,"toCell":7/); cy.get('#selectionRange2').contains(/"fromRow":4,"fromCell":0,"toRow":4,"toCell":7/); }); + + it('should preserve row ranges when enabling multi-selection and show the live modifier-drag preview', () => { + const gridSelector = '#grid48-2'; + const firstRange = '{"fromRow":0,"fromCell":0,"toRow":1,"toCell":7}'; + const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; + const combinedRanges = `${firstRange}${secondRange}`; + + cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); + cy.get('[data-test="enable-multi-selection"]').uncheck(); + + const firstRowCell = `${gridSelector} .slick-row[data-row="0"] .slick-cell.l1.r1`; + cy.get(firstRowCell).dragStart(); + cy.get(firstRowCell).dragCell(1, 0); + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', firstRange); + + cy.get('[data-test="enable-multi-selection"]').check(); + cy.get('#selectionRange2').should('have.text', firstRange); + + const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; + cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + + cy.get('#selectionRange2').should('have.text', combinedRanges); + cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); + cy.get(`${gridSelector} .slick-row[data-row="2"] .slick-cell.selected`).should('have.length', 0); + + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', combinedRanges); + }); }); }); diff --git a/demos/react/test/cypress/e2e/example48.cy.ts b/demos/react/test/cypress/e2e/example48.cy.ts index fa612292a..d8687dda0 100644 --- a/demos/react/test/cypress/e2e/example48.cy.ts +++ b/demos/react/test/cypress/e2e/example48.cy.ts @@ -314,5 +314,39 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').contains(/"fromRow":2,"fromCell":0,"toRow":2,"toCell":7/); cy.get('#selectionRange2').contains(/"fromRow":4,"fromCell":0,"toRow":4,"toCell":7/); }); + + it('should preserve row ranges when enabling multi-selection and show the live modifier-drag preview', () => { + const gridSelector = '#grid48-2'; + const firstRange = '{"fromRow":0,"fromCell":0,"toRow":1,"toCell":7}'; + const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; + const combinedRanges = `${firstRange}${secondRange}`; + + cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); + cy.get('[data-test="enable-multi-selection"]').uncheck(); + + const firstRowCell = `${gridSelector} .slick-row[data-row="0"] .slick-cell.l1.r1`; + cy.get(firstRowCell).dragStart(); + cy.get(firstRowCell).dragCell(1, 0); + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', firstRange); + + cy.get('[data-test="enable-multi-selection"]').check(); + cy.get('#selectionRange2').should('have.text', firstRange); + + const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; + cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + + cy.get('#selectionRange2').should('have.text', combinedRanges); + cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); + cy.get(`${gridSelector} .slick-row[data-row="2"] .slick-cell.selected`).should('have.length', 0); + + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', combinedRanges); + }); }); }); diff --git a/demos/vue/test/cypress/e2e/example48.cy.ts b/demos/vue/test/cypress/e2e/example48.cy.ts index fa612292a..d8687dda0 100644 --- a/demos/vue/test/cypress/e2e/example48.cy.ts +++ b/demos/vue/test/cypress/e2e/example48.cy.ts @@ -314,5 +314,39 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').contains(/"fromRow":2,"fromCell":0,"toRow":2,"toCell":7/); cy.get('#selectionRange2').contains(/"fromRow":4,"fromCell":0,"toRow":4,"toCell":7/); }); + + it('should preserve row ranges when enabling multi-selection and show the live modifier-drag preview', () => { + const gridSelector = '#grid48-2'; + const firstRange = '{"fromRow":0,"fromCell":0,"toRow":1,"toCell":7}'; + const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; + const combinedRanges = `${firstRange}${secondRange}`; + + cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); + cy.get('[data-test="enable-multi-selection"]').uncheck(); + + const firstRowCell = `${gridSelector} .slick-row[data-row="0"] .slick-cell.l1.r1`; + cy.get(firstRowCell).dragStart(); + cy.get(firstRowCell).dragCell(1, 0); + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', firstRange); + + cy.get('[data-test="enable-multi-selection"]').check(); + cy.get('#selectionRange2').should('have.text', firstRange); + + const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; + cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + + cy.get('#selectionRange2').should('have.text', combinedRanges); + cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); + cy.get(`${gridSelector} .slick-row[data-row="2"] .slick-cell.selected`).should('have.length', 0); + + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', combinedRanges); + }); }); }); diff --git a/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts b/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts index fa612292a..d8687dda0 100644 --- a/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts +++ b/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts @@ -314,5 +314,39 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').contains(/"fromRow":2,"fromCell":0,"toRow":2,"toCell":7/); cy.get('#selectionRange2').contains(/"fromRow":4,"fromCell":0,"toRow":4,"toCell":7/); }); + + it('should preserve row ranges when enabling multi-selection and show the live modifier-drag preview', () => { + const gridSelector = '#grid48-2'; + const firstRange = '{"fromRow":0,"fromCell":0,"toRow":1,"toCell":7}'; + const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; + const combinedRanges = `${firstRange}${secondRange}`; + + cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); + cy.get('[data-test="enable-multi-selection"]').uncheck(); + + const firstRowCell = `${gridSelector} .slick-row[data-row="0"] .slick-cell.l1.r1`; + cy.get(firstRowCell).dragStart(); + cy.get(firstRowCell).dragCell(1, 0); + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', firstRange); + + cy.get('[data-test="enable-multi-selection"]').check(); + cy.get('#selectionRange2').should('have.text', firstRange); + + const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; + cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + + cy.get('#selectionRange2').should('have.text', combinedRanges); + cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); + cy.get(`${gridSelector} .slick-row[data-row="2"] .slick-cell.selected`).should('have.length', 0); + + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', combinedRanges); + }); }); }); diff --git a/packages/common/src/extensions/__tests__/slickHybridSelectionModel.spec.ts b/packages/common/src/extensions/__tests__/slickHybridSelectionModel.spec.ts index 6af55822c..bf003a7c0 100644 --- a/packages/common/src/extensions/__tests__/slickHybridSelectionModel.spec.ts +++ b/packages/common/src/extensions/__tests__/slickHybridSelectionModel.spec.ts @@ -222,15 +222,16 @@ describe('Row Selection Model Plugin', () => { }); }); - it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with rowSelectColumnIds defined with column IDs', () => { + it('should preserve row ranges when "refreshSelections" is called with rowSelectColumnIds defined with column IDs', () => { vi.spyOn(gridStub, 'getVisibleColumns').mockReturnValueOnce([{ id: 'firstName', field: 'firstName', name: 'First Name' }]); vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); plugin = new SlickHybridSelectionModel({ rowSelectColumnIds: ['firstName'], selectActiveRow: false }); plugin.init(gridStub); - vi.spyOn(plugin, 'getSelectedRows').mockReturnValue([0, 1]); + vi.spyOn(plugin, 'getSelectedRanges').mockReturnValue([new SlickRange(0, 0, 1, 2)]); const setSelectedRowsSpy = vi.spyOn(plugin, 'setSelectedRows'); + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); const mouseEvent = addVanillaEventPropagation(new Event('mouseenter')); gridStub.onActiveCellChanged.notify({ cell: undefined as any, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); @@ -239,10 +240,11 @@ describe('Row Selection Model Plugin', () => { gridStub.onActiveCellChanged.notify({ cell: 0, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); - expect(setSelectedRowsSpy).toHaveBeenCalledWith([0, 1]); + expect(setSelectedRowsSpy).not.toHaveBeenCalled(); + expect(setSelectedRangesSpy).toHaveBeenCalledWith([expect.objectContaining({ fromCell: 0, fromRow: 0, toRow: 1 })], undefined, ''); }); - it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with enableRowMoveManager enabled', () => { + it('should preserve row ranges when "refreshSelections" is called with enableRowMoveManager enabled', () => { vi.spyOn(gridStub, 'getVisibleColumns').mockReturnValueOnce([]); vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); mockGridOptions.enableRowMoveManager = true; @@ -251,29 +253,29 @@ describe('Row Selection Model Plugin', () => { plugin = new SlickHybridSelectionModel({ selectActiveRow: false }); plugin.init(gridStub); - vi.spyOn(plugin, 'getSelectedRows').mockReturnValue([0, 1]); - const setSelectedRowsSpy = vi.spyOn(plugin, 'setSelectedRows'); + vi.spyOn(plugin, 'getSelectedRanges').mockReturnValue([new SlickRange(0, 0, 1, 2)]); + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); const mouseEvent = addVanillaEventPropagation(new Event('mouseenter')); gridStub.onActiveCellChanged.notify({ cell: 0, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); - expect(setSelectedRowsSpy).toHaveBeenCalledWith([0, 1]); + expect(setSelectedRangesSpy).toHaveBeenCalledWith([new SlickRange(0, 0, 1, 2)], undefined, ''); }); - it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with rowSelectOverride returning true', () => { + it('should preserve row ranges when "refreshSelections" is called with rowSelectOverride returning true', () => { vi.spyOn(gridStub, 'getVisibleColumns').mockReturnValueOnce([]); vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); plugin = new SlickHybridSelectionModel({ rowSelectOverride: () => true, selectActiveRow: false }); plugin.init(gridStub); - vi.spyOn(plugin, 'getSelectedRows').mockReturnValue([0, 1]); - const setSelectedRowsSpy = vi.spyOn(plugin, 'setSelectedRows'); + vi.spyOn(plugin, 'getSelectedRanges').mockReturnValue([new SlickRange(0, 0, 1, 2)]); + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); const mouseEvent = addVanillaEventPropagation(new Event('mouseenter')); gridStub.onActiveCellChanged.notify({ cell: 0, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); - expect(setSelectedRowsSpy).toHaveBeenCalledWith([0, 1]); + expect(setSelectedRangesSpy).toHaveBeenCalledWith([new SlickRange(0, 0, 1, 2)], undefined, ''); }); it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with selectionType set to "cell"', () => { @@ -294,36 +296,36 @@ describe('Row Selection Model Plugin', () => { expect(setSelectedRangesSpy).toHaveBeenCalledWith([], undefined, ''); }); - it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with selectionType set to "row"', () => { + it('should preserve row ranges when "refreshSelections" is called with selectionType set to "row"', () => { vi.spyOn(gridStub, 'getVisibleColumns').mockReturnValueOnce([]); vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); plugin = new SlickHybridSelectionModel({ selectionType: 'row', selectActiveRow: false }); plugin.init(gridStub); - vi.spyOn(plugin, 'getSelectedRows').mockReturnValue([0, 1]); - const setSelectedRowsSpy = vi.spyOn(plugin, 'setSelectedRows'); + vi.spyOn(plugin, 'getSelectedRanges').mockReturnValue([new SlickRange(0, 0, 1, 2)]); + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); const mouseEvent = addVanillaEventPropagation(new Event('mouseenter')); gridStub.onActiveCellChanged.notify({ cell: 0, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); - expect(setSelectedRowsSpy).toHaveBeenCalledWith([0, 1]); + expect(setSelectedRangesSpy).toHaveBeenCalledWith([new SlickRange(0, 0, 1, 2)], undefined, ''); }); - it('should expect that "setSelectedRows" is being triggered when "refreshSelections" is called with rowSelectOverride returning true', () => { + it('should preserve row ranges when "refreshSelections" is called with rowSelectOverride returning true', () => { vi.spyOn(gridStub, 'getVisibleColumns').mockReturnValueOnce([]); vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); plugin = new SlickHybridSelectionModel({ rowSelectOverride: () => true, selectActiveRow: false }); plugin.init(gridStub); - vi.spyOn(plugin, 'getSelectedRows').mockReturnValue([0, 1]); - const setSelectedRowsSpy = vi.spyOn(plugin, 'setSelectedRows'); + vi.spyOn(plugin, 'getSelectedRanges').mockReturnValue([new SlickRange(0, 0, 1, 2)]); + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); const mouseEvent = addVanillaEventPropagation(new Event('mouseenter')); gridStub.onActiveCellChanged.notify({ cell: 0, row: 3, grid: gridStub }, mouseEvent, gridStub); plugin.refreshSelections(); - expect(setSelectedRowsSpy).toHaveBeenCalledWith([0, 1]); + expect(setSelectedRangesSpy).toHaveBeenCalledWith([new SlickRange(0, 0, 1, 2)], undefined, ''); }); it('should not call "setSelectedRows" when cell/row are not defined', () => { @@ -699,6 +701,33 @@ describe('Row Selection Model Plugin', () => { ); }); + it('should show the current row range during a modifier drag without accumulating intermediate ranges', () => { + plugin = new SlickHybridSelectionModel({ selectionType: 'row', enableMultiSelection: true, dragToSelect: true }); + plugin.init(gridStub); + plugin.setSelectedRanges([new SlickRange(0, 0, 1, 2)]); + + const setSelectedRangesSpy = vi.spyOn(plugin, 'setSelectedRanges'); + const scrollEvent = addVanillaEventPropagation(new Event('scroll')); + plugin.getCellRangeSelector()!.onCellRangeSelecting.notify({ range: new SlickRange(2, 0, 2, 2), addToSelection: true } as any, scrollEvent, gridStub); + expect(plugin.getSelectedRanges()).toEqual([new SlickRange(0, 0, 1, 2), new SlickRange(2, 0, 2, 2)]); + + plugin.getCellRangeSelector()!.onCellRangeSelecting.notify({ range: new SlickRange(2, 0, 3, 2), addToSelection: true } as any, scrollEvent, gridStub); + expect(plugin.getSelectedRanges()).toEqual([new SlickRange(0, 0, 1, 2), new SlickRange(2, 0, 3, 2)]); + + plugin.getCellRangeSelector()!.onCellRangeSelected.notify({ range: new SlickRange(2, 0, 3, 2), addToSelection: true } as any, scrollEvent, gridStub); + + expect(setSelectedRangesSpy).toHaveBeenCalledTimes(3); + expect(setSelectedRangesSpy).toHaveBeenCalledWith( + [ + { fromCell: 0, fromRow: 0, toCell: 2, toRow: 1 }, + { fromCell: 0, fromRow: 2, toCell: 2, toRow: 3 }, + ], + undefined, + undefined + ); + expect(plugin.getSelectedRanges()).toEqual([new SlickRange(0, 0, 1, 2), new SlickRange(2, 0, 3, 2)]); + }); + it('should be able to manually create Row Selection and then call "setSelectedRanges" when "onCellRangeSelected" event is triggered', () => { vi.spyOn(gridStub, 'getColumns').mockReturnValueOnce(mockColumns); const setSelectedRangeSpy = vi.spyOn(plugin, 'setSelectedRanges'); diff --git a/packages/common/src/extensions/slickHybridSelectionModel.ts b/packages/common/src/extensions/slickHybridSelectionModel.ts index 00f461986..f1bfc0a98 100644 --- a/packages/common/src/extensions/slickHybridSelectionModel.ts +++ b/packages/common/src/extensions/slickHybridSelectionModel.ts @@ -21,6 +21,7 @@ export class SlickHybridSelectionModel implements SelectionModel new SlickRange(range.fromRow, 0, range.toRow, lastCell)); + this.setSelectedRanges(ranges, undefined, ''); } else { this.setSelectedRanges(this.getSelectedRanges(), undefined, ''); } @@ -638,6 +641,7 @@ export class SlickHybridSelectionModel implements SelectionModel { cy.get('#selectionRange2').contains(/"fromRow":2,"fromCell":0,"toRow":2,"toCell":7/); cy.get('#selectionRange2').contains(/"fromRow":4,"fromCell":0,"toRow":4,"toCell":7/); }); + + it('should preserve row ranges when enabling multi-selection and show the live modifier-drag preview', () => { + const gridSelector = '.grid37-2'; + const firstRange = '{"fromRow":0,"fromCell":0,"toRow":1,"toCell":7}'; + const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; + const combinedRanges = `${firstRange}${secondRange}`; + + cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); + cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); + cy.get('[data-test="enable-multi-selection"]').uncheck(); + + const firstRowCell = `${gridSelector} .slick-row[data-row="0"] .slick-cell.l1.r1`; + cy.get(firstRowCell).dragStart(); + cy.get(firstRowCell).dragCell(1, 0); + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', firstRange); + + cy.get('[data-test="enable-multi-selection"]').check(); + cy.get('#selectionRange2').should('have.text', firstRange); + + const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; + cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + + cy.get('#selectionRange2').should('have.text', combinedRanges); + cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); + cy.get(`${gridSelector} .slick-row[data-row="2"] .slick-cell.selected`).should('have.length', 0); + + cy.dragEnd(gridSelector); + cy.get('#selectionRange2').should('have.text', combinedRanges); + }); }); }); From e291810a4bece9741bec3500729505bfe9cd7a62 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 28 Aug 2026 18:50:21 -0400 Subject: [PATCH 2/3] chore: update Cypress tests --- demos/aurelia/test/cypress/e2e/example48.cy.ts | 7 ++++--- demos/react/test/cypress/e2e/example48.cy.ts | 7 ++++--- demos/vue/test/cypress/e2e/example48.cy.ts | 7 ++++--- .../angular-slickgrid/test/cypress/e2e/example48.cy.ts | 7 ++++--- test/cypress/e2e/example37.cy.ts | 7 ++++--- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/demos/aurelia/test/cypress/e2e/example48.cy.ts b/demos/aurelia/test/cypress/e2e/example48.cy.ts index d8687dda0..5e7f31768 100644 --- a/demos/aurelia/test/cypress/e2e/example48.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example48.cy.ts @@ -337,9 +337,10 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').should('have.text', firstRange); const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; - cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + cy.get(secondRowCell).as('secondRowCell'); + cy.get('@secondRowCell').trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); cy.get('#selectionRange2').should('have.text', combinedRanges); cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); diff --git a/demos/react/test/cypress/e2e/example48.cy.ts b/demos/react/test/cypress/e2e/example48.cy.ts index d8687dda0..5e7f31768 100644 --- a/demos/react/test/cypress/e2e/example48.cy.ts +++ b/demos/react/test/cypress/e2e/example48.cy.ts @@ -337,9 +337,10 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').should('have.text', firstRange); const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; - cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + cy.get(secondRowCell).as('secondRowCell'); + cy.get('@secondRowCell').trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); cy.get('#selectionRange2').should('have.text', combinedRanges); cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); diff --git a/demos/vue/test/cypress/e2e/example48.cy.ts b/demos/vue/test/cypress/e2e/example48.cy.ts index d8687dda0..5e7f31768 100644 --- a/demos/vue/test/cypress/e2e/example48.cy.ts +++ b/demos/vue/test/cypress/e2e/example48.cy.ts @@ -337,9 +337,10 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').should('have.text', firstRange); const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; - cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + cy.get(secondRowCell).as('secondRowCell'); + cy.get('@secondRowCell').trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); cy.get('#selectionRange2').should('have.text', combinedRanges); cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); diff --git a/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts b/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts index d8687dda0..5e7f31768 100644 --- a/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts +++ b/frameworks/angular-slickgrid/test/cypress/e2e/example48.cy.ts @@ -337,9 +337,10 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('#selectionRange2').should('have.text', firstRange); const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; - cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + cy.get(secondRowCell).as('secondRowCell'); + cy.get('@secondRowCell').trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); cy.get('#selectionRange2').should('have.text', combinedRanges); cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); diff --git a/test/cypress/e2e/example37.cy.ts b/test/cypress/e2e/example37.cy.ts index 2c7e69281..aefe48b22 100644 --- a/test/cypress/e2e/example37.cy.ts +++ b/test/cypress/e2e/example37.cy.ts @@ -396,9 +396,10 @@ describe('Example 37 - Hybrid Selection Model', () => { cy.get('#selectionRange2').should('have.text', firstRange); const secondRowCell = `${gridSelector} .slick-row[data-row="3"] .slick-cell.l1.r1`; - cy.get(secondRowCell).trigger('mousedown', { which: 1, ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); - cy.get(secondRowCell).trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); + cy.get(secondRowCell).as('secondRowCell'); + cy.get('@secondRowCell').trigger('mousedown', { which: 1, ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 10, { ctrlKey: true, force: true }); + cy.get('@secondRowCell').trigger('mousemove', 30, 52, { ctrlKey: true, force: true }); cy.get('#selectionRange2').should('have.text', combinedRanges); cy.get(`${gridSelector} .slick-cell.selected`).should('have.length', 8 * 4); From 679962509477646641b59e65873ecccb2e61abab Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 28 Aug 2026 22:50:40 -0400 Subject: [PATCH 3/3] chore: improve multi-selection checkbox --- demos/aurelia/src/examples/slickgrid/example48.html | 4 ++-- demos/react/src/examples/slickgrid/Example48.tsx | 6 +++--- demos/vanilla/src/examples/example37.html | 2 +- demos/vue/src/components/Example48.vue | 4 ++-- .../src/demos/examples/example48.component.html | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/demos/aurelia/src/examples/slickgrid/example48.html b/demos/aurelia/src/examples/slickgrid/example48.html index 21dbe6902..723de7a62 100644 --- a/demos/aurelia/src/examples/slickgrid/example48.html +++ b/demos/aurelia/src/examples/slickgrid/example48.html @@ -45,14 +45,14 @@

-