From 2e65fa8da2abcff5e279a0cf52afab6a537ba8d9 Mon Sep 17 00:00:00 2001 From: "Ghislain B." Date: Sat, 18 Jan 2025 15:05:11 -0500 Subject: [PATCH] feat: add final rowspan implementation (#1101) * feat: add final rowspan implementation --- cypress/e2e/example-0031-row-span.cy.ts | 446 +++++++++ .../example-0032-row-span-many-columns.cy.ts | 442 +++++++++ .../example-auto-scroll-when-dragging.cy.ts | 22 +- cypress/e2e/example-autotooltips.cy.ts | 24 +- cypress/e2e/example-checkbox-header-row.cy.ts | 6 +- cypress/e2e/example-checkbox-row-select.cy.ts | 6 +- cypress/e2e/example-colspan.cy.ts | 115 ++- ...xample-composite-editor-modal-dialog.cy.ts | 124 +-- cypress/e2e/example-csp-header.cy.ts | 25 +- cypress/e2e/example-draggable-grouping.cy.ts | 67 +- .../example-draggable-header-grouping.cy.ts | 73 +- cypress/e2e/example-example1-simple.cy.ts | 32 +- ...example-excel-compatible-spreadsheet.cy.ts | 32 + ...mple-frozen-columns-and-column-group.cy.ts | 62 +- .../e2e/example-frozen-columns-and-rows.cy.ts | 94 +- cypress/e2e/example-frozen-rows.cy.ts | 94 +- cypress/e2e/example-grouping-esm.cy.ts | 51 +- cypress/e2e/example-grouping.cy.ts | 51 +- cypress/e2e/example-infinite-scroll-esm.cy.ts | 28 +- .../e2e/example-plugin-custom-tooltip.cy.ts | 14 +- ...xample-row-detail-selection-and-move.cy.ts | 90 +- .../e2e/example-spreadsheet-dataview.cy.ts | 24 +- cypress/e2e/example-spreadsheet.cy.ts | 4 +- cypress/e2e/example-trading-esm.cy.ts | 42 +- .../example-web-component-pubsub-esm.cy.ts | 10 + cypress/e2e/example16-row-detail.cy.ts | 4 +- cypress/e2e/example3-editing.cy.ts | 24 +- cypress/e2e/example3b-editing-with-undo.cy.ts | 42 +- cypress/e2e/example4-model-esm.cy.ts | 40 +- .../e2e/example4-model-html-formatters.cy.ts | 24 +- cypress/e2e/example4-model.cy.ts | 42 +- cypress/support/commands.ts | 4 +- examples/example-0031-row-span-employees.html | 337 +++++++ .../example-0032-row-span-many-columns.html | 321 +++++++ ...ample-frozen-columns-and-column-group.html | 4 +- examples/example-optimizing-dataview.html | 2 +- examples/example-spreadsheet-dataview.html | 3 +- examples/example4-model-esm.html | 17 +- examples/example4-model.html | 10 +- examples/index.html | 4 +- src/models/column.interface.ts | 3 + src/models/elementPosition.interface.ts | 9 - src/models/gridOption.interface.ts | 20 +- ...oupItemMetadataProviderOption.interface.ts | 2 +- src/models/htmlElementPosition.interface.ts | 6 - src/models/index.ts | 3 +- src/models/itemMetadata.interface.ts | 4 +- src/models/position.interface.ts | 23 + src/plugins/slick.cellselectionmodel.ts | 2 +- src/slick.grid.ts | 865 +++++++++++++----- src/slick.groupitemmetadataprovider.ts | 4 +- src/styles/example-demo.scss | 30 + src/styles/slick-alpine-theme.scss | 3 + src/styles/slick-default-theme.scss | 3 + 54 files changed, 3016 insertions(+), 817 deletions(-) create mode 100644 cypress/e2e/example-0031-row-span.cy.ts create mode 100644 cypress/e2e/example-0032-row-span-many-columns.cy.ts create mode 100644 examples/example-0031-row-span-employees.html create mode 100644 examples/example-0032-row-span-many-columns.html delete mode 100644 src/models/elementPosition.interface.ts delete mode 100644 src/models/htmlElementPosition.interface.ts create mode 100644 src/models/position.interface.ts diff --git a/cypress/e2e/example-0031-row-span.cy.ts b/cypress/e2e/example-0031-row-span.cy.ts new file mode 100644 index 000000000..ba68f432f --- /dev/null +++ b/cypress/e2e/example-0031-row-span.cy.ts @@ -0,0 +1,446 @@ +describe('Example - colspan/rowspan - Employees Timesheets', { retries: 1 }, () => { + const GRID_ROW_HEIGHT = 30; + const fullTitles = [ + "Employee ID", "Employee Name", "9:00 AM", "9:30 AM", "10:00 AM", "10:30 AM", "11:00 AM", "11:30 AM", "12:00 PM", + "12:30 PM", "1:00 PM", "1:30 PM", "2:00 PM", "2:30 PM", "3:00 PM", "3:30 PM", "4:00 PM", "4:30 PM", "5:00 PM", + ]; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/examples/example-0031-row-span-employees.html`); + cy.get('h2').contains('Demonstrates'); + cy.get('h2 + p').contains('This sample demonstrates the Grid component with the row spanning feature.'); + }); + + it('should have exact column titles', () => { + cy.get('#myGrid') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should expect 1st column to be frozen (frozen)', () => { + cy.get('.grid-canvas-left .slick-cell.frozen').should('have.length', 10); + cy.get('.grid-canvas-right .slick-cell:not(.frozen)').should('have.length.above', 60); + }); + + describe('Spanning', () => { + it('should expect "Davolio", "Check Mail", and "Development" to all have rowspan of 2 in morning hours', () => { + cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2)); + + cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2)); + + cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2)); + }); + + it('should expect "Lunch Break" to span over 3 columns and over all rows', () => { + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10)); + }); + + it('should expect a large "Development" section that spans over multiple columns & rows in the afternoon', () => { + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5)); + }); + }); + + describe('Basic Key Navigations', () => { + it('should start at Employee 10001, then type "End" key and expect to be in "Team Meeting" between 4:30-5:00pm', () => { + cy.get('[data-row=0] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l0.r0.active').should('contain', '10001'); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + }); + + it('should start at Employee 10002, then type "End" key and also expect to be in "Team Meeting" between 4:30-5:00pm', () => { + cy.get('[data-row=1] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l0.r0.active').should('contain', '10002'); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + }); + + it('should start at Employee 10004, then type "ArrowRight" key twice and expect to be in "Check Mail" between 9:00-10:30am', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{rightarrow}{rightarrow}'); + cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + }); + + it('should start at Employee 10004, then type "ArrowRight" key 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); + cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + }); + + it('should start at Employee 10004, then type "ArrowRight" key 5x times and expect to be in "Lunch Break"', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, then type "ArrowRight" key 6x times and expect to be in "Development" between 2:30-3:30pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + }); + + // then rollback by going backward + it('should be on Employee 10004 row at previous "Development" cell, then type "ArrowLeft" key once and expect to be in "Lunch Break"', () => { + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get('@active_cell').type('{leftarrow}'); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key once and expect to be in "Conference" between 4:00-5:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}'); + cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}{leftarrow}{leftarrow}'); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 4x times and expect to be back to "Lunch Break"', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); + cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + }); + + // going down + it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}'); + cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}'); + cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}'); + cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}'); + cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + }); + + // going up from inverse + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}'); + cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}'); + cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}'); + cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}{uparrow}'); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + }); + }); + + describe('Grid Navigate Functions', () => { + it('should start at Employee 10004, then type "Navigate Right" twice and expect to be in "Check Mail" between 9:00-10:30am', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-test="goto-next"]') + .click() + .click(); + cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + }); + + it('should start at Employee 10004, then type "Navigate Right" 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-test="goto-next"]') + .click() + .click() + .click() + .click(); + cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + }); + + it('should start at Employee 10004, then type "Navigate Right" 5x times and expect to be in "Lunch Break"', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-test="goto-next"]') + .click() + .click() + .click() + .click() + .click(); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, then type "Navigate Right" 6x times and expect to be in "Development" between 2:30-3:30pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-test="goto-next"]') + .click() + .click() + .click() + .click() + .click() + .click(); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + }); + + // then rollback by going backward + it('should be on Employee 10004 row at previous "Development" cell, then type "Navigate Left" once and expect to be in "Lunch Break"', () => { + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get('[data-test="goto-prev"]').click(); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" once and expect to be in "Conference" between 4:00-5:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get('[data-test="goto-prev"]').click(); + cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get('[data-test="goto-prev"]') + .click() + .click() + .click(); + cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 4x times and expect to be back to "Lunch Break"', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-prev"]') + .click() + .click() + .click() + .click(); + cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + }); + + it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { + cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('@active_cell').type('{end}'); + cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-prev"]') + .click() + .click() + .click() + .click() + .click(); + cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + }); + + // going down + it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click(); + cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click(); + cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click(); + cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click() + .click(); + cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + }); + + // going up from inverse + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click() + .click(); + cy.get('[data-test="goto-up"]') + .click(); + cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click() + .click(); + cy.get('[data-test="goto-up"]') + .click() + .click(); + cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click() + .click(); + cy.get('[data-test="goto-up"]') + .click() + .click() + .click(); + cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + }); + + it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-test="goto-down"]') + .click() + .click() + .click() + .click(); + cy.get('[data-test="goto-up"]') + .click() + .click() + .click() + .click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + }); + }); + + describe('Grid Editing', () => { + it('should toggle editing', () => { + cy.get('#isEditable').contains('false'); + cy.get('[data-row=0] > .slick-cell.l4.r4').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active .editor-text').should('not.exist'); + + cy.get('[data-test=toggle-editing]').click(); + cy.get('#isEditable').contains('true'); + + cy.get('[data-row=0] > .slick-cell.l4.r4').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').should('exist'); + cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').type('Team Meeting.xyz{enter}'); + }); + + // going down + it('should have changed active cell to "Support" between 9:30-11:00am', () => { + cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text') + .invoke('val') + .then(text => expect(text).to.eq('Support')); + cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text').type('Support.xyz{enter}'); + }); + + it('should have changed active cell to "Check Email" between 9:00-10:30am', () => { + cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text') + .invoke('val') + .then(text => expect(text).to.eq('Check Mail')); + cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text').type('Check Mail.xyz{enter}'); + }); + + it('should have changed active cell to "Task Assign" between 9:00-11:00am', () => { + cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text') + .invoke('val') + .then(text => expect(text).to.eq('Task Assign')); + cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text').type('Task Assign.xyz{enter}'); + }); + + it('should have changed active cell to "Support" between 10:00-11:30am', () => { + cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text') + .invoke('val') + .then(text => expect(text).to.eq('Support')); + cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text').type('Support.xyz{enter}'); + }); + + it('should have changed active cell to "Testing" and cancel editing when typing "Escape" key', () => { + cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text') + .invoke('val') + .then(text => expect(text).to.eq('Testing')); + cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').type('{esc}'); + cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').should('not.exist'); + }); + }); +}); diff --git a/cypress/e2e/example-0032-row-span-many-columns.cy.ts b/cypress/e2e/example-0032-row-span-many-columns.cy.ts new file mode 100644 index 000000000..0572fa19a --- /dev/null +++ b/cypress/e2e/example-0032-row-span-many-columns.cy.ts @@ -0,0 +1,442 @@ +describe('Example - colspan & rowspan spanning many columns', { retries: 0 }, () => { + const GRID_ROW_HEIGHT = 25; + const fullTitles = [ + 'Title', + 'Revenue Growth', 'Pricing Policy', 'Policy Index', 'Expense Control', 'Excess Cash', 'Net Trade Cycle', 'Cost of Capital', + 'Revenue Growth', 'Pricing Policy', 'Policy Index', 'Expense Control', 'Excess Cash', 'Net Trade Cycle', 'Cost of Capital', + 'Revenue Growth', 'Pricing Policy', 'Policy Index', 'Expense Control', 'Excess Cash', 'Net Trade Cycle', 'Cost of Capital' + ]; + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/examples/example-0032-row-span-many-columns.html`); + cy.get('h2').contains('Demonstrates'); + cy.get('h2 + p').contains('This page demonstrates colspan & rowspan using DataView with item metadata.'); + }); + + it('should have exact column titles', () => { + cy.get('#myGrid') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should drag Title column to swap with 2nd column "Revenue Growth" in the grid and expect rowspan to stay at same position with Task 0 to spread instead', () => { + const expectedTitles = ['Revenue Growth', 'Title', 'Pricing Policy']; + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l0.r0.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + cy.get('.slick-header-columns').children('.slick-header-column:nth(0)').contains('Title').drag('.slick-header-column:nth(1)'); + cy.get('.slick-header-column:nth(0)').contains('Revenue Growth'); + cy.get('.slick-header-column:nth(1)').contains('Title'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l1.r1`).should('not.exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l1.r1`).should('contain', 'Task 3'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + cy.get('.slick-header-columns') + .children() + .each(($child, index) => { + if (index < expectedTitles.length) { + expect($child.text()).to.eq(expectedTitles[index]); + } + }); + }); + + it('should drag back Title column to reswap with 2nd column "Revenue Growth" in the grid and expect rowspan to stay at same position with Revenue Growth to now spread', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1.r1.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + cy.get('.slick-header-columns').children('.slick-header-column:nth(0)').contains('Revenue Growth').drag('.slick-header-column:nth(1)'); + cy.get('.slick-header-column:nth(0)').contains('Title'); + cy.get('.slick-header-column:nth(1)').contains('Revenue Growth'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l0.r0`).should('not.exist'); + + const expectedTitles = ['Title', 'Revenue Growth', 'Pricing Policy']; + cy.get('.slick-header-columns') + .children() + .each(($child, index) => { + if (index < expectedTitles.length) { + expect($child.text()).to.eq(expectedTitles[index]); + } + }); + }); + + describe('spanning', () => { + it('should expect first row to be regular rows without any spanning', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 0'); + + for (let i = 2; i <= 6; i++) { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l${i}.r${i}`).should('exist'); + } + }); + + it('should expect 1st row, second cell to span (rowspan) across 3 rows', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1).rowspan`).should(($el) => { + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3); + }); + + for (let i = 2; i < 14; i++) { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(${i})`).contains(/\d+$/); // use regexp to make sure it's a number + } + }); + + it('should expect 3rd row first cell to span (rowspan) across 3 rows', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l0.r0.rowspan`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l0.r0.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + for (let i = 2; i <= 5; i++) { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(${i})`).contains(/\d+$/); + } + }); + + it('should expect 4th row to have 2 sections (blue, green) spanning across 3 rows (rowspan) and 2 columns (colspan)', () => { + // blue rowspan section + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l1.r1.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l2.r2`) + .should('exist') + .contains(/\d+$/); + + // green colspan/rowspan section + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l3.r7`) + .should('exist') + .contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l8.r8`) + .should('exist') + .contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l9.r9`) + .should('exist') + .contains(/\d+$/); + }); + + it('should click on "Toggle blue cell colspan..." and expect colspan to widen from 1 column to 2 columns and from 5 rows to 3 rowspan', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l1.r1.rowspan`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l1.r1.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) + ); + + cy.get('[data-test="toggleSpans"]').click(); + cy.get('.slick-cell.l1.r1.rowspan').should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell.l1.r2.rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should expect Task 8 on 2nd column to have rowspan spanning 80 cells', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 8'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell:nth(1).rowspan`).contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell:nth(1).rowspan`).should(($el) => { + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80); + }); + }); + + it('should scroll to the right and still expect spans without any extra texts', () => { + cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(400, 0).wait(10); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0).rowspan`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1).rowspan`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1).rowspan`).should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + + // next rows are regular cells + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell.l3.r3`).should('not.exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell.l4.r4`).should('not.exist'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell.l3.r3`).should('not.exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell.l3.r3`).should('not.exist'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 6}px;"] > .slick-cell.l4.r4`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 6}px;"] > .slick-cell.l4.r4`).should('exist'); + }); + + it('should scroll back to left and expect Task 8 to have 2 different spans (Revenue Grow: rowspan=80, Policy Index: rowspan=2000,colspan=2)', () => { + cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).wait(10); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 8'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell:nth(1).rowspan`).should(($el) => { + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 80); + }); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell:nth(1)`).contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell:nth(2)`).contains(/\d+$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 8}px;"] > .slick-cell.l3.r4`).should('exist'); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 9}px;"] > .slick-cell.l0.r0`).should('contain', 'Task 9'); + }); + + it('should scroll to row 85 and still expect 3 spans in the screen, "Revenue Growth" and "Policy Index" spans', () => { + cy.get('[data-test="clearScrollTo"]').click(); + cy.get('[data-test="nbrows"]').type('{backspace}85'); + cy.get('[data-test="scrollToBtn"]').click(); + + // left dashed rowspan "Revenue Growth" + cy.get(`[data-row=85] > .slick-cell.l0.r0`).should('contain', 'Task 85'); + cy.get(`[data-row=85] > .slick-cell.l2.r2`).contains(/\d+$/); + + // rowspan middle (yellowish) "Policy Index" + cy.get(`[data-row=88] > .slick-cell.l0.r0`).should('contain', 'Task 88'); + cy.get(`[data-row=88] > .slick-cell.l1.r1`).should('exist'); + }); + + it('should scroll to the end of the grid and still expect "PolicyIndex" column to span across 2 columns and rows until the end of the grid', () => { + cy.get('[data-test="clearScrollTo"]').click(); + cy.get('[data-test="nbrows"]').type('{backspace}490'); + cy.get('[data-test="scrollToBtn"]').click(); + + cy.get(`[data-row=485] > .slick-cell.l0.r0`).should('contain', 'Task 485'); + + cy.get(`[data-row=499] > .slick-cell.l0.r0`).should('contain', 'Task 499'); + cy.get(`[data-row=499] > .slick-cell.l1.r1`).should('exist'); + cy.get(`[data-row=499] > .slick-cell.l2.r2`).should('exist'); + cy.get(`[data-row=499] > .slick-cell.l5.r5`).should('exist'); + }); + + it('should load 5K data and expect 8.3 rowspan row height to increase/decrease when data changes from 500 to 5K back to 500', () => { + cy.get('[data-row=8] .slick-cell.l3.r4').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * (500 - 8)) + ); + + cy.get('[data-test="add-5k-rows-btn"]').click(); + cy.get('[data-row=8] .slick-cell.l3.r4').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * (5000 - 8)) + ); + + cy.get('[data-test="add-500-rows-btn"]').click(); + cy.get('[data-row=8] .slick-cell.l3.r4').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * (500 - 8)) + ); + }); + }); + + describe('Basic Key Navigations', () => { + it('should scroll back to top', () => { + cy.get('[data-test="clearScrollTo"]').click(); + cy.get('[data-test="nbrows"]').type('0'); + cy.get('[data-test="scrollToBtn"]').click(); + }); + + it('should start at Task 6 on PolicyIndex column, then type "Arrow Up" key and expect active cell to become the green section in the middle', () => { + cy.get('[data-row=6] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=6] > .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{uparrow}'); + cy.get('[data-row=3] > .slick-cell.l1.r2.active').should('have.length', 1); + }); + + it('should start at Task 6 on PricingPolicy column, then type "Arrow Left" key and expect active cell to become the green section in the middle', () => { + cy.get('[data-row=6] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=6] > .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('[data-test="toggleSpans"]').click(); + cy.get('@active_cell').type('{leftarrow}'); + cy.get('[data-row=3] .slick-cell.l1.r1.active').should('have.length', 1); + }); + + it('should start at Task 5 on Task 5 column, then type "Arrow Right" key 3x times and expect active cell to become the wide green section in the middle', () => { + cy.get('[data-row=5] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=5] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{rightarrow}{rightarrow}'); + cy.get('[data-row=5] .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('[data-row=5] .slick-cell.l2.r2.active').type('{rightarrow}'); + cy.get('[data-row=3] .slick-cell.l3.r7.active').should(($el) => + expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3) + ); + }); + + it('should start at Task 2 on PricingPolicy column, then type "Arrow Left" key and expect active cell to become the dashed section beside Task 0-3 on RevenueGrowth column', () => { + cy.get('[data-row=2] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=2] > .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{leftarrow}'); + cy.get('[data-row=0] > .slick-cell.l1.r1.active').should('have.length', 1); + }); + + it('should start at Task 2 on PricingPolicy column, then type "Arrow Left" key twice and expect active cell to become Task 2 cell', () => { + cy.get('[data-row=2] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=2] > .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{leftarrow}{leftarrow}'); + cy.get('[data-row=2] > .slick-cell.l0.r0.active').contains('Task 2'); + cy.get('[data-row=2] > .slick-cell.l0.r0.active').should('have.length', 1); + }); + + it('should start at Task 2 on PricingPolicy column, then type "Home" key and expect active cell to become Task 2 cell', () => { + cy.get('[data-row=2] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=2] .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{home}'); + cy.get('[data-row=2] .slick-cell.l0.r0.active').contains('Task 2'); + cy.get('[data-row=2] .slick-cell.l0.r0.active').should('have.length', 1); + }); + + it('should start at Task 2 on PricingPolicy column, then type "End" key and expect active cell to become Task 2 cell', () => { + cy.get('[data-row=2] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=2] .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=2] .slick-cell.l21.r21.active').should('have.length', 1); + }); + + it('should start at RevenueGrowth column on first dashed cell, then type "Ctrl+End" then "Ctrl+Home" keys and expect active cell to go to bottom/top of grid on same column', () => { + cy.get('[data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l2.r2.active').should('have.length', 1); + cy.get('@active_cell').type('{ctrl}{end}', { release: false }); + cy.get('[data-row=499] > .slick-cell.l21.r21.active').should('have.length', 1); + cy.get('[data-row=499] > .slick-cell.l21.r21.active').type('{ctrl}{home}', { release: false }); + cy.get('[data-row=0] .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('[data-row=0] .slick-cell.l1.r1').should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 3)); + }); + + it('should start at first row on PolicyIndex column, then type "Ctrl+DownArrow" keys and expect active cell to become yellowish section', () => { + cy.get('[data-row=0] > .slick-cell.l3.r3').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l3.r3.active').should('have.length', 1); + cy.get('@active_cell').type('{ctrl}{downarrow}', { release: false }); + cy.get('[data-row=8] > .slick-cell.l3.r4.active').should('have.length', 1); + }); + + it('should start at first row on ExpenseControl column, then type "Ctrl+DownArrow" keys and expect active cell to become the cell just above the yellowish section', () => { + cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); + cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('have.length', 1); + cy.get('@active_cell').type('{ctrl}{downarrow}', { release: false }); + cy.get('[data-row=7] .slick-cell.l4.r4.active').should('have.length', 1); + }); + + it('should start at Task 13, type "End" key and expect active cell to be the last span cell', () => { + cy.get('[data-row=13] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=13] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=13] > .slick-cell.l21.r21.active').should('have.length', 1); + }); + + it('should go to Task 13 last cell, type "Home" key and expect active cell to become Task 13', () => { + cy.get('[data-row=13] > .slick-cell.l21.r21').as('active_cell').click(); + cy.get('[data-row=13] > .slick-cell.l21.r21.active').should('have.length', 1); + cy.get('@active_cell').type('{home}'); + cy.get('[data-row=13] > .slick-cell.l0.r0.active').should('have.length', 1); + }); + + it('should start at Task 15, type "End" key and expect active cell to be on the last orange span cell', () => { + cy.get('[data-row=15] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=15] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=15] > .slick-cell.l18.r21.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l18.r21.active').type('{home}'); + }); + + it('should start at Task 17, type "End" key and expect active cell to be on the last orange span cell', () => { + cy.get('[data-row=17] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=17] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{end}'); + cy.get('[data-row=15] > .slick-cell.l18.r21.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l18.r21.active').type('{home}'); + }); + + it('should start at Task 5, type "ArrowRight" key 3x times and expect active cell to be at cell 3.3', () => { + cy.get('[data-row=15] > .slick-cell.l0.r0').type('{ctrl}{uparrow}'); + cy.get('[data-row=5] > .slick-cell.l0.r0').contains('Task 5').as('active_cell').click(); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}'); + cy.get('[data-row=3] > .slick-cell.l3.r7.active').should('have.length', 1); + }); + + it('should start at Task 5, type "ArrowRight" key 4x times and expect active cell to be at cell 5.8', () => { + cy.get('[data-row=5] > .slick-cell.l0.r0').contains('Task 5').as('active_cell').click(); + cy.get('[data-row=5] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); + cy.get('[data-row=5] > .slick-cell.l8.r8.active').should('have.length', 1); + }); + + it('should start at Task 5, type "ArrowRight" key 4x times, then "ArrowLeft" 4x times and be back at Task 5', () => { + cy.get('[data-row=5] > .slick-cell.l0.r0').contains('Task 5').as('active_cell').click(); + cy.get('[data-row=5] > .slick-cell.l0.r0.active').should('have.length', 1); + cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); + cy.get('[data-row=5] > .slick-cell.l0.r0.active').should('have.length', 1); + }); + + it('should start at Task 2 on Pricing Policy column and type "PageDown" key 3x times and be on Task 59 on same column', () => { + cy.get('[data-row=2] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pagedown}'); + cy.get('[data-row=59] > .slick-cell.l2.r2.active').should('have.length', 1); + }); + + it('should start at Task 59 on Pricing Policy column and type "PageUp" key 3x times and be back to Task 2 on same column', () => { + cy.get('[data-row=59] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}{pageup}{pageup}'); + cy.get('[data-row=2] > .slick-cell.l2.r2.active').should('have.length', 1); + }); + + it('should start at Task 0 on Policy Index column and type "PageDown" key 2x times but expect active cell to stay on initial cell but still scroll down around Task 40', () => { + cy.get('[data-row=0] > .slick-cell.l3.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}'); + cy.get('[data-row=0] > .slick-cell.l3.r3.active').should('have.length', 1); + cy.get('[data-row=40]').should('be.visible'); + }); + + it('should start at Task 1 on Excess Cash column and type "PageDown" key 4x times and be on Task 77 on same column', () => { + cy.get('[data-row=0] > .slick-cell.l3.r3.active').type('{ctrl}{uparrow}'); + cy.get('[data-row=1] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pagedown}{pagedown}'); + cy.get('[data-row=77] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + + it('should start at Task 77 on Excess Cash column and type "PageDown" key 4x times and be on Task 105 on same column', () => { + cy.get('[data-row=77] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('[data-row=105] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + + it('should start at Task 105 on Excess Cash column and type "PageUp" key once and be on Task 85 on same column', () => { + cy.get('[data-row=105] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}'); + cy.get('[data-row=85] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + + it('should start at Task 85 on Excess Cash column and type "PageUp" key once and be on Task 66 on same column', () => { + cy.get('[data-row=85] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}'); + cy.get('[data-row=66] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + + it('should start at Task 66 on Excess Cash column and type "PageUp" key 3x times and be on Task 9 on same column', () => { + cy.get('[data-row=66] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}{pageup}{pageup}'); + cy.get('[data-row=9] > .slick-cell.l5.r5.active').should('have.length', 1).type('{pageup}'); + }); + + it('should start at Task 0 on Revenue Growth column and type "PageDown" key once and be on Task 88 on same column', () => { + cy.get('[data-row=0] > .slick-cell.l1.r1').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('[data-row=88] > .slick-cell.l1.r1.active').should('have.length', 1); + }); + + it('should start at Task 88 on Revenue Growth column and type "PageUp" key once and be on Task 8 on same column', () => { + cy.get('[data-row=88] > .slick-cell.l1.r1').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}'); + cy.get('[data-row=8] > .slick-cell.l1.r1.active').should('have.length', 1); + }); + + it('should start at Task 9 on Excess Cash column and type "PageUp" key once and be on Task 0 on same column', () => { + cy.get('[data-row=9] > .slick-cell.l5.r5').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}'); + cy.get('[data-row=0] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + + it('should start at Task 9 on Excess Cash column and type "PageDown" key 26x times and be on last Task 499 on same column', () => { + cy.get('[data-row=9] > .slick-cell.l5.r5').as('active_cell').click(); + let command = ''; + for (let i = 1; i <= 26; i++) { + command += '{pagedown}'; + } + cy.get('@active_cell').type(command); + cy.get('[data-row=499] > .slick-cell.l5.r5.active').should('have.length', 1); + }); + }); +}); diff --git a/cypress/e2e/example-auto-scroll-when-dragging.cy.ts b/cypress/e2e/example-auto-scroll-when-dragging.cy.ts index 72c030f2a..5ccab88b0 100644 --- a/cypress/e2e/example-auto-scroll-when-dragging.cy.ts +++ b/cypress/e2e/example-auto-scroll-when-dragging.cy.ts @@ -190,15 +190,15 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => { }); it('should have a frozen grid with 4 containers with 2 columns on the left and 3 rows on the top after click Set/Clear Frozen button', () => { - cy.get('#myGrid [style="top: 0px;"]').should('have.length', 1); - cy.get('#myGrid2 [style="top: 0px;"]').should('have.length', 1); + cy.get('#myGrid div.slick-row[style*="top: 0px"]').should('have.length', 1); + cy.get('#myGrid2 div.slick-row[style*="top: 0px"]').should('have.length', 1); cy.get('#toggleFrozen').click(); - cy.get('#myGrid [style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('#myGrid2 [style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('#myGrid .grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 2 * 2); - cy.get('#myGrid2 .grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 2 * 2); + cy.get('#myGrid div.slick-row[style*="top: 0px"]').should('have.length', 2 * 2); + cy.get('#myGrid2 div.slick-row[style*="top: 0px"]').should('have.length', 2 * 2); + cy.get('#myGrid .grid-canvas-left > [style*="top: 0px"]').children().should('have.length', 2 * 2); + cy.get('#myGrid2 .grid-canvas-left > [style*="top: 0px"]').children().should('have.length', 2 * 2); cy.get('#myGrid .grid-canvas-top').children().should('have.length', 3 * 2); cy.get('#myGrid2 .grid-canvas-top').children().should('have.length', 3 * 2); }); @@ -268,8 +268,8 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => { it('should have a frozen & grouping by Duration grid after click Set/Clear grouping by Duration button', { scrollBehavior: false }, () => { cy.get('#toggleGroup').trigger('click'); - cy.get('#myGrid [style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('#myGrid2 [style="top: 0px;"]').should('have.length', 2 * 2); + cy.get('#myGrid div.slick-row[style*="top: 0px;"]').should('have.length', 2 * 2); + cy.get('#myGrid2 div.slick-row[style*="top: 0px;"]').should('have.length', 2 * 2); cy.get('#myGrid .grid-canvas-top.grid-canvas-left').contains('Duration'); cy.get('#myGrid2 .grid-canvas-top.grid-canvas-left').contains('Duration'); }); @@ -282,7 +282,7 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => { cy.get('@viewport').invoke('scrollTop').then(scrollAfter => { expect(scrollBefore).to.be.lessThan(scrollAfter); cy.dragEnd(selector); - cy.get(selector + ' [style="top: 350px;"].slick-group').should('not.be.hidden');; + cy.get(selector + ' [style*="top: 350px;"].slick-group').should('not.be.hidden');; }); }); } @@ -295,8 +295,8 @@ describe('Example - Auto scroll when dragging', { retries: 1 }, () => { it('should reset to default grid when click Set/Clear Frozen button and Set/Clear grouping button', () => { cy.get('#toggleFrozen').trigger('click'); cy.get('#toggleGroup').trigger('click'); - cy.get('#myGrid [style="top: 0px;"]').should('have.length', 1); - cy.get('#myGrid2 [style="top: 0px;"]').should('have.length', 1); + cy.get('#myGrid div.slick-row[style*="top: 0px;"]').should('have.length', 1); + cy.get('#myGrid2 div.slick-row[style*="top: 0px;"]').should('have.length', 1); }); }); diff --git a/cypress/e2e/example-autotooltips.cy.ts b/cypress/e2e/example-autotooltips.cy.ts index a9b786b89..465e3d353 100644 --- a/cypress/e2e/example-autotooltips.cy.ts +++ b/cypress/e2e/example-autotooltips.cy.ts @@ -34,22 +34,22 @@ describe('Example AutoTooltips Plugin', () => { .trigger('mousemove', 'bottomRight') .trigger('mouseup', 'bottomRight', { which: 1, force: true }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).trigger('mouseover'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).should('have.attr', 'title', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).trigger('mouseover'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3.r3`).should('have.attr', 'title', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).trigger('mouseover'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).should('have.attr', 'title', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).trigger('mouseover'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l3.r3`).should('have.attr', 'title', '01/01/2009'); }); it('should hover over "Finish" cell to see tooltip', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).trigger('mouseover'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('not.have.attr', 'title', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).trigger('mouseover'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('not.have.attr', 'title', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).trigger('mouseover'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('not.have.attr', 'title', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).trigger('mouseover'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4.r4`).should('not.have.attr', 'title', '01/05/2009'); }); }); \ No newline at end of file diff --git a/cypress/e2e/example-checkbox-header-row.cy.ts b/cypress/e2e/example-checkbox-header-row.cy.ts index 35c234c72..c818a80ec 100644 --- a/cypress/e2e/example-checkbox-header-row.cy.ts +++ b/cypress/e2e/example-checkbox-header-row.cy.ts @@ -119,8 +119,8 @@ describe('Example - Checkbox Header Row', () => { // Row index 3, 5 and 21 (last one will be on 2nd page) cy.get('input[type="checkbox"]:checked').should('have.length', 2); // 2x in current page and 1x in next page - cy.get('[style="top: 75px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); - cy.get('[style="top: 125px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); + cy.get('[style*="top: 75px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); + cy.get('[style*="top: 125px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); }); it('should go to next page and expect 1 row selected in that second page', () => { @@ -128,7 +128,7 @@ describe('Example - Checkbox Header Row', () => { .click(); cy.get('input[type="checkbox"]:checked').should('have.length', 1); // only 1x row in page 2 - cy.get('[style="top: 100px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); + cy.get('[style*="top: 100px;"] > .slick-cell:nth(0) input[type="checkbox"]').should('be.checked'); }); it('should click on "Select All" checkbox and expect all rows selected in current page', () => { diff --git a/cypress/e2e/example-checkbox-row-select.cy.ts b/cypress/e2e/example-checkbox-row-select.cy.ts index 37f919430..4a081d0f5 100644 --- a/cypress/e2e/example-checkbox-row-select.cy.ts +++ b/cypress/e2e/example-checkbox-row-select.cy.ts @@ -31,8 +31,8 @@ describe('Example - Checkbox Row Select', () => { }); it('should be able to select first 2 rows and now expect 3 rows selected', () => { - cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) input[type=checkbox]`).click(); - cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) input[type=checkbox]`).click(); + cy.get(`.slick-row[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) input[type=checkbox]`).click(); + cy.get(`.slick-row[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) input[type=checkbox]`).click(); cy.get('#myGrid') .find('.slick-cell-checkboxsel input:checked') @@ -43,7 +43,7 @@ describe('Example - Checkbox Row Select', () => { cy.get('#unselectRow5') .click(); - cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) input[type=checkbox]`) + cy.get(`.slick-row[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) input[type=checkbox]`) .should('not.be.checked'); cy.get('#myGrid') diff --git a/cypress/e2e/example-colspan.cy.ts b/cypress/e2e/example-colspan.cy.ts index e605e873d..b78075496 100644 --- a/cypress/e2e/example-colspan.cy.ts +++ b/cypress/e2e/example-colspan.cy.ts @@ -1,45 +1,98 @@ describe('Example - Column Span & Header Grouping', { retries: 1 }, () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows - const GRID_ROW_HEIGHT = 25; - const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven']; - for (let i = 0; i < 30; i++) { - fullTitles.push(`Mock${i}`); - } + const GRID_ROW_HEIGHT = 25; + const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven']; + for (let i = 0; i < 30; i++) { + fullTitles.push(`Mock${i}`); + } - it('should display Example title', () => { - cy.visit(`${Cypress.config('baseUrl')}/examples/example-colspan.html`); - cy.get('h2').contains('Demonstrates'); - cy.get('h2 + ul > li').first().contains('column span'); + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseUrl')}/examples/example-colspan.html`); + cy.get('h2').contains('Demonstrates'); + cy.get('h2 + ul > li').first().contains('column span'); + }); + + it('should have exact column titles', () => { + cy.get('#myGrid') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should expect 1st row to be 1 column spanned to the entire width', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l0.r5`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.exist'); + }); + + it('should expect 2nd row to be 4 columns and not be spanned', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell.l0.r0`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1).l1.r3`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + }); + + it('should expect 3rd row to be 1 column spanned to the entire width', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell.l0.r5`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('not.exist'); + }); + + it('should expect 4th row to be 4 columns and not be spanned', () => { + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + }); + + describe('Basic Key Navigations', () => { + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and be on Task 20 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('[data-row=20] > .slick-cell.l0.r5.active').should('have.length', 1); + }); + + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and be on Task 39 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}'); + cy.get('[data-row=39] > .slick-cell.l1.r3.active').should('have.length', 1); + }); + + it('should start at Task 39 on Duration colspan 5 days and type "PageUp" key 2x times and be on Task 1 with full colspan', () => { + cy.get('[data-row=39] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}{pageup}'); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should have exact column titles', () => { - cy.get('#myGrid') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should expect 1st row to be 1 column spanned to the entire width', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.exist'); + it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); + cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should expect 2nd row to be 4 columns and not be spanned', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{downarrow}'); + cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should expect 3rd row to be 1 column spanned to the entire width', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('not.exist'); + it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{downarrow}{downarrow}'); + cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should expect 4th row to be 4 columns and not be spanned', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); + }); }); diff --git a/cypress/e2e/example-composite-editor-modal-dialog.cy.ts b/cypress/e2e/example-composite-editor-modal-dialog.cy.ts index 0e1053b02..c8de1441c 100644 --- a/cypress/e2e/example-composite-editor-modal-dialog.cy.ts +++ b/cypress/e2e/example-composite-editor-modal-dialog.cy.ts @@ -23,11 +23,11 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel }); it('should expect first row to include "Task 0" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('exist'); }); it('should open the Edit Modal and expect same data to include "Task 0" and other specific properties', () => { @@ -110,11 +110,11 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel cy.wait(10); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 0); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 0); }); it('should open the Mass Update try to change "Duration" below 5 and expect it to become invalid', () => { @@ -161,29 +161,29 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel }); it('Should expect to see "Duration" of "27 days" and "Effort-Driven" to be enabled accross the entire grid', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 4'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 5'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 5'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); }); it('Should expect an Alert about missing row selection before executing Mass Selection', () => { @@ -198,8 +198,8 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel }); it('Should select row 2 and 3 and change ', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).click(); cy.get('[data-test="mass-selection-button"]').click(); @@ -223,31 +223,31 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel }); it('Should expect to see "Duration" of "27 days" and "Effort-Driven" to be enabled accross the entire grid', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', '7 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', '7 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 4'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); - - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 5'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0000'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).should('contain', '7 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(3)`).should('contain', '7 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 5'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).should('contain', '27 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); }); it('Should open a Create Modal window and click on Effort-Driven then expect 3 validation errors', () => { @@ -316,15 +316,15 @@ describe('Example - Composite Editor Modal with Create/Edit/Mass-Update/Mass-Sel }); it('Should expect to see "Duration" of "27 days" and "Effort-Driven" to be enabled accross the entire grid', () => { - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(1)`).should('contain', 'Task 8899'); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(2)`).should('contain', 'random text'); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(3)`).should('contain', '9 days'); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(4)`).each($cell => { + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(1)`).should('contain', 'Task 8899'); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(2)`).should('contain', 'random text'); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(3)`).should('contain', '9 days'); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(4)`).each($cell => { const htmlText = $cell.html(); expect(htmlText).to.eq(''); }); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(6)`).should('contain', ''); - cy.get(`[style="top: 12500px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(5)`).should('contain', '02/02/2020'); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(6)`).should('contain', ''); + cy.get(`[style*="top: 12500px;"] > .slick-cell:nth(7)`).find('.sgi-check').should('have.length', 1); }); }); diff --git a/cypress/e2e/example-csp-header.cy.ts b/cypress/e2e/example-csp-header.cy.ts index 582f1cda6..ed99e37ce 100644 --- a/cypress/e2e/example-csp-header.cy.ts +++ b/cypress/e2e/example-csp-header.cy.ts @@ -1,5 +1,4 @@ describe('Example CSP Header - with Column Span & Header Grouping', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const GRID_ROW_HEIGHT = 25; const fullTitles = ['Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven']; for (let i = 0; i < 30; i++) { @@ -20,26 +19,26 @@ describe('Example CSP Header - with Column Span & Header Grouping', () => { }); it('should expect 1st row to be 1 column spanned to the entire width', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.exist'); }); it('should expect 2nd row to be 4 columns and not be spanned', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); }); it('should expect 3rd row to be 1 column spanned to the entire width', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('not.exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('not.exist'); }); it('should expect 4th row to be 4 columns and not be spanned', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(2)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(3)`).contains(/(true|false)/); }); }); diff --git a/cypress/e2e/example-draggable-grouping.cy.ts b/cypress/e2e/example-draggable-grouping.cy.ts index fdca91855..0b0f55bf0 100644 --- a/cypress/e2e/example-draggable-grouping.cy.ts +++ b/cypress/e2e/example-draggable-grouping.cy.ts @@ -1,5 +1,4 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const GRID_ROW_HEIGHT = 25; const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', 'Cost', 'Effort-Driven']; for (let i = 0; i < 30; i++) { @@ -38,13 +37,13 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="collapse-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); }); it('should click on Expand All columns and expect 1st row as grouping title and 2nd row as a regular row', () => { @@ -52,11 +51,11 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="expand-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should show 1 column title (Duration) shown in the pre-header section', () => { @@ -66,14 +65,14 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { it('should "Group by Duration then Effort-Driven" and expect 1st row to be expanded, 2nd row to be expanded and 3rd row to be a regular row', () => { cy.get('[data-test="group-duration-effort-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should show 2 column titles (Duration, Effort-Driven) shown in the pre-header section', () => { @@ -91,22 +90,22 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { }); it('should expect the grouping to be swapped as well in the grid', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should use the preheader Toggle All button and expect all groups to now be collapsed', () => { cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); }); it('should expand all rows with "Expand All" and expect all the Groups to be expanded and the Toogle All icon to be collapsed', () => { @@ -152,21 +151,21 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { it('should use the preheader Toggle All button and expect all groups to now be expanded', () => { cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) .should('have.css', 'marginLeft').and('eq', `0px`); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) .should('have.css', 'marginLeft').and('eq', `15px`); }); it('should use the preheader Toggle All button again and expect all groups to now be collapsed', () => { cy.get('.slick-preheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); }); it('should clear all groups with "Clear all Grouping" and expect all the Groups to be collapsed and the Toogle All icon to be collapsed', () => { diff --git a/cypress/e2e/example-draggable-header-grouping.cy.ts b/cypress/e2e/example-draggable-header-grouping.cy.ts index 691c867dd..0d074643a 100644 --- a/cypress/e2e/example-draggable-header-grouping.cy.ts +++ b/cypress/e2e/example-draggable-header-grouping.cy.ts @@ -1,5 +1,4 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const GRID_ROW_HEIGHT = 25; const preHeaders = ['', 'Common Factor', 'Period', 'Analysis', '']; const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', 'Cost', 'Effort-Driven']; @@ -41,13 +40,13 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="collapse-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); }); it('should click on Expand All columns and expect 1st row as grouping title and 2nd row as a regular row', () => { @@ -55,11 +54,11 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="expand-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should show 1 column title (Duration) shown in the pre-header section', () => { @@ -69,14 +68,14 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { it('should "Group by Duration then Effort-Driven" and expect 1st row to be expanded, 2nd row to be expanded and 3rd row to be a regular row', () => { cy.get('[data-test="group-duration-effort-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should show 2 column titles (Duration, Effort-Driven) shown in the pre-header section', () => { @@ -94,22 +93,22 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { }); it('should expect the grouping to be swapped as well in the grid', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should use the topheader Toggle All button and expect all groups to now be collapsed', () => { cy.get('.slick-topheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); }); it('should expand all rows with "Expand All" and expect all the Groups to be expanded and the Toogle All icon to be collapsed', () => { @@ -155,21 +154,21 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { it('should use the topheader Toggle All button and expect all groups to now be expanded', () => { cy.get('.slick-topheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) .should('have.css', 'marginLeft').and('eq', `0px`); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`) .should('have.css', 'marginLeft').and('eq', `15px`); }); it('should use the topheader Toggle All button again and expect all groups to now be collapsed', () => { cy.get('.slick-topheader-panel .slick-group-toggle-all').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Effort-Driven: True'); }); it('should clear all groups with "Clear all Grouping" and expect all the Groups to be collapsed and the Toogle All icon to be collapsed', () => { @@ -199,7 +198,7 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { .scrollTo('bottom') .wait(10); - cy.get(`#myGrid [style="top: ${GRID_ROW_HEIGHT * 499}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 499'); + cy.get(`#myGrid [style*="top: ${GRID_ROW_HEIGHT * 499}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 499'); }); it('should add 50K items and expect last row to be Task 50,000', () => { @@ -211,8 +210,8 @@ describe('Example - Draggable Grouping', { retries: 1 }, () => { .scrollTo('bottom') .wait(10); - // cy.get(`#myGrid [style="top: ${GRID_ROW_HEIGHT * 49999}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); - cy.get(`#myGrid [style="top: 1.24998e+06px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); + // cy.get(`#myGrid [style*="top: ${GRID_ROW_HEIGHT * 49999}px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); + cy.get(`#myGrid [style*="top: 1.24998e+06px;"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); }); it('should be able to call column picker from the pre-header', () => { diff --git a/cypress/e2e/example-example1-simple.cy.ts b/cypress/e2e/example-example1-simple.cy.ts index 36a04085f..b365625e1 100644 --- a/cypress/e2e/example-example1-simple.cy.ts +++ b/cypress/e2e/example-example1-simple.cy.ts @@ -87,23 +87,23 @@ describe('Example 1 - Basic Grid', () => { }); it('should scroll horizontally completely to the right and expect all cell to be rendered', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2`).contains(/[0-9]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l1`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2`).contains(/[0-9]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l1`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l2`).contains(/[0-9]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l1`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l2`).contains(/[0-9]*/); // scroll to right cy.get('.slick-viewport-top.slick-viewport-left') .scrollTo('100%', '0%', { duration: 1500 }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2`).contains(/[true|false]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell.l2`).contains(/[true|false]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l2`).contains(/[true|false]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 15}px;"] > .slick-cell.l2`).contains(/[true|false]*/); }); it('should scroll vertically to the middle of the grid and expect all cell to be rendered', () => { @@ -111,12 +111,12 @@ describe('Example 1 - Basic Grid', () => { cy.get('.slick-viewport-top.slick-viewport-left') .scrollTo('100%', '40%', { duration: 1500 }); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l2`).contains(/[true|false]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 195}px;"] > .slick-cell.l2`).contains(/[true|false]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l2`).contains(/[true|false]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l3`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l4`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 210}px;"] > .slick-cell.l2`).contains(/[true|false]*/); }); }); diff --git a/cypress/e2e/example-excel-compatible-spreadsheet.cy.ts b/cypress/e2e/example-excel-compatible-spreadsheet.cy.ts index 3322a8794..f2f8f93b6 100644 --- a/cypress/e2e/example-excel-compatible-spreadsheet.cy.ts +++ b/cypress/e2e/example-excel-compatible-spreadsheet.cy.ts @@ -28,4 +28,36 @@ describe('Example - Excel-compatible spreadsheet and Cell Selection', { retries: cy.getCell(3, 2, '', { parentSelector: '#myGrid', rowHeight: cellHeight }) .should('have.text', '3'); }); + + describe('Basic Key Navigations', () => { + it('should start at B1 and type "PageDown" key once and be on B12', () => { + cy.get('#myGrid [data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('#myGrid [data-row=11] > .slick-cell.l2.r2.active').should('have.length', 1); + }); + + it('should start at B12 and type "PageDown" key 2x times and "PageUp" once be on B23', () => { + cy.get('#myGrid [data-row=11] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}'); + cy.get('#myGrid [data-row=22] > .slick-cell.l2.r2.active').should('have.length', 1); + }); + + it('should start at B23 and type "PageDown" 2x times then "Ctrl+Home" and be on 0,0', () => { + cy.get('#myGrid [data-row=22] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{ctrl}{home}'); + cy.get('#myGrid [data-row=0] > .slick-cell.l0.r0.active').should('have.length', 1); + }); + + it('should start at B1 and type "PageDown" key 3x times and then "Ctrl+UpArrow" and be on B1', () => { + cy.get('#myGrid [data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}{pagedown}{ctrl}{uparrow}'); + cy.get('#myGrid [data-row=0] > .slick-cell.l2.r2.active').should('have.length', 1); + }); + + it('should start at 0,0 and type "Ctrl+End" on bottom right at Z99', () => { + cy.get('#myGrid [data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); + cy.get('@active_cell').type('{ctrl}{end}'); + cy.get('#myGrid [data-row=99] > .slick-cell.l26.r26.active').should('have.length', 1); + }); + }); }); diff --git a/cypress/e2e/example-frozen-columns-and-column-group.cy.ts b/cypress/e2e/example-frozen-columns-and-column-group.cy.ts index 986ff5e80..19397e8cc 100644 --- a/cypress/e2e/example-frozen-columns-and-column-group.cy.ts +++ b/cypress/e2e/example-frozen-columns-and-column-group.cy.ts @@ -21,16 +21,16 @@ describe('Example - Row Grouping Titles', () => { }); it('should have a frozen grid on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('[style="top: 0px;"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3); - cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 4); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 2); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 3); + cy.get('.grid-canvas-right > [style*="top: 0px;"]').children().should('have.length', 4); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -46,17 +46,18 @@ describe('Example - Row Grouping Titles', () => { }); it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.contains('Remove Frozen Columns') + cy.get('[data-test="remove-frozen-btn"]') + .contains('Remove Frozen Columns') .click({ force: true }); - cy.get('[style="top: 0px;"]').should('have.length', 1); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 7); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 1); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 7); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(3)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(3)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -72,19 +73,20 @@ describe('Example - Row Grouping Titles', () => { }); it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.contains('Set 3 Frozen Columns') + cy.get('[data-test="set-frozen-btn"]') + .contains('Set 3 Frozen Columns') .click({ force: true }); - cy.get('[style="top: 0px;"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3); - cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 4); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 2); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 3); + cy.get('.grid-canvas-right > [style*="top: 0px;"]').children().should('have.length', 4); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -138,4 +140,14 @@ describe('Example - Row Grouping Titles', () => { .children() .each(($child, index) => expect($child.text()).to.eq(fullTitlesWithoutId[index])); }); + + it('should scroll to the bottom of the grid and expect last row to contain Task 49999', () => { + cy.get('#myGrid') + .find('.slick-viewport-top.slick-viewport-right') + .scrollTo('bottom') + .wait(10); + + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(0)`) + .should('have.text', 'Task 49999'); + }); }); diff --git a/cypress/e2e/example-frozen-columns-and-rows.cy.ts b/cypress/e2e/example-frozen-columns-and-rows.cy.ts index 738b97023..01d5b69c7 100644 --- a/cypress/e2e/example-frozen-columns-and-rows.cy.ts +++ b/cypress/e2e/example-frozen-columns-and-rows.cy.ts @@ -22,88 +22,98 @@ describe('Example - Frozen Columns & Rows', { retries: 1 }, () => { }); it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 6 columns on the right', () => { - cy.get('[style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 8 * 2); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 2 * 2); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 3 * 2); + cy.get('.grid-canvas-right > [style*="top: 0px;"]').children().should('have.length', 8 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); // top-right - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 5'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 6'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 5'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 6'); // bottom-right - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '5'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 25px;"] > .slick-cell:nth(4)').should('contain', '6'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', '5'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 25px;"] > .slick-cell:nth(4)').should('contain', '6'); }); it('should change frozen row and increment by 1 and expect changes to be reflected in the grid', () => { cy.get('input#frozenRow').type('{backspace}7'); cy.get('button#setFrozenRow').click(); - cy.get('[style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 8 * 2); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 2 * 2); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 3 * 2); + cy.get('.grid-canvas-right > [style*="top: 0px;"]').children().should('have.length', 8 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); // top-right - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 7'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 8'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 7'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 8'); // bottom-right - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(4)').should('contain', '7'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 25px;"] > .slick-cell:nth(4)').should('contain', '8'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(2)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', '7'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 25px;"] > .slick-cell:nth(4)').should('contain', '8'); }); it('should change frozen column and increment by 1 and expect changes to be reflected in the grid', () => { cy.get('input#frozenColumn').type('{backspace}3'); cy.get('button#setFrozenColumn').click(); - cy.get('[style="top: 0px;"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 4 * 2); - cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 7 * 2); + cy.get('div.slick-row[style*="top: 0px;"]').should('have.length', 2 * 2); + cy.get('.grid-canvas-left > [style*="top: 0px;"]').children().should('have.length', 4 * 2); + cy.get('.grid-canvas-right > [style*="top: 0px;"]').children().should('have.length', 7 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 0'); // top-right - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(3)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(3)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 7'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 8'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', 'Task 7'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="top: 25px;"] > .slick-cell:nth(1)').should('contain', 'Task 8'); // bottom-right - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 0px;"] > .slick-cell:nth(3)').should('contain', '7'); - cy.get('.grid-canvas-bottom.grid-canvas-right > [style="top: 25px;"] > .slick-cell:nth(3)').should('contain', '8'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(0)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 0px;"] > .slick-cell:nth(3)').should('contain', '7'); + cy.get('.grid-canvas-bottom.grid-canvas-right > [style*="top: 25px;"] > .slick-cell:nth(3)').should('contain', '8'); }); it('should click on "Select first 10 rows" button and expect first few rows to be selected', () => { cy.get('button#btnSelectRows').click(); cy.get('.selected').should('have.length', 10 * 11); // 10 rows * 11 columns }); + + it('should scroll to the bottom of the grid and expect last row to contain Task 49999', () => { + cy.get('#myGrid') + .find('.slick-viewport-bottom.slick-viewport-right') + .scrollTo('bottom') + .wait(10); + + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(0)`).should('have.text', '49999'); + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); + }); }); diff --git a/cypress/e2e/example-frozen-rows.cy.ts b/cypress/e2e/example-frozen-rows.cy.ts index e911ad1ea..0bc823b0a 100644 --- a/cypress/e2e/example-frozen-rows.cy.ts +++ b/cypress/e2e/example-frozen-rows.cy.ts @@ -22,71 +22,81 @@ describe('Example - Frozen Rows', { retries: 1 }, () => { }); it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 6 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); // top + bottom - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 11 * 2); + cy.get('[style*="transform: translateY(0px);"]').should('have.length', 2); // top + bottom + cy.get('.grid-canvas-left > [style*="transform: translateY(0px);"]').children().should('have.length', 11 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 49995'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '49995'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 49995'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '49995'); }); it('should change frozen row and increment by 1 and expect changes to be reflected in the grid', () => { cy.get('input#frozenRow').type('{backspace}7'); cy.get('button#setFrozenRow').click(); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); // top + bottom - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 11 * 2); + cy.get('[style*="transform: translateY(0px);"]').should('have.length', 2); // top + bottom + cy.get('.grid-canvas-left > [style*="transform: translateY(0px);"]').children().should('have.length', 11 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 49993'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '49993'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 49993'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '49993'); }); it('should uncheck "frozen bottom rows" and set it', () => { cy.get('input#frozenBottomRows').uncheck(); cy.get('button#setFrozenBottomRows').click(); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); // top + bottom - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 11 * 2); + cy.get('[style*="transform: translateY(0px);"]').should('have.length', 2); // top + bottom + cy.get('.grid-canvas-left > [style*="transform: translateY(0px);"]').children().should('have.length', 11 * 2); // top-left - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-top.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-top.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '0'); // bottom-left - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 7'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); - cy.get('.grid-canvas-bottom.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '7'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 7'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '01/01/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(5)').should('contain', '01/05/2009'); + cy.get('.grid-canvas-bottom.grid-canvas-left > [style*="transform: translateY(0px);"] > .slick-cell:nth(7)').should('contain', '7'); + }); + + it('should scroll to the bottom of the grid and expect last row to contain Task 49999', () => { + cy.get('#myGrid') + .find('.slick-viewport-bottom.slick-viewport-left') + .scrollTo('bottom') + .wait(10); + + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(0)`).should('have.text', '49999'); + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); }); }); diff --git a/cypress/e2e/example-grouping-esm.cy.ts b/cypress/e2e/example-grouping-esm.cy.ts index dab3d1763..733f8dd1a 100644 --- a/cypress/e2e/example-grouping-esm.cy.ts +++ b/cypress/e2e/example-grouping-esm.cy.ts @@ -1,5 +1,4 @@ describe('Example - Grouping & Aggregators (ESM)', { retries: 1 }, () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const GRID_ROW_HEIGHT = 28; const fullTitles = ['#', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Cost', 'Effort-Driven']; for (let i = 0; i < 30; i++) { @@ -25,13 +24,13 @@ describe('Example - Grouping & Aggregators (ESM)', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="collapse-all-btn"]').click(); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 4}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 4}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); }); it('should click on Expand All columns and expect 1st row as grouping title and 2nd row as a regular row', () => { @@ -39,42 +38,42 @@ describe('Example - Grouping & Aggregators (ESM)', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="expand-all-btn"]').click(); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should "Group by Duration then Effort-Driven" and expect 1st row to be expanded, 2nd row to be collapsed and 3rd row to have group totals', () => { cy.get('[data-test="group-duration-effort-btn"]').click(); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: True'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-0 .slick-cell:nth(2)`).should('contain', 'total: 0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-0 .slick-cell:nth(2)`).should('contain', 'total: 0'); }); it('should "Group by Duration then Effort-Driven then Percent" and expect fist 2 rows to be expanded, 3rd row to be collapsed then 4th row to have group total', () => { cy.get('[data-test="group-duration-effort-percent-btn"]').click(); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^avg: [0-9]%$/); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-2`) + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^avg: [0-9]%$/); + cy.get(`[style*="transform: translateY(${GRID_ROW_HEIGHT * 3}px);"].slick-group-totals.slick-group-level-2`) .find('.slick-cell:nth(3)').contains('avg: '); }); }); diff --git a/cypress/e2e/example-grouping.cy.ts b/cypress/e2e/example-grouping.cy.ts index 119e58803..884ad9a7c 100644 --- a/cypress/e2e/example-grouping.cy.ts +++ b/cypress/e2e/example-grouping.cy.ts @@ -1,5 +1,4 @@ describe('Example - Grouping & Aggregators', { retries: 1 }, () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const GRID_ROW_HEIGHT = 25; const fullTitles = ['#', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Cost', 'Effort-Driven']; for (let i = 0; i < 30; i++) { @@ -25,13 +24,13 @@ describe('Example - Grouping & Aggregators', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="collapse-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 2'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 4'); }); it('should click on Expand All columns and expect 1st row as grouping title and 2nd row as a regular row', () => { @@ -39,42 +38,42 @@ describe('Example - Grouping & Aggregators', { retries: 1 }, () => { cy.get('[data-test="group-duration-sort-value-btn"]').click(); cy.get('[data-test="expand-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(2)`).should('contain', '0'); }); it('should "Group by Duration then Effort-Driven" and expect 1st row to be expanded, 2nd row to be collapsed and 3rd row to have group totals', () => { cy.get('[data-test="group-duration-effort-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: True'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-1 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: True'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-0 .slick-cell:nth(2)`).should('contain', 'total: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-0 .slick-cell:nth(2)`).should('contain', 'total: 0'); }); it('should "Group by Duration then Effort-Driven then Percent" and expect fist 2 rows to be expanded, 3rd row to be collapsed then 4th row to have group total', () => { cy.get('[data-test="group-duration-effort-percent-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"].slick-group-level-0 > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Duration: 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-toggle.expanded`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"].slick-group-level-1 .slick-group-title`).should('contain', 'Effort-Driven: False'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"].slick-group-level-2 .slick-group-title`).contains(/^% Complete: [0-9]/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^avg: [0-9]%$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2`) + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2 .slick-cell:nth(3)`).contains(/^avg: [0-9]%$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"].slick-group-totals.slick-group-level-2`) .find('.slick-cell:nth(3)').contains('avg: '); }); }); diff --git a/cypress/e2e/example-infinite-scroll-esm.cy.ts b/cypress/e2e/example-infinite-scroll-esm.cy.ts index 954a2e97a..3f00b6916 100644 --- a/cypress/e2e/example-infinite-scroll-esm.cy.ts +++ b/cypress/e2e/example-infinite-scroll-esm.cy.ts @@ -19,12 +19,12 @@ describe('Example - Infinite Scroll', () => { }); it('should expect first row to include "Task 0" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-check').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-check').should('have.length', 1); }); it('should scroll to bottom of the grid and expect next batch of 50 items appended to current dataset for a total of 100 items', () => { @@ -61,10 +61,10 @@ describe('Example - Infinite Scroll', () => { cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') .scrollTo('top'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 10'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 100'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 1'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 10'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 100'); }); it('should enable onSort for data reset and expect dataset to be reset to 50 items after sorting by Title', () => { @@ -79,9 +79,9 @@ describe('Example - Infinite Scroll', () => { cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left') .scrollTo('top'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 9'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 8'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 7'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 6'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 9'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 8'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 7'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 6'); }); }); diff --git a/cypress/e2e/example-plugin-custom-tooltip.cy.ts b/cypress/e2e/example-plugin-custom-tooltip.cy.ts index a184f496f..d43a4f9bf 100644 --- a/cypress/e2e/example-plugin-custom-tooltip.cy.ts +++ b/cypress/e2e/example-plugin-custom-tooltip.cy.ts @@ -20,7 +20,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over 1st row checkbox column and NOT expect any tooltip to show since it is disabled on that column', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).as('checkbox0-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).as('checkbox0-cell'); cy.get('@checkbox0-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').should('not.exist'); @@ -28,7 +28,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over Task 1 cell and expect async tooltip to show', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).as('task1-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).as('task1-cell'); cy.get('@task1-cell').should('contain', 'Task 1'); cy.get('@task1-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -47,7 +47,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over Task 5 cell and expect async tooltip to show', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).as('task5-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(1)`).as('task5-cell'); cy.get('@task5-cell').should('contain', 'Task 5'); cy.get('@task5-cell').trigger('mouseover'); cy.get('.slick-custom-tooltip').contains('loading...'); @@ -66,7 +66,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over 6th row Description and expect full cell content to show in a tooltip because cell has ellipsis and is too long for the cell itself', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(2)`).as('desc5-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(2)`).as('desc5-cell'); cy.get('@desc5-cell').should('contain', 'This is a sample task description.'); cy.get('@desc5-cell').trigger('mouseover'); @@ -78,7 +78,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over 6th row Description 2 and expect regular tooltip title + concatenated full cell content when using "useRegularTooltipFromFormatterOnly: true"', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).as('desc2-5-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(3)`).as('desc2-5-cell'); cy.get('@desc2-5-cell').should('contain', 'This is a sample task description.'); cy.get('@desc2-5-cell').trigger('mouseover'); @@ -89,7 +89,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over 6th row Duration and expect a custom tooltip shown with 4 label/value pairs displayed', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(4)`).as('duration5-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(4)`).as('duration5-cell'); cy.get('@duration5-cell').should('contain', '5 days'); cy.get('@duration5-cell').trigger('mouseover'); @@ -112,7 +112,7 @@ describe('Example - Custom Tooltip', () => { }); it('should mouse over % Complete cell of Task 5 and expect regular tooltip to show with content "x %" where x is a number', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(5)`).as('percentage-cell'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(5)`).as('percentage-cell'); cy.get('@percentage-cell').find('.percent-complete-bar').should('exist'); cy.get('@percentage-cell').trigger('mouseover'); diff --git a/cypress/e2e/example-row-detail-selection-and-move.cy.ts b/cypress/e2e/example-row-detail-selection-and-move.cy.ts index a6ab56ad8..3d931a5dd 100644 --- a/cypress/e2e/example-row-detail-selection-and-move.cy.ts +++ b/cypress/e2e/example-row-detail-selection-and-move.cy.ts @@ -19,7 +19,7 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { .click() .wait(250); - cy.get('.slick-cell + .dynamic-cell-detail .innerDetailView_3') + cy.get('.slick-cell + .dynamic-cell-detail .innerDetailView_3') .find('h2') .should('contain', 'Task 3'); @@ -31,18 +31,18 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { }); it('should drag opened Row Detail to another valid position in the grid', () => { - cy.get('[style="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); - cy.get('[style="top: 75px;"] > .slick-cell.cell-reorder').as('moveIconTask3'); - cy.get('[style="top: 100px;"]').as('expandIconTask4'); + cy.get('[style*="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); + cy.get('[style*="top: 75px;"] > .slick-cell.cell-reorder').as('moveIconTask3'); + cy.get('[style*="top: 100px;"]').as('expandIconTask4'); cy.get('@moveIconTask3') .trigger('mousedown', { button: 0, force: true }) .trigger('mousemove', 'bottomRight'); - cy.get('[style="top: 100px;"]') + cy.get('[style*="top: 100px;"]') .trigger('mousemove', 'bottomRight', { force: true }); - cy.get('[style="top: 100px;"]') + cy.get('[style*="top: 100px;"]') .trigger('mouseup', 'bottomRight', { force: true }); cy.get('input[type="checkbox"]:checked') @@ -53,28 +53,28 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { cy.get('.slick-viewport-top.slick-viewport-left') .scrollTo('top'); - cy.get('[style="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); - cy.get('[style="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); - cy.get('[style="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); - cy.get('[style="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); - cy.get('[style="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); + cy.get('[style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); + cy.get('[style*="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); + cy.get('[style*="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); + cy.get('[style*="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); + cy.get('[style*="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); cy.get('input[type="checkbox"]:checked') .should('have.length', 0); }); it('should try moving a row to an invalid target and expect nothing moved (same rows as prior test)', () => { - cy.get('[style="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); - cy.get('[style="top: 100px;"]').as('expandIconTask4'); + cy.get('[style*="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); + cy.get('[style*="top: 100px;"]').as('expandIconTask4'); cy.get('@moveIconTask1') .trigger('mousedown', { button: 0, force: true }) .trigger('mousemove', 'bottomRight'); - cy.get('[style="top: 75px;"]') + cy.get('[style*="top: 75px;"]') .trigger('mousemove', 'topRight', { force: true }); - cy.get('[style="top: 75px;"]') + cy.get('[style*="top: 75px;"]') .trigger('mouseup', 'topRight', { force: true }); cy.get('input[type="checkbox"]:checked') @@ -82,11 +82,11 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { }); it('should select 2 rows (Task 1,3), then move row and expect the 2 rows to still be selected without any other rows', () => { - cy.get('[style="top: 25px;"] > .slick-cell:nth(2)').click(); - cy.get('[style="top: 100px;"] > .slick-cell:nth(2)').click(); + cy.get('[style*="top: 25px;"] > .slick-cell:nth(2)').click(); + cy.get('[style*="top: 100px;"] > .slick-cell:nth(2)').click(); - cy.get('[style="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); - cy.get('[style="top: 150px;"]').as('moveIconTask3'); + cy.get('[style*="top: 25px;"] > .slick-cell.cell-reorder').as('moveIconTask1'); + cy.get('[style*="top: 150px;"]').as('moveIconTask3'); cy.get('@moveIconTask1').should('have.length', 1); @@ -98,25 +98,25 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { .trigger('mousemove', 'bottomRight', { force: true }) .trigger('mouseup', 'bottomRight', { force: true }); - cy.get('[style="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); - cy.get('[style="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); - cy.get('[style="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); - cy.get('[style="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); - cy.get('[style="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 5'); - cy.get('[style="top: 125px;"] > .slick-cell:nth(4)').should('contain', 'Task 6'); - cy.get('[style="top: 150px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); + cy.get('[style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); + cy.get('[style*="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); + cy.get('[style*="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); + cy.get('[style*="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); + cy.get('[style*="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 5'); + cy.get('[style*="top: 125px;"] > .slick-cell:nth(4)').should('contain', 'Task 6'); + cy.get('[style*="top: 150px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); // Task 4 and 3 should be selected cy.get('input[type="checkbox"]:checked').should('have.length', 2); - cy.get('[style="top: 75px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); - cy.get('[style="top: 150px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); + cy.get('[style*="top: 75px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); + cy.get('[style*="top: 150px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); }); it('should click on "Single Row Move OFF", then drag a row and expect both selected rows to be moved together', () => { cy.contains('Single Row Move OFF').click(); - cy.get('[style="top: 175px;"] > .slick-cell.cell-reorder').as('moveIconTask7'); - cy.get('[style="top: 75px;"] > .slick-cell.cell-reorder').as('moveIconTask3'); + cy.get('[style*="top: 175px;"] > .slick-cell.cell-reorder').as('moveIconTask7'); + cy.get('[style*="top: 75px;"] > .slick-cell.cell-reorder').as('moveIconTask3'); cy.get('@moveIconTask3').should('have.length', 1); @@ -124,34 +124,34 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { .trigger('mousedown', { button: 0, force: true }) .trigger('mousemove', 'bottomRight'); - cy.get('[style="top: 200px;"]') + cy.get('[style*="top: 200px;"]') .trigger('mousemove', 'bottomRight', { force: true }) .trigger('mouseup', 'bottomRight', { force: true }); - cy.get('[style="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); - cy.get('[style="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); - cy.get('[style="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); - cy.get('[style="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 5'); - cy.get('[style="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 6'); - cy.get('[style="top: 125px;"] > .slick-cell:nth(4)').should('contain', 'Task 7'); - cy.get('[style="top: 150px;"] > .slick-cell:nth(4)').should('contain', 'Task 8'); - cy.get('[style="top: 175px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); - cy.get('[style="top: 200px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); + cy.get('[style*="top: 0px;"] > .slick-cell:nth(4)').should('contain', 'Task 0'); + cy.get('[style*="top: 25px;"] > .slick-cell:nth(4)').should('contain', 'Task 2'); + cy.get('[style*="top: 50px;"] > .slick-cell:nth(4)').should('contain', 'Task 4'); + cy.get('[style*="top: 75px;"] > .slick-cell:nth(4)').should('contain', 'Task 5'); + cy.get('[style*="top: 100px;"] > .slick-cell:nth(4)').should('contain', 'Task 6'); + cy.get('[style*="top: 125px;"] > .slick-cell:nth(4)').should('contain', 'Task 7'); + cy.get('[style*="top: 150px;"] > .slick-cell:nth(4)').should('contain', 'Task 8'); + cy.get('[style*="top: 175px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); + cy.get('[style*="top: 200px;"] > .slick-cell:nth(4)').should('contain', 'Task 1'); // Task 1 and 3 should be selected cy.get('input[type="checkbox"]:checked').should('have.length', 2); - cy.get('[style="top: 175px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); - cy.get('[style="top: 200px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); + cy.get('[style*="top: 175px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); + cy.get('[style*="top: 200px;"] > .slick-cell:nth(2) input[type="checkbox"]:checked').should('have.length', 1); }); it('should open the Task 3 Row Detail and still expect same detail', () => { - cy.get('[style="top: 175px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); + cy.get('[style*="top: 175px;"] > .slick-cell:nth(4)').should('contain', 'Task 3'); - cy.get('[style="top: 175px;"] > .slick-cell:nth(0)') + cy.get('[style*="top: 175px;"] > .slick-cell:nth(0)') .click() .wait(250); - cy.get('.slick-cell + .dynamic-cell-detail .innerDetailView_3') + cy.get('.slick-cell + .dynamic-cell-detail .innerDetailView_3') .find('h2') .should('contain', 'Task 3'); diff --git a/cypress/e2e/example-spreadsheet-dataview.cy.ts b/cypress/e2e/example-spreadsheet-dataview.cy.ts index b0f3ff3dc..76473352a 100644 --- a/cypress/e2e/example-spreadsheet-dataview.cy.ts +++ b/cypress/e2e/example-spreadsheet-dataview.cy.ts @@ -28,10 +28,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{uparrow}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 9}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=9] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":9,"fromCell":4,"toCell":4,"toRow":9}'); }); @@ -41,10 +41,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{downarrow}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 11}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=11] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":11,"fromCell":4,"toCell":4,"toRow":11}'); }); @@ -54,10 +54,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{leftarrow}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l3.r3.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l3.r3.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":3,"toCell":3,"toRow":10}'); }); @@ -67,10 +67,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{rightarrow}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l5.r5.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l5.r5.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":5,"toCell":5,"toRow":10}'); }); @@ -80,10 +80,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{end}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l100.r100.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l100.r100.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":100,"toCell":100,"toRow":10}'); }); @@ -93,10 +93,10 @@ describe('Example - Spreadsheet with DataView and Cell Selection', { retries: 0 .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{home}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l0.r0.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l0.r0.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":0,"toCell":0,"toRow":10}'); }); }); diff --git a/cypress/e2e/example-spreadsheet.cy.ts b/cypress/e2e/example-spreadsheet.cy.ts index e95d321e0..8300503dc 100644 --- a/cypress/e2e/example-spreadsheet.cy.ts +++ b/cypress/e2e/example-spreadsheet.cy.ts @@ -115,10 +115,10 @@ describe('Example - Spreadsheet and Cell Selection', { retries: 0 }, () => { .click(); cy.get('#selectionRange').should('have.text', '{"fromRow":10,"fromCell":4,"toCell":4,"toRow":10}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 10}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=10] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('@cell_D10').type('{uparrow}'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 9}px;"] > .slick-cell.l4.r4.selected`).should('have.length', 1); + cy.get('[data-row=9] .slick-cell.l4.r4.selected').should('have.length', 1); cy.get('#selectionRange').should('have.text', '{"fromRow":9,"fromCell":4,"toCell":4,"toRow":9}'); }); diff --git a/cypress/e2e/example-trading-esm.cy.ts b/cypress/e2e/example-trading-esm.cy.ts index 7faf53f5e..2d8c8de33 100644 --- a/cypress/e2e/example-trading-esm.cy.ts +++ b/cypress/e2e/example-trading-esm.cy.ts @@ -17,12 +17,12 @@ describe('Example - Real-Time Trading Platform', () => { it('should check first 5 rows and expect certain data', () => { for (let i = 0; i < 5; i++) { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/AUD|CAD|USD$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/AUD|CAD|USD$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); } }); @@ -49,29 +49,29 @@ describe('Example - Real-Time Trading Platform', () => { cy.get('[data-test="group-currency-btn"]').click(); cy.get('[data-test="collapse-all-btn"]').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: AUD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: AUD'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: CAD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: CAD'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: USD'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-toggle.collapsed`).should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0) .slick-group-title`).should('contain', 'Currency: USD'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 5}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9,.]*/); }); it('should clear Grouping and expect regular trading row from non specific currency', () => { cy.get('[data-test="clear-grouping-btn"]').click(); for (let i = 0; i < 5; i++) { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/AUD|CAD|USD$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(0)`).contains(/AUD|CAD|USD$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(4)`).contains(/Buy|Sell$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(5)`).contains(/\$\(?[0-9.]*\)?/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(6)`).contains(/\$[0-9.]*/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(7)`).contains(/\d$/); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * i}px;"] > .slick-cell:nth(8)`).contains(/\$[0-9.]*/); } }); }); diff --git a/cypress/e2e/example-web-component-pubsub-esm.cy.ts b/cypress/e2e/example-web-component-pubsub-esm.cy.ts index 70508d50e..9d6f180b2 100644 --- a/cypress/e2e/example-web-component-pubsub-esm.cy.ts +++ b/cypress/e2e/example-web-component-pubsub-esm.cy.ts @@ -136,5 +136,15 @@ describe('Example - Web Component with PubSub Service instead of SlickEvent (ESM expect(win.console.log).to.have.callCount(2); expect(win.console.log).to.be.calledWith('Grid Menu onMenuClose:: slick-gridmenu'); }); + + it('should scroll to the bottom of the grid and expect last row to contain Task 49999', () => { + cy.get('#myGrid') + .find('.slick-viewport-top.slick-viewport-left') + .scrollTo('bottom') + .wait(10); + + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(0)`).should('have.text', '49999'); + cy.get(`#myGrid [data-row="49999"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); + }); }); }); diff --git a/cypress/e2e/example16-row-detail.cy.ts b/cypress/e2e/example16-row-detail.cy.ts index 9d418c9bc..d9ef91115 100644 --- a/cypress/e2e/example16-row-detail.cy.ts +++ b/cypress/e2e/example16-row-detail.cy.ts @@ -32,7 +32,7 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { }); it('should open the 2nd Row Detail of the 4th row and expect to find some details', () => { - cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 10}px;"] .slick-cell:nth(1)`) + cy.get(`.slick-row[style*="top: ${GRID_ROW_HEIGHT * 10}px;"] .slick-cell:nth(1)`) .click() .wait(50); @@ -54,7 +54,7 @@ describe('Example - Row Detail/Row Move/Checkbox Selector Plugins', () => { }); it('should open the Task 3 Row Detail and still expect same detail', () => { - cy.get(`.slick-row[style="top: ${GRID_ROW_HEIGHT * 3}px;"] .slick-cell:nth(1)`) + cy.get(`.slick-row[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] .slick-cell:nth(1)`) .click() .wait(50); diff --git a/cypress/e2e/example3-editing.cy.ts b/cypress/e2e/example3-editing.cy.ts index 2e89b9296..70ea935a0 100644 --- a/cypress/e2e/example3-editing.cy.ts +++ b/cypress/e2e/example3-editing.cy.ts @@ -16,11 +16,11 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" by double-clicking on first row and expect no more editable cell', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 0); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).dblclick(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).dblclick(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -39,8 +39,8 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" by clicking once on second row and expect next row to become editable after clicking "Save" button', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -50,7 +50,7 @@ describe('Example3 Editing', () => { .contains('Save') .click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'Second Row!'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px"] > .slick-cell:nth(1)`).should('contain', 'Second Row!'); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -64,8 +64,8 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" by clicking once on second row and expect next row and not expect next line to become editable', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -75,7 +75,7 @@ describe('Example3 Editing', () => { .contains('Save') .click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Third Row Text'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 2}px;"] > .slick-cell:nth(1)`).should('contain', 'Third Row Text'); cy.get('.slick-large-editor-text').should('have.length', 0); }); @@ -85,8 +85,8 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" and expect once again that the next line will become editable', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -96,7 +96,7 @@ describe('Example3 Editing', () => { .contains('Save') .click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Fourth Row'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Fourth Row'); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') diff --git a/cypress/e2e/example3b-editing-with-undo.cy.ts b/cypress/e2e/example3b-editing-with-undo.cy.ts index 2a723e69c..623697363 100644 --- a/cypress/e2e/example3b-editing-with-undo.cy.ts +++ b/cypress/e2e/example3b-editing-with-undo.cy.ts @@ -16,11 +16,11 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" by double-clicking on first row and expect no more editable cell', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 0); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).dblclick(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).dblclick(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -38,8 +38,8 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" by clicking once on second row and expect next row to become editable after clicking "Save" button', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 1}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -76,8 +76,8 @@ describe('Example3 Editing', () => { }); it('should be able to edit "Description" and expect once again that the next line will become editable', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).click(); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -87,7 +87,7 @@ describe('Example3 Editing', () => { .contains('Save') .click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Fourth Row'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'Fourth Row'); cy.get('.slick-large-editor-text').should('have.length', 1); cy.get('.slick-large-editor-text textarea') @@ -108,12 +108,12 @@ describe('Example3 Editing', () => { }); it('should expect Task 0,3,4 to have descriptions other than original text', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 4'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); }); it('should click undo edits twice and expect Task 3-4 to be undoned but Task 0 to still be changed', () => { @@ -133,12 +133,12 @@ describe('Example3 Editing', () => { }); it('should expect Task 3-4 to have descriptions other than original text', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 4'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Something else'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 3'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(0)`).should('contain', 'Task 4'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('not.contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Something else'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 3}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 4}px;"] > .slick-cell:nth(1)`).should('contain', 'This is a sample'); }); }); diff --git a/cypress/e2e/example4-model-esm.cy.ts b/cypress/e2e/example4-model-esm.cy.ts index 0217da03f..a228f7863 100644 --- a/cypress/e2e/example4-model-esm.cy.ts +++ b/cypress/e2e/example4-model-esm.cy.ts @@ -23,12 +23,12 @@ describe('Example 4 - Model (ESM)', () => { }); it('should expect first row to include "Task 0" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); }); it('should display the text "Showing all 50000 rows" without Pagination', () => { @@ -128,12 +128,12 @@ describe('Example 4 - Model (ESM)', () => { }); it('should expect first row to include "Task 49950" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); }); it('should remove pagination and scroll back to top', () => { @@ -149,17 +149,25 @@ describe('Example 4 - Model (ESM)', () => { }); it('should expect "Task 19" row to be editable', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 19}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 19').click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 19}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 19').click(); cy.get('.slick-large-editor-text textarea').type('Task 2222'); cy.get('.slick-large-editor-text #save').click(); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 19}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2222'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 19}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 2222'); }); it('should expect "Task 18" row to NOT be editable when "onBeforeEditCell" returns false', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 18}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 18').click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 18}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 18').click(); cy.get('.slick-large-editor-text textarea').should('not.exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 18}px;"] > .slick-cell:nth(2)`).should('contain', '5 days').click(); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 18}px;"] > .slick-cell:nth(2)`).should('contain', '5 days').click(); cy.get('.editor-text').should('not.exist'); }); + + it('should navigate to bottom/top of the grid with command execution', () => { + cy.get('[data-test="navigate-bottom"]').click(); + cy.get('[data-row=49999] > .slick-cell.l1.r1').should('contain', 'Task 49999'); + + cy.get('[data-test="navigate-top"]').click(); + cy.get('[data-row=1] > .slick-cell.l1.r1').should('contain', 'Task 1'); + }); }); diff --git a/cypress/e2e/example4-model-html-formatters.cy.ts b/cypress/e2e/example4-model-html-formatters.cy.ts index ca1ea2d87..0a527d948 100644 --- a/cypress/e2e/example4-model-html-formatters.cy.ts +++ b/cypress/e2e/example4-model-html-formatters.cy.ts @@ -23,12 +23,12 @@ describe('Example 4 - HTML Formatters', () => { }); it('should expect first row to include "Task 0" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); }); it('should display the text "Showing all 50000 rows" without Pagination', () => { @@ -128,11 +128,11 @@ describe('Example 4 - HTML Formatters', () => { }); it('should expect first row to include "Task 49950" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); }); }); diff --git a/cypress/e2e/example4-model.cy.ts b/cypress/e2e/example4-model.cy.ts index d6195f024..40f7d757f 100644 --- a/cypress/e2e/example4-model.cy.ts +++ b/cypress/e2e/example4-model.cy.ts @@ -23,12 +23,12 @@ describe('Example 4 - Model', () => { }); it('should expect first row to include "Task 0" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); }); it('should display the text "Showing all 50000 rows" without Pagination', () => { @@ -49,6 +49,14 @@ describe('Example 4 - Model', () => { }); }); + it('should navigate to bottom/top of the grid with command execution', () => { + cy.get('[data-test="navigate-bottom"]').click(); + cy.get('[data-row=49999] > .slick-cell.l1.r1').should('contain', 'Task 49999'); + + cy.get('[data-test="navigate-top"]').click(); + cy.get('[data-row=1] > .slick-cell.l1.r1').should('contain', 'Task 1'); + }); + it('Should display "Showing page 1 of 1000" text after changing Pagination to 50 items per page', () => { cy.get('.sgi-lightbulb') .click(); @@ -128,11 +136,21 @@ describe('Example 4 - Model', () => { }); it('should expect first row to include "Task 49950" and other specific properties', () => { - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); - cy.get(`[style="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(1)`).should('contain', 'Task 49950'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(3) .percent-complete-bar`).should('exist'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(4)`).should('contain', '01/01/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(5)`).should('contain', '01/05/2009'); + cy.get(`[style*="top: ${GRID_ROW_HEIGHT * 0}px;"] > .slick-cell:nth(6)`).find('.sgi.sgi-checkbox-intermediate').should('have.length', 1); + }); + + it('should scroll to the bottom of the grid and expect last row to contain Task 49999', () => { + cy.get('#myGrid') + .find('.slick-viewport-top.slick-viewport-left') + .scrollTo('bottom') + .wait(10); + + cy.get(`#myGrid [data-row="49"] > .slick-cell:nth(0)`).should('have.text', '49999'); + cy.get(`#myGrid [data-row="49"] > .slick-cell:nth(1)`).should('have.text', 'Task 49999'); }); }); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index dc1c86096..5f009efa2 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -49,7 +49,7 @@ Cypress.Commands.add('getCell', (row, col, viewport = 'topLeft', { parentSelecto 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.l${col}.r${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 = 25 } = {}) => { @@ -57,7 +57,7 @@ Cypress.Commands.add('getNthCell', (row, nthCol, viewport = 'topLeft', { parentS 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})`); + return cy.get(`${parentSelector} ${canvasSelectorX}${canvasSelectorY} [style*="top: ${row * rowHeight}px;"] > .slick-cell:nth(${nthCol})`); }); const LOCAL_STORAGE_MEMORY = {}; diff --git a/examples/example-0031-row-span-employees.html b/examples/example-0031-row-span-employees.html new file mode 100644 index 000000000..45f1ba2bc --- /dev/null +++ b/examples/example-0031-row-span-employees.html @@ -0,0 +1,337 @@ + + + + + + + SlickGrid Example: Basic Grid: Showing rowspan + + + + + + + +

Example colspan/rowspan - Employees Timesheets

+
+

+ + Demonstrates: +

+

+ This sample demonstrates the Grid component with the row spanning feature. + In this sample, you will see multiple columns and rows spanning. +

+
+ NOTE: "rowspan" is an opt-in feature, because of its small perf hit (it needs to loop through all row metadatas to map all rowspan), and requires the enableCellRowSpan grid option to be enabled to work properly. +
+
+ NOTE 2: colspan/rowspan are both using DataView item metadata and are both based on row indexes and will not + update neither keep the row in sync with the data. It is really up to you the user to update the metadata logic of how and where the cells should span. + (i.e: Filtering/Sorting/Paging/... will not change/update the spanning in the grid by itself) +
+
+ NOTE 3: column/row freezing (pinning) are not supported, or at least not recommended unless you know exactly what you're doing! + Any freezing column/row that could intersect because of a colspan/rowspan will cause problems. +
+ +

+ View Source: + + View the source for this example on Github + +

+ + + + + + +
+
+ +
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/example-0032-row-span-many-columns.html b/examples/example-0032-row-span-many-columns.html new file mode 100644 index 000000000..6b43c2d57 --- /dev/null +++ b/examples/example-0032-row-span-many-columns.html @@ -0,0 +1,321 @@ + + + + + + SlickGrid Example: colspan & rowspan spanning many columns + + + + + + +

Example colspan/rowspan spanning many columns/cells

+
+
+
+ +
+
+
+ +
+

+ + Demonstrates: +

+

+ This page demonstrates colspan & rowspan using DataView with item metadata. + Note: the extra color styling was only added to emphasize a little more the colspan/rowspan sizes. +

+
+ NOTE: "rowspan" is an opt-in feature, because of its small perf hit (it needs to loop through all row metadatas to map all rowspan), and requires the enableCellRowSpan grid option to be enabled to work properly. +
+
+ NOTE 2: colspan/rowspan are both using DataView item metadata and are both based on row indexes and will not + update neither keep the row in sync with the data. It is really up to you the user to update the metadata logic of how and where the cells should span. + (i.e: Filtering/Sorting/Paging/... will not change/update the spanning in the grid by itself) +
+
+ + + + + + +
+ + +
+ + +

View Source:

+ + + + + + + + + + + + \ No newline at end of file diff --git a/examples/example-frozen-columns-and-column-group.html b/examples/example-frozen-columns-and-column-group.html index 6d79be8f8..10e5ef1a2 100644 --- a/examples/example-frozen-columns-and-column-group.html +++ b/examples/example-frozen-columns-and-column-group.html @@ -36,8 +36,8 @@

  • Frozen columns with extra header row grouping columns into categories
  • - - + +

    View Source:

    diff --git a/examples/example-optimizing-dataview.html b/examples/example-optimizing-dataview.html index 7d77259d2..5278e4bf1 100644 --- a/examples/example-optimizing-dataview.html +++ b/examples/example-optimizing-dataview.html @@ -98,7 +98,7 @@

    View Source:

    var options = { editable: false, enableAddRow: false, - enableCellNavigation: true + enableCellNavigation: true, }; var percentCompleteThreshold = 0; diff --git a/examples/example-spreadsheet-dataview.html b/examples/example-spreadsheet-dataview.html index 5a432f31c..249c91003 100644 --- a/examples/example-spreadsheet-dataview.html +++ b/examples/example-spreadsheet-dataview.html @@ -86,7 +86,8 @@

    Range Selection

    enableAddRow: true, enableCellNavigation: true, asyncEditorLoading: false, - autoEdit: false + autoEdit: false, + // enableCellRowSpan: true }; var columns = [ diff --git a/examples/example4-model-esm.html b/examples/example4-model-esm.html index 9a72205cd..abcf49169 100644 --- a/examples/example4-model-esm.html +++ b/examples/example4-model-esm.html @@ -92,7 +92,15 @@



    - +
    + + + +

    @@ -379,6 +387,13 @@

    View Source:

    dataView.refresh(); } + document.querySelector("#btnNavigateBottom").addEventListener('click', () => { + grid.navigateBottom(); + }); + document.querySelector("#btnNavigateTop").addEventListener('click', () => { + grid.navigateTop(); + }); + document.querySelector("#btnSelectRows").addEventListener('click', () => { if (!SlickGlobalEditorLock.commitCurrentEdit()) { return; diff --git a/examples/example4-model.html b/examples/example4-model.html index 020d70c96..bb3e5ae03 100644 --- a/examples/example4-model.html +++ b/examples/example4-model.html @@ -60,7 +60,15 @@



    - +
    + + + +

    diff --git a/examples/index.html b/examples/index.html index 6aa3e398e..a492b2d1f 100644 --- a/examples/index.html +++ b/examples/index.html @@ -82,7 +82,9 @@

    Layout

  • Spreadsheet: features of the previous example but using a DataView
  • No vertical scrolling
  • Filling the whole window
  • -
  • Colspan
  • +
  • Column Spanning (colspan)
  • +
  • Columns/Rows Spanning - Employees Timesheets (colspan/rowspan)
  • +
  • Columns/Rows Spanning - large dataset (colspan/rowspan)
  • Column grouping
  • Grid auto-resize on browser resize
  • Grid column size to content
  • diff --git a/src/models/column.interface.ts b/src/models/column.interface.ts index 893412e45..3b118a9cf 100644 --- a/src/models/column.interface.ts +++ b/src/models/column.interface.ts @@ -167,6 +167,9 @@ export interface Column { /** Is the column resizable, can we make it wider/thinner? A resize cursor will show on the right side of the column when enabled. */ resizable?: boolean; + /** Row span in cell count or use `*` to span across the entire row */ + rowspan?: number; + /** Is the column selectable? Goes with grid option "enableCellNavigation: true". */ selectable?: boolean; diff --git a/src/models/elementPosition.interface.ts b/src/models/elementPosition.interface.ts deleted file mode 100644 index 6a54510a4..000000000 --- a/src/models/elementPosition.interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ElementPosition { - top: number; - left: number; - bottom: number; - right: number; - height: number; - width: number; - visible: boolean; -} diff --git a/src/models/gridOption.interface.ts b/src/models/gridOption.interface.ts index 0b301ec57..bcf113fb2 100644 --- a/src/models/gridOption.interface.ts +++ b/src/models/gridOption.interface.ts @@ -22,9 +22,9 @@ export interface CellViewportRange { } export interface CustomDataView { - getLength: () => number; getItem: (index: number) => T; - getItemMetadata(index: number): ItemMetadata | null; + getItemMetadata(row: number, cell?: boolean | number): ItemMetadata | null; + getLength: () => number; } export interface CssStyleHash { @@ -166,6 +166,13 @@ export interface GridOption { /** Defaults to false, editor cell navigation left/right keys */ editorCellNavOnLRKeys?: boolean; + /** + * Do we want to enable cell rowspan? + * Note: this is an opt-in option because of the multiple row/column/cells looping that it has to do + * (which is at least an O^n3 but only for visible range) + */ + enableCellRowSpan?: boolean; + /** * Defaults to true, this option can be a boolean or a Column Reorder function. * When provided as a boolean, it will permits the user to move an entire column from a position to another. @@ -249,12 +256,21 @@ export interface GridOption { /** Should we log the sanitized html? */ logSanitizedHtml?: boolean; + /** + * Defaults to 5000, max number of rows that we'll consider doing a partial rowspan remapping. + * Anything else will be considered to require a full rowspan remap when necessary + */ + maxPartialRowSpanRemap?: number; + /** Max supported CSS height */ maxSupportedCssHeight?: number; /** What is the minimum row buffer to use? */ minRowBuffer?: number; + /** What is the maximum row buffer to use? */ + maxRowBuffer?: number; + /** Use a mixin function when applying defaults to passed in option and columns objects, rather than creating a new object, so as not to break references */ mixinDefaults?: boolean; diff --git a/src/models/groupItemMetadataProviderOption.interface.ts b/src/models/groupItemMetadataProviderOption.interface.ts index 00235a7cf..6beae5ce3 100644 --- a/src/models/groupItemMetadataProviderOption.interface.ts +++ b/src/models/groupItemMetadataProviderOption.interface.ts @@ -1,7 +1,7 @@ import type { Formatter } from './index.js'; export interface ItemMetadataProvider { - getRowMetadata(item: any, row: number): any; + getRowMetadata(item: any, row?: number): any; } export interface GroupItemMetadataProviderOption { diff --git a/src/models/htmlElementPosition.interface.ts b/src/models/htmlElementPosition.interface.ts deleted file mode 100644 index 31837a47f..000000000 --- a/src/models/htmlElementPosition.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface HtmlElementPosition { - top?: number; - bottom?: number; - left?: number; - right?: number; -} diff --git a/src/models/index.ts b/src/models/index.ts index a85065622..150fd99d8 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -24,7 +24,6 @@ export type * from './editor.interface.js'; export type * from './editorArguments.interface.js'; export type * from './editorValidationResult.interface.js'; export type * from './editorValidator.interface.js'; -export type * from './elementPosition.interface.js'; export type * from './excelCopyBufferOption.interface.js'; export type * from './externalCopyClipCommand.interface.js'; export * from './fieldType.enum.js'; @@ -48,7 +47,6 @@ export type * from './headerButtonOnCommandArgs.interface.js'; export type * from './headerButtonsOrMenu.interface.js'; export type * from './headerMenuItems.interface.js'; export type * from './headerMenuOption.interface.js'; -export type * from './htmlElementPosition.interface.js'; export type * from './infer.type.js'; export type * from './interactions.interface.js'; export type * from './itemMetadata.interface.js'; @@ -62,6 +60,7 @@ export type * from './menuOptionItemCallbackArgs.interface.js'; export type * from './mouseOffsetViewport.interface.js'; export type * from './multiColumnSort.interface.js'; export type * from './pagingInfo.interface.js'; +export type * from './position.interface.js'; export type * from './positionMethod.type.js'; export type * from './resizerOption.interface.js'; export type * from './rowDetailViewOption.interface.js'; diff --git a/src/models/itemMetadata.interface.ts b/src/models/itemMetadata.interface.ts index 5b2f8fcb5..b2577b528 100644 --- a/src/models/itemMetadata.interface.ts +++ b/src/models/itemMetadata.interface.ts @@ -1,5 +1,7 @@ import type { Column, Editor, Formatter, GroupTotalsFormatter } from './index.js'; +export type ColumnMetadata = Pick; + /** * Provides a powerful way of specifying additional information about a data item that let the grid customize the appearance * and handling of a particular data item. The method should return null if the item requires no special handling, @@ -26,6 +28,6 @@ export interface ItemMetadata { /** column-level metadata */ columns?: { // properties describing metadata related to individual columns - [colIdOrIdx in string | number]: Pick; + [colIdOrIdx in string | number]: ColumnMetadata; } } \ No newline at end of file diff --git a/src/models/position.interface.ts b/src/models/position.interface.ts new file mode 100644 index 000000000..aaba4b297 --- /dev/null +++ b/src/models/position.interface.ts @@ -0,0 +1,23 @@ +export interface CellPosition { + row: number; + cell: number; + posX: number; + posY: number; +} + +export interface ElementPosition { + top: number; + left: number; + bottom: number; + right: number; + height: number; + width: number; + visible: boolean; +} + +export interface HtmlElementPosition { + top?: number; + bottom?: number; + left?: number; + right?: number; +} \ No newline at end of file diff --git a/src/plugins/slick.cellselectionmodel.ts b/src/plugins/slick.cellselectionmodel.ts index 2a99a5fec..4f8e53d07 100644 --- a/src/plugins/slick.cellselectionmodel.ts +++ b/src/plugins/slick.cellselectionmodel.ts @@ -288,4 +288,4 @@ if (IIFE_ONLY && window.Slick) { CellSelectionModel: SlickCellSelectionModel } }); -} +} \ No newline at end of file diff --git a/src/slick.grid.ts b/src/slick.grid.ts index 3de7566a8..908d9ffdd 100644 --- a/src/slick.grid.ts +++ b/src/slick.grid.ts @@ -3,8 +3,10 @@ import type SortableInstance from 'sortablejs'; import type { AutoSize, + CellPosition, CellViewportRange, Column, + ColumnMetadata, ColumnSort, CssStyleHash, CSSStyleDeclarationWritable, @@ -261,6 +263,7 @@ export class SlickGrid = Column, O e rowHighlightDuration: 400, selectedCellCssClass: 'selected', multiSelect: true, + enableCellRowSpan: false, enableTextSelectionOnCells: false, dataItemColumnValueExtractor: null, frozenBottom: false, @@ -296,6 +299,7 @@ export class SlickGrid = Column, O e suppressCssChangesOnHiddenInit: false, ffMaxSupportedCssHeight: 6000000, maxSupportedCssHeight: 1000000000, + maxPartialRowSpanRemap: 5000, sanitizer: undefined, // sanitize function, built in basic sanitizer is: Slick.RegexSanitizer(dirtyHtml) logSanitizedHtml: false, // log to console when sanitised - recommend true for testing of dev and production mixinDefaults: true, @@ -415,14 +419,18 @@ export class SlickGrid = Column, O e protected _activeCanvasNode!: HTMLDivElement; protected _activeViewportNode!: HTMLDivElement; protected activePosX!: number; + protected activePosY!: number; protected activeRow!: number; protected activeCell!: number; protected activeCellNode: HTMLDivElement | null = null; protected currentEditor: Editor | null = null; protected serializedEditorValue: any; protected editController?: EditController; - - protected rowsCache: Array = {} as any; + protected _prevDataLength = 0; + protected _prevInvalidatedRowsCount = 0; + protected _rowSpanIsCached = false; + protected _colsWithRowSpanCache: { [colIdx: number]: Set } = {}; + protected rowsCache: Record = {}; protected renderedRows = 0; protected numVisibleRows = 0; protected prevScrollTop = 0; @@ -451,7 +459,6 @@ export class SlickGrid = Column, O e // async call handles protected h_editorLoader?: number; - protected h_render = null; protected h_postrender?: number; protected h_postrenderCleanup?: number; protected postProcessedRows: any = {}; @@ -3759,6 +3766,15 @@ export class SlickGrid = Column, O e } } + /** + * Returns item metadata by a row index when it exists + * @param {Number} row + * @returns {ItemMetadata | null} + */ + getItemMetadaWhenExists(row: number): ItemMetadata | null { + return 'getItemMetadata' in this.data ? (this.data as CustomDataView).getItemMetadata(row) : null; + } + /** Get Top Panel DOM element */ getTopPanel() { return this._topPanels[0]; @@ -3858,10 +3874,18 @@ export class SlickGrid = Column, O e ////////////////////////////////////////////////////////////////////////////////////////////// // Rendering / Scrolling + protected getRowHeight() { + return this._options.rowHeight!; + } + protected getRowTop(row: number) { return Math.round(this._options.rowHeight! * row - this.offset); } + protected getRowBottom(row: number) { + return this.getRowTop(row) + this._options.rowHeight!; + } + protected getRowFromPosition(y: number) { return Math.floor((y + this.offset) / this._options.rowHeight!); } @@ -3929,7 +3953,7 @@ export class SlickGrid = Column, O e protected getEditor(row: number, cell: number): Editor | EditorConstructor | null | undefined { const column = this.columns[cell]; - const rowMetadata = (this.data as CustomDataView)?.getItemMetadata?.(row); + const rowMetadata = this.getItemMetadaWhenExists(row); const columnMetadata = rowMetadata?.columns; if (columnMetadata?.[column.id]?.editor !== undefined) { @@ -3959,16 +3983,19 @@ export class SlickGrid = Column, O e (row % 2 === 1 ? ' odd' : ' even'); if (!d) { - rowCss += ' ' + this._options.addNewRowCssClass; + rowCss += ` ${this._options.addNewRowCssClass}`; } - const metadata = (this.data as CustomDataView)?.getItemMetadata?.(row); - + const metadata = this.getItemMetadaWhenExists(row); if (metadata?.cssClasses) { - rowCss += ' ' + metadata.cssClasses; + rowCss += ` ${metadata.cssClasses}`; } - const rowDiv = Utils.createDomElement('div', { className: `ui-widget-content ${rowCss}`, role: 'row' }); + const rowDiv = Utils.createDomElement('div', { + className: `ui-widget-content ${rowCss}`, + role: 'row', + dataset: { row: `${row}` }, + }); const frozenRowOffset = this.getFrozenRowOffset(row); const topOffset = this.getRowTop(row) - frozenRowOffset; if (this._options.rowTopOffsetRenderType === 'transform') { @@ -3986,64 +4013,90 @@ export class SlickGrid = Column, O e divArrayR.push(rowDivR); } + const columnCount = this.columns.length; + let columnData: ColumnMetadata | null; let colspan: number | string; + let rowspan: number; let m: C; - for (let i = 0, ii = this.columns.length; i < ii; i++) { + let isRenderCell = true; + + for (let i = 0, ii = columnCount; i < ii; i++) { + isRenderCell = true; m = this.columns[i]; if (!m || m.hidden) { continue; } colspan = 1; + rowspan = 1; + columnData = null; if (metadata?.columns) { - const columnData = metadata.columns[m.id] || metadata.columns[i]; + columnData = metadata.columns[m.id] || metadata.columns[i]; colspan = columnData?.colspan || 1; + rowspan = columnData?.rowspan || 1; if (colspan === '*') { colspan = ii - i; } + if (rowspan > dataLength - row) { + rowspan = dataLength - row; + } + } + + if (!this._options.enableCellRowSpan && rowspan > 1) { + console.warn('[SlickGrid] Cell "rowspan" is an opt-in grid option because of its small perf hit, you must enable it via the "enableCellRowSpan" grid option.'); + } + + const ncolspan = colspan as number; // at this point colspan is for sure a number + + // don't render child cell of a rowspan cell + const prs = this.getParentRowSpanByCell(row, i); + if (prs) { + continue; } // Do not render cells outside of the viewport. - if (this.columnPosRight[Math.min(ii - 1, i + (colspan as number) - 1)] > range.leftPx) { + if (this.columnPosRight[Math.min(ii - 1, i + ncolspan - 1)] > range.leftPx) { if (!m.alwaysRenderColumn && this.columnPosLeft[i] > range.rightPx) { - // All columns to the right are outside the range. - break; + isRenderCell = false; // render as false but keep looping to correctly save cellspan pointers } - if (this.hasFrozenColumns() && (i > this._options.frozenColumn!)) { - this.appendCellHtml(rowDivR!, row, i, (colspan as number), d); - } else { - this.appendCellHtml(rowDiv, row, i, (colspan as number), d); + // All columns to the right are outside the range, so no need to render them + if (isRenderCell) { + const targetedRowDiv = (this.hasFrozenColumns() && (i > this._options.frozenColumn!) ? rowDivR! : rowDiv); + this.appendCellHtml(targetedRowDiv, row, i, ncolspan, rowspan, columnData, d); } } else if (m.alwaysRenderColumn || (this.hasFrozenColumns() && i <= this._options.frozenColumn!)) { - this.appendCellHtml(rowDiv, row, i, (colspan as number), d); + this.appendCellHtml(rowDiv, row, i, ncolspan, rowspan, columnData, d); } - if ((colspan as number) > 1) { - i += ((colspan as number) - 1); + if (ncolspan > 1) { + i += (ncolspan - 1); } } } - protected appendCellHtml(divRow: HTMLElement, row: number, cell: number, colspan: number, item: TData) { + protected appendCellHtml(divRow: HTMLElement, row: number, cell: number, colspan: number, rowspan: number, columnMetadata: ColumnMetadata | null, item: TData) { // divRow: the html element to append items too // row, cell: row and column index // colspan: HTML colspan // item: grid data for row const m = this.columns[cell]; - let cellCss = 'slick-cell l' + cell + ' r' + Math.min(this.columns.length - 1, cell + colspan - 1) + (m.cssClass ? ' ' + m.cssClass : ''); + let cellCss = `slick-cell l${cell} r${Math.min(this.columns.length - 1, cell + colspan - 1)}` + + (m.cssClass ? ` ${m.cssClass}` : '') + + (rowspan > 1 ? ' rowspan' : '') + + (columnMetadata?.cssClass ? ` ${columnMetadata.cssClass}` : ''); if (this.hasFrozenColumns() && cell <= this._options.frozenColumn!) { - cellCss += (' frozen'); + cellCss += ' frozen'; } if (row === this.activeRow && cell === this.activeCell && this._options.showCellSelection) { - cellCss += (' active'); + cellCss += ' active'; } // TODO: merge them together in the setter Object.keys(this.cellCssClasses).forEach(key => { if (this.cellCssClasses[key][row]?.[m.id]) { - cellCss += (' ' + this.cellCssClasses[key][row][m.id]); + cellCss += ` ${this.cellCssClasses[key][row][m.id]}`; } }); @@ -4077,6 +4130,12 @@ export class SlickGrid = Column, O e cellDiv.setAttribute('title', toolTipText); } + // update cell rowspan height when spanning more than 1 row + const cellHeight = this.getCellHeight(row, rowspan); + if (rowspan > 1 && cellHeight !== (this._options.rowHeight! - this.cellHeightDiff)) { + cellDiv.style.height = `${cellHeight || 0}px`; + } + if (m.hasOwnProperty('cellAttrs') && m.cellAttrs instanceof Object) { Object.keys(m.cellAttrs).forEach(key => { if (m.cellAttrs.hasOwnProperty(key)) { @@ -4102,6 +4161,18 @@ export class SlickGrid = Column, O e } protected cleanupRows(rangeToKeep: { bottom: number; top: number; }) { + // when using rowspan, we might have mandatory rows that cannot be cleaned up + // that is basically the starting row that holds the rowspan, that row cannot be cleaned up because it would break the UI + const mandatoryRows = new Set(); + if (this._options.enableCellRowSpan) { + for (let i = rangeToKeep.top, ln = rangeToKeep.bottom; i <= ln; i++) { + const parentRowSpan = this.getRowSpanIntersect(i); + if (parentRowSpan !== null) { + mandatoryRows.add(parentRowSpan); // add to Set which will take care of duplicate rows + } + } + } + Object.keys(this.rowsCache).forEach(rowId => { if (this.rowsCache) { let i = +rowId; @@ -4118,6 +4189,7 @@ export class SlickGrid = Column, O e if (((i = parseInt(rowId, 10)) !== this.activeRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom) && (removeFrozenRow) + && !mandatoryRows.has(i) ) { this.removeRowFromCache(i); } @@ -4128,7 +4200,116 @@ export class SlickGrid = Column, O e } } - /** Invalidate all grid rows and re-render the grid rows */ + /** + * from a row number, return any column indexes that intersected with the grid row including the cell + * @param {Number} row - grid row index + */ + getRowSpanColumnIntersects(row: number): number[] { + return this.getRowSpanIntersection(row, 'columns'); + } + + /** + * from a row number, verify if the rowspan is intersecting and return it when found, + * otherwise return `null` when nothing is found or when the rowspan feature is disabled. + * @param {Number} row - grid row index + */ + getRowSpanIntersect(row: number): number | null { + return this.getRowSpanIntersection(row); + } + + protected getRowSpanIntersection(row: number, outputType?: 'columns' | 'start'): R { + const columnIntersects: number[] = []; + let rowStartIntersect = null; + + for (let col = 0, cln = this.columns.length; col < cln; col++) { + const rmeta = this._colsWithRowSpanCache[col]; + if (rmeta) { + for (const range of Array.from(rmeta)) { + const [start, end] = range.split(':').map(Number); + if (row >= start && row <= end) { + if (outputType === 'columns') { + columnIntersects.push(col); + } else { + rowStartIntersect = start; + break; + } + } + } + } + } + return (outputType === 'columns' ? columnIntersects : rowStartIntersect) as R; + } + + /** + * Returns the parent rowspan details when child cell are spanned from a rowspan or `null` when it's not spanned. + * By default it will exclude the parent cell that holds the rowspan, and return `null`, that initiated the rowspan unless the 3rd argument is disabled. + * The exclusion is helpful to find out when we're dealing with a child cell of a rowspan + * @param {Number} row - grid row index + * @param {Number} cell - grid cell/column index + * @param {Boolean} [excludeParentRow] - should we exclude the parent who initiated the rowspan in the search (defaults to true)? + */ + getParentRowSpanByCell(row: number, cell: number, excludeParentRow = true): { start: number; end: number; range: string; } | null { + let spanDetail = null; + const rowspanRange = this._colsWithRowSpanCache[cell] || new Set(); + + for (const range of Array.from(rowspanRange)) { + const [start, end] = range.split(':').map(Number); + const startCondition = excludeParentRow ? row > start : row >= start; + if (startCondition && row <= end) { + spanDetail = { start, end, range }; + break; + } + } + + return spanDetail; + } + + /** + * Remap all the rowspan metadata by looping through all dataset rows and keep a cache of rowspan by column indexes + * For example: + * 1- if 2nd row of the 1st column has a metadata.rowspan of 3 then the cache will be: `{ 0: '1:4' }` + * 2- if 2nd row if the 1st column has a metadata.rowspan of 3 AND a colspan of 2 then the cache will be: `{ 0: '1:4', 1: '1:4' }` + */ + protected remapAllColumnsRowSpan() { + const ln = this.getDataLength(); + if (ln > 0) { + this._colsWithRowSpanCache = {}; + for (let row = 0; row < ln; row++) { + this.remapRowSpanMetadataByRow(row); + } + + this._rowSpanIsCached = true; + } + } + + protected remapRowSpanMetadataByRow(row: number) { + const colMeta = this.getItemMetadaWhenExists(row); + if (colMeta?.columns) { + Object.keys(colMeta.columns).forEach(col => { + const colIdx = +col; + const columnMeta = colMeta.columns![colIdx]; + const colspan = +(columnMeta?.colspan || 1); + const rowspan = +(columnMeta?.rowspan || 1); + this.remapRowSpanMetadata(row, colIdx, colspan, rowspan); + }); + } + } + + protected remapRowSpanMetadata(row: number, cell: number, colspan: number, rowspan: number) { + if (rowspan > 1) { + const rspan = `${row}:${row + rowspan - 1}`; + this._colsWithRowSpanCache[cell] ??= new Set(); + this._colsWithRowSpanCache[cell].add(rspan); + if (colspan > 1) { + for (let i = 1; i < colspan; i++) { + this._colsWithRowSpanCache[cell + i] ??= new Set(); + this._colsWithRowSpanCache[cell + i].add(rspan); + } + } + } + } + + /** Invalidate all grid rows and re-render the visible grid rows */ invalidate() { this.updateRowCount(); this.invalidateAllRows(); @@ -4162,17 +4343,65 @@ export class SlickGrid = Column, O e if (!rows || !rows.length) { return; } + + let row; this.vScrollDir = 0; const rl = rows.length; + + // use Set to avoid duplicates + const invalidatedRows = new Set(); + const requiredRemapRows = new Set(); + + // only do a partial rowspan remapping when the number of rows is limited and the rows aren't the full dataset + // otherwise a full rowspan remap of the cache is much quicker and cheaper to perform + const isRowSpanFullRemap = ( + rows.length > this._options.maxPartialRowSpanRemap! || + rows.length === this.getDataLength() || + this._prevInvalidatedRowsCount + rows.length === this.getDataLength() + ); + for (let i = 0; i < rl; i++) { - if (this.currentEditor && this.activeRow === rows[i]) { + row = rows[i]; + if (this.currentEditor && this.activeRow === row) { this.makeActiveCellNormal(); } - if (this.rowsCache[rows[i]]) { - this.removeRowFromCache(rows[i]); + if (this.rowsCache[row]) { + this.removeRowFromCache(row); + } + + // add any rows that have rowspan intersects if it's not already in the list + if (this._options.enableCellRowSpan && !isRowSpanFullRemap) { + invalidatedRows.add(row); + const parentRowSpan = this.getRowSpanIntersect(row); + if (parentRowSpan !== null) { + invalidatedRows.add(parentRowSpan); + } + } + } + + // when a partial rowspan remapping is necessary + if (this._options.enableCellRowSpan && !isRowSpanFullRemap) { + for (const ir of Array.from(invalidatedRows)) { + const colIdxs = this.getRowSpanColumnIntersects(ir); + for (const cidx of colIdxs) { + const prs = this.getParentRowSpanByCell(ir, cidx); + if (prs && this._colsWithRowSpanCache[cidx]) { + this._colsWithRowSpanCache[cidx].delete(prs.range); + requiredRemapRows.add(prs.range.split(':').map(Number)[0]); + } + } + } + + // now that we know all the rows that need remapping, let's start remapping + for (const row of Array.from(requiredRemapRows)) { + this.remapRowSpanMetadataByRow(row); } } - if (this._options.enableAsyncPostRenderCleanup) { this.startPostProcessingCleanup(); } + + if (this._options.enableAsyncPostRenderCleanup) { + this.startPostProcessingCleanup(); + } + this._prevInvalidatedRowsCount = rows.length; } /** @@ -4180,8 +4409,16 @@ export class SlickGrid = Column, O e * @param {Number} row */ invalidateRow(row: number) { - if (!row && row !== 0) { return; } - this.invalidateRows([row]); + if (row >= 0) { + const rows = [row]; + if (this._options.enableCellRowSpan) { + const intersectedRow = this.getRowSpanIntersect(row); + if (intersectedRow !== null) { + rows.push(intersectedRow); + } + } + this.invalidateRows(rows); + } } protected queuePostProcessedRowForCleanup(cacheEntry: RowCaching, postProcessedRow: any, rowIdx: number) { @@ -4280,6 +4517,12 @@ export class SlickGrid = Column, O e if (this.currentEditor && this.activeRow === row && this.activeCell === cell) { this.currentEditor.loadValue(d); } else { + // if the cell has other coordinates because of row/cell span, update that cell (which will invalidate this cellNode) + // const spans = this.getSpans(row, cell); + // if (spans[0] !== row || spans[1] !== cell) { + // this.updateCell(spans[0], spans[1]); + // return; + // } const formatterResult = d ? this.getFormatter(row, m)(row, cell, this.getDataItemValueForColumn(d, m), m, d, this as unknown as SlickGridModel) : ''; this.applyFormatResultToCellNode(formatterResult, cellNode); this.invalidatePostProcessingResults(row); @@ -4323,6 +4566,21 @@ export class SlickGrid = Column, O e this.invalidatePostProcessingResults(row); } + getCellHeight(row: number, rowspan: number) { + let cellHeight = this._options.rowHeight || 0; + if (rowspan > 1) { + const rowSpanBottomIdx = row + rowspan - 1; + cellHeight = this.getRowBottom(rowSpanBottomIdx) - this.getRowTop(row); + } else { + const rowHeight = this.getRowHeight(); + if (rowHeight !== cellHeight - this.cellHeightDiff) { + cellHeight = rowHeight; + } + } + cellHeight -= this.cellHeightDiff; + return Math.ceil(cellHeight); + } + /** * Get the number of rows displayed in the viewport * Note that the row count is an approximation because it is a calculated value using this formula (viewport / rowHeight = rowCount), @@ -4508,6 +4766,16 @@ export class SlickGrid = Column, O e if (!this.initialized) { return; } const dataLength = this.getDataLength(); + + // remap all rowspan cache when necessary + if (dataLength > 0 && dataLength !== this._prevDataLength) { + this._rowSpanIsCached = false; // will force a full remap + } + if (this._options.enableCellRowSpan && !this._rowSpanIsCached) { + this.remapAllColumnsRowSpan(); + } + + this._prevDataLength = dataLength; const dataLengthIncludingAddNew = this.getDataLengthIncludingAddNew(); let numberOfRows = 0; let oldH = ((this.hasFrozenRows && !this._options.frozenBottom) ? Utils.height(this._canvasBottomL) : Utils.height(this._canvasTopL)) as number; @@ -4696,7 +4964,7 @@ export class SlickGrid = Column, O e } // Ignore alwaysRenderedColumns - if (Array.isArray(this.columns) && this.columns[i] && this.columns[i].alwaysRenderColumn) { + if (Array.isArray(this.columns) && this.columns[i]?.alwaysRenderColumn) { return; } @@ -4737,6 +5005,8 @@ export class SlickGrid = Column, O e let cellsAdded: number; let totalCellsAdded = 0; let colspan: number | string; + let columnData: ColumnMetadata | null; + const columnCount = this.columns.length; for (let row = range.top as number, btm = range.bottom as number; row <= btm; row++) { cacheEntry = this.rowsCache[row]; @@ -4747,17 +5017,20 @@ export class SlickGrid = Column, O e // cellRenderQueue populated in renderRows() needs to be cleared first this.ensureCellNodesInRowsCache(row); - this.cleanUpCells(range, row); + if (!this._options.enableCellRowSpan || this.getRowSpanIntersect(row) === null) { + this.cleanUpCells(range, row); + } + // Render missing cells. cellsAdded = 0; - let metadata = (this.data as CustomDataView)?.getItemMetadata?.(row) ?? {} as ItemMetadata; + let metadata = this.getItemMetadaWhenExists(row); metadata = metadata?.columns as ItemMetadata; const d = this.getDataItem(row); - // TODO: shorten this loop (index? heuristics? binary search?) - for (let i = 0, ii = this.columns.length; i < ii; i++) { + // TODO: shorten this loop (index? heuristics? binary search?) + for (let i = 0, ii = columnCount; i < ii; i++) { if (!this.columns[i] || this.columns[i].hidden) { continue; } // Cells to the right are outside the range. @@ -4772,21 +5045,30 @@ export class SlickGrid = Column, O e } colspan = 1; + columnData = null; if (metadata) { - const columnData = metadata[this.columns[i].id as keyof ItemMetadata] || (metadata as any)[i]; + columnData = metadata[this.columns[i].id as keyof ItemMetadata] || (metadata as any)[i]; colspan = columnData?.colspan ?? 1; if (colspan === '*') { colspan = ii - i; } } - const colspanNb = colspan as number; // at this point colspan is for sure a number - if (this.columnPosRight[Math.min(ii - 1, i + colspanNb - 1)] > range.leftPx) { - this.appendCellHtml(divRow, row, i, colspanNb, d); + const ncolspan = colspan as number; // at this point colspan is for sure a number + + // don't render child cell of a rowspan cell + const prs = this.getParentRowSpanByCell(row, i); + if (prs) { + continue; + } + + if (this.columnPosRight[Math.min(ii - 1, i + ncolspan - 1)] > range.leftPx) { + const rowspan = this.getRowspan(row, i); + this.appendCellHtml(divRow, row, i, ncolspan, rowspan, columnData, d); cellsAdded++; } - i += (colspanNb > 1 ? colspanNb - 1 : 0); + i += (ncolspan > 1 ? ncolspan - 1 : 0); } if (cellsAdded) { @@ -4821,12 +5103,32 @@ export class SlickGrid = Column, O e } } + protected createEmptyCachingRow(): RowCaching { + return { + rowNode: null, + + // ColSpans of rendered cells (by column idx). + // Can also be used for checking whether a cell has been rendered. + cellColSpans: [], + + // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache(). + cellNodesByColumnIdx: [], + + // Column indices of cell nodes that have been rendered, but not yet indexed in + // cellNodesByColumnIdx. These are in the same order as cell nodes added at the + // end of the row. + cellRenderQueue: [], + }; + } + protected renderRows(range: { top: number; bottom: number; leftPx: number; rightPx: number; }) { const divArrayL: HTMLElement[] = []; const divArrayR: HTMLElement[] = []; const rows: number[] = []; let needToReselectCell = false; const dataLength = this.getDataLength(); + const mustRenderRows = new Set(); + const renderingRows = new Set(); for (let i = range.top as number, ii = range.bottom as number; i <= ii; i++) { if (this.rowsCache[i] || (this.hasFrozenRows && this._options.frozenBottom && i === this.getDataLength())) { @@ -4834,73 +5136,84 @@ export class SlickGrid = Column, O e } this.renderedRows++; rows.push(i); + renderingRows.add(i); - // Create an entry right away so that appendRowHtml() can - // start populating it. - this.rowsCache[i] = { - rowNode: null, - - // ColSpans of rendered cells (by column idx). - // Can also be used for checking whether a cell has been rendered. - cellColSpans: [], + // Create an entry right away so that appendRowHtml() can start populating it. + this.rowsCache[i] = this.createEmptyCachingRow(); - // Cell nodes (by column idx). Lazy-populated by ensureCellNodesInRowsCache(). - cellNodesByColumnIdx: [], - - // Column indices of cell nodes that have been rendered, but not yet indexed in - // cellNodesByColumnIdx. These are in the same order as cell nodes added at the - // end of the row. - cellRenderQueue: [] - }; + // add any rows that have rowspan intersects if it's not already in the list + if (this._options.enableCellRowSpan) { + const parentRowSpan = this.getRowSpanIntersect(i); + if (parentRowSpan !== null) { + renderingRows.add(parentRowSpan); // add to Set which will take care of duplicate rows + } + } this.appendRowHtml(divArrayL, divArrayR, i, range, dataLength); + mustRenderRows.add(i); if (this.activeCellNode && this.activeRow === i) { needToReselectCell = true; } this.counter_rows_rendered++; } - if (!rows.length) { return; } + // check if there's any col/row span intersecting and if so add them to the renderingRows + const mandatorySpanRows = this.setDifference(renderingRows, mustRenderRows); + if (mandatorySpanRows.size > 0) { + mandatorySpanRows.forEach(r => { + this.removeRowFromCache(r); // remove any previous element to avoid duplicates in DOM + rows.push(r); + this.rowsCache[r] = this.createEmptyCachingRow(); + this.appendRowHtml(divArrayL, divArrayR, r, range, dataLength); + }); + } - const x = document.createElement('div'); - const xRight = document.createElement('div'); + if (rows.length) { + const x = document.createElement('div'); + const xRight = document.createElement('div'); - divArrayL.forEach(elm => x.appendChild(elm as HTMLElement)); - divArrayR.forEach(elm => xRight.appendChild(elm as HTMLElement)); + divArrayL.forEach(elm => x.appendChild(elm as HTMLElement)); + divArrayR.forEach(elm => xRight.appendChild(elm as HTMLElement)); - for (let i = 0, ii = rows.length; i < ii; i++) { - if ((this.hasFrozenRows) && (rows[i] >= this.actualFrozenRow)) { - if (this.hasFrozenColumns()) { + for (let i = 0, ii = rows.length; i < ii; i++) { + if ((this.hasFrozenRows) && (rows[i] >= this.actualFrozenRow)) { + if (this.hasFrozenColumns()) { + if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild && xRight.firstChild) { + this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement, xRight.firstChild as HTMLElement]; + this._canvasBottomL.appendChild(x.firstChild as ChildNode); + this._canvasBottomR.appendChild(xRight.firstChild as ChildNode); + } + } else { + if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild) { + this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement]; + this._canvasBottomL.appendChild(x.firstChild as ChildNode); + } + } + } else if (this.hasFrozenColumns()) { if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild && xRight.firstChild) { this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement, xRight.firstChild as HTMLElement]; - this._canvasBottomL.appendChild(x.firstChild as ChildNode); - this._canvasBottomR.appendChild(xRight.firstChild as ChildNode); + this._canvasTopL.appendChild(x.firstChild as ChildNode); + this._canvasTopR.appendChild(xRight.firstChild as ChildNode); } } else { if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild) { this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement]; - this._canvasBottomL.appendChild(x.firstChild as ChildNode); + this._canvasTopL.appendChild(x.firstChild as ChildNode); } } - } else if (this.hasFrozenColumns()) { - if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild && xRight.firstChild) { - this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement, xRight.firstChild as HTMLElement]; - this._canvasTopL.appendChild(x.firstChild as ChildNode); - this._canvasTopR.appendChild(xRight.firstChild as ChildNode); - } - } else { - if (this.rowsCache?.hasOwnProperty(rows[i]) && x.firstChild) { - this.rowsCache[rows[i]].rowNode = [x.firstChild as HTMLElement]; - this._canvasTopL.appendChild(x.firstChild as ChildNode); - } } - } - if (needToReselectCell) { - this.activeCellNode = this.getCellNode(this.activeRow, this.activeCell); + if (needToReselectCell) { + this.activeCellNode = this.getCellNode(this.activeRow, this.activeCell); + } } } + /** polyfill if the new Set.difference() added in ES2024 */ + protected setDifference(a: Set, b: Set): Set { + return new Set(Array.from(a).filter((item) => !b.has(item))); + } + protected startPostProcessing() { if (!this._options.enableAsyncPostRender) { return; @@ -4996,7 +5309,6 @@ export class SlickGrid = Column, O e this.lastRenderedScrollTop = this.scrollTop; this.lastRenderedScrollLeft = this.scrollLeft; - this.h_render = null; this.trigger(this.onRendered, { startRow: visible.top, endRow: visible.bottom, grid: this }); } @@ -5857,14 +6169,14 @@ export class SlickGrid = Column, O e top: y1, left: x1, bottom: y2, - right: x2 + right: x2, }; } ////////////////////////////////////////////////////////////////////////////////////////////// // Cell switching - /** Resets active cell by making cell normal and other internal resets. */ + /** Resets active cell by making cell normal and other internal reset. */ resetActiveCell() { this.setActiveCellInternal(null, false); } @@ -5945,7 +6257,8 @@ export class SlickGrid = Column, O e const cell = this.getCellFromPoint(activeCellOffset!.left, Math.ceil(activeCellOffset!.top) - rowOffset); this.activeRow = cell.row; - this.activeCell = this.activePosX = this.activeCell = this.activePosX = this.getCellFromNode(this.activeCellNode); + this.activePosY = cell.row; + this.activeCell = this.activePosX = this.getCellFromNode(this.activeCellNode); if (!Utils.isDefined(opt_editMode) && this._options.autoEditNewRow) { opt_editMode = (this.activeRow === this.getDataLength()) || this._options.autoEdit; @@ -5959,9 +6272,8 @@ export class SlickGrid = Column, O e } if (this._options.editable && opt_editMode && this.isCellPotentiallyEditable(this.activeRow, this.activeCell)) { - window.clearTimeout(this.h_editorLoader); - if (this._options.asyncEditorLoading) { + window.clearTimeout(this.h_editorLoader); this.h_editorLoader = window.setTimeout(() => { this.makeActiveCellEditable(undefined, preClickModeOn, e); }, this._options.asyncEditorLoadDelay); @@ -6097,7 +6409,7 @@ export class SlickGrid = Column, O e Utils.emptyElement(this.activeCellNode); } - let metadata = (this.data as CustomDataView)?.getItemMetadata?.(this.activeRow); + let metadata = this.getItemMetadaWhenExists(this.activeRow); metadata = metadata?.columns as any; const columnMetaData = metadata && (metadata[columnDef.id as keyof ItemMetadata] || (metadata as any)[this.activeCell]); @@ -6331,22 +6643,13 @@ export class SlickGrid = Column, O e row = 0; } - let cell = 0; - let prevCell: number | null = null; - const prevActivePosX = this.activePosX; - while (cell <= this.activePosX) { - if (this.canCellBeActive(row, cell)) { - prevCell = cell; - } - cell += this.getColspan(row, cell); - } - - if (prevCell !== null) { - this.setActiveCellInternal(this.getCellNode(row, prevCell)); - this.activePosX = prevActivePosX; - } else { - this.resetActiveCell(); - } + // use the gotoDown/Up but cancel its row move to activate same row + // (i.e.: gotoDown(row - 1) will go to same row if it can be activated or next one down). + // We do this in order to find the next cell that can be activated which can be much further away (i.e. rowspan) + const pos = dir === 1 + ? this.gotoDown(row - 1 || 0, this.activeCell, this.activePosY, this.activePosX) + : this.gotoUp(row + 1, this.activeCell, this.activePosY, this.activePosX) + this.navigateToPos(pos); } } @@ -6368,13 +6671,23 @@ export class SlickGrid = Column, O e /** Navigate to the bottom of the grid */ navigateBottom() { - this.unsetActiveCell(); - this.navigateToRow(this.getDataLength() - 1); + const row = this.getDataLength() - 1; + let tmpRow = this.getParentRowSpanByCell(row, this.activeCell)?.start ?? row; + + do { + if (this._options.enableCellRowSpan) { + this.setActiveRow(tmpRow); + } + const isValidMode = this.navigateToRow(tmpRow); + if ((isValidMode && this.activeCell === this.activePosX) || !Utils.isDefined(this.activeCell)) { + break; + } + } while (--tmpRow > 0); } navigateToRow(row: number) { const num_rows = this.getDataLength(); - if (!num_rows) { return true; } + if (!num_rows) { return false; } if (row < 0) { row = 0; @@ -6383,6 +6696,8 @@ export class SlickGrid = Column, O e } this.scrollCellIntoView(row, 0, true); + let isValidMove = !Utils.isDefined(this.activeCell) || !Utils.isDefined(this.activeRow); + if (this._options.enableCellNavigation && Utils.isDefined(this.activeRow)) { let cell = 0; let prevCell: number | null = null; @@ -6390,6 +6705,9 @@ export class SlickGrid = Column, O e while (cell <= this.activePosX) { if (this.canCellBeActive(row, cell)) { prevCell = cell; + if (!Utils.isDefined(this.activeCell) || cell === this.activeCell) { + isValidMove = true; + } } cell += this.getColspan(row, cell); } @@ -6401,15 +6719,18 @@ export class SlickGrid = Column, O e this.resetActiveCell(); } } - return true; + return isValidMove; } protected getColspan(row: number, cell: number): number { - const metadata = (this.data as CustomDataView)?.getItemMetadata?.(row); + const metadata = this.getItemMetadaWhenExists(row); if (!metadata || !metadata.columns) { return 1; } + if (cell >= this.columns.length) { + cell = this.columns.length - 1; + } const columnData = metadata.columns[this.columns[cell].id] || metadata.columns[cell]; let colspan = columnData?.colspan; if (colspan === '*') { @@ -6421,216 +6742,298 @@ export class SlickGrid = Column, O e return colspan as number; } - protected findFirstFocusableCell(row: number) { + protected getRowspan(row: number, cell: number) { + let rowspan = 1; + const metadata = this.getItemMetadaWhenExists(row); + if (metadata?.columns) { + Object.keys(metadata.columns).forEach(col => { + const colIdx = Number(col); + if (colIdx === cell) { + const columnMeta = metadata.columns![colIdx]; + rowspan = Number(columnMeta?.rowspan || 1); + } + }); + } + return rowspan; + } + + protected findFocusableRow(row: number, cell: number, dir: 'up' | 'down') { + let r = row; + const rowRange = this._colsWithRowSpanCache[cell] || new Set(); + let found = false; + + Array.from(rowRange).forEach((rrange) => { + const [start, end] = rrange.split(':').map(Number); + if (!found && row >= start && row <= end) { + r = dir === 'up' ? start : end; + if (this.canCellBeActive(r, cell)) { + found = true; + } + } + }); + if (r < 0) { + r = 0; + } + + return r; + } + + protected findFirstFocusableCell(row: number): { cell: number; row: number; } { let cell = 0; + let focusableRow = row; + let ff = -1; + while (cell < this.columns.length) { - if (this.canCellBeActive(row, cell)) { - return cell; + const prs = this.getParentRowSpanByCell(row, cell); + focusableRow = (prs !== null && prs.start !== row) ? prs.start : row; + if (this.canCellBeActive(focusableRow, cell)) { + ff = cell; + break; } - cell += this.getColspan(row, cell); + cell += this.getColspan(focusableRow, cell); } - return null; + return { cell: ff, row: focusableRow }; } - protected findLastFocusableCell(row: number) { + protected findLastFocusableCell(row: number): { cell: number; row: number; } { let cell = 0; - let lastFocusableCell: number | null = null; + let focusableRow = row; + let lf = -1; + while (cell < this.columns.length) { - if (this.canCellBeActive(row, cell)) { - lastFocusableCell = cell; + const prs = this.getParentRowSpanByCell(row, cell); + focusableRow = (prs !== null && prs.start !== row) ? prs.start : row; + if (this.canCellBeActive(focusableRow, cell)) { + lf = cell; + } + cell += this.getColspan(focusableRow, cell); + } + + return { cell: lf, row: focusableRow }; + } + + /** + * From any row/cell indexes that might have colspan/rowspan, find its starting indexes + * For example, if we start at 0,0 and we have colspan/rowspan of 4 for both and our indexes is row:2,cell:3 + * then our starting row/cell is 0,0. If a cell has no spanning at all then row/cell output is same as input + */ + findSpanStartingCell(row: number, cell: number) { + const prs = this.getParentRowSpanByCell(row, cell); + const focusableRow = (prs !== null && prs.start !== row) ? prs.start : row; + let fc = 0; + let prevCell = 0; + + while (fc < this.columns.length) { + fc += this.getColspan(focusableRow, fc); + if (fc > cell) { + fc = prevCell; + return { cell: fc, row: focusableRow }; } - cell += this.getColspan(row, cell); + prevCell = fc; } - return lastFocusableCell; + + return { cell: fc, row: focusableRow }; } - protected gotoRight(row: number, cell: number, _posX?: number) { + protected gotoRight(_row: number, cell: number, posY: number, _posX?: number) { if (cell >= this.columns.length) { return null; } + let fc = cell + 1; + let fr = posY; do { - cell += this.getColspan(row, cell); - } - while (cell < this.columns.length && !this.canCellBeActive(row, cell)); + const sc = this.findSpanStartingCell(posY, fc); + fr = sc.row; + fc = sc.cell; + if (this.canCellBeActive(fr, fc) && fc > cell) { + break; + } + fc += this.getColspan(fr, sc.cell); + } while (fc < this.columns.length); - if (cell < this.columns.length) { + if (fc < this.columns.length) { return { - row, - cell, - posX: cell + row: fr, + cell: fc, + posX: fc, + posY, }; } return null; } - protected gotoLeft(row: number, cell: number, _posX?: number) { + protected gotoLeft(row: number, cell: number, posY: number, _posX?: number) { if (cell <= 0) { return null; } - const firstFocusableCell = this.findFirstFocusableCell(row); - if (firstFocusableCell === null || firstFocusableCell >= cell) { + const ff = this.findFirstFocusableCell(row); + if (ff.cell === null || ff.cell >= cell) { return null; } + let pos: CellPosition | null; let prev = { row, - cell: firstFocusableCell, - posX: firstFocusableCell + cell: ff.cell, + posX: ff.cell, + posY, }; - let pos; + while (true) { - pos = this.gotoRight(prev.row, prev.cell, prev.posX); + pos = this.gotoRight(prev.row, prev.cell, prev.posY, prev.posX); if (!pos) { return null; } if (pos.cell >= cell) { + // when right cell is within a rowspan, we need to use original row (posY) + const nextRow = this.findFocusableRow(posY, prev.cell, 'up'); + if (nextRow !== prev.row) { + prev.row = nextRow; + } return prev; } prev = pos; } } - protected gotoDown(row: number, cell: number, posX: number) { + protected gotoDown(row: number, cell: number, _posY: number, posX: number) { let prevCell; - const dataLengthIncludingAddNew = this.getDataLengthIncludingAddNew(); - while (true) { - if (++row >= dataLengthIncludingAddNew) { - return null; - } - + const ub = this.getDataLengthIncludingAddNew(); + do { + row += this.getRowspan(row, posX); prevCell = cell = 0; while (cell <= posX) { prevCell = cell; cell += this.getColspan(row, cell); } + } + while (row <= ub && !this.canCellBeActive(row, prevCell)); - if (this.canCellBeActive(row, prevCell)) { - return { - row, - cell: prevCell, - posX - }; - } + if (row <= ub) { + return { + row, + cell: prevCell, + posX, + posY: row, + }; } + return null; } - protected gotoUp(row: number, cell: number, posX: number) { + protected gotoUp(row: number, cell: number, _posY: number, posX: number) { let prevCell; - while (true) { - if (--row < 0) { - return null; - } - + if (row <= 0) { + return null; + } + do { + row = this.findFocusableRow(row - 1, posX, 'up'); prevCell = cell = 0; while (cell <= posX) { prevCell = cell; cell += this.getColspan(row, cell); } + } + while (row >= 0 && !this.canCellBeActive(row, prevCell)); - if (this.canCellBeActive(row, prevCell)) { - return { - row, - cell: prevCell, - posX - }; - } + if (cell <= this.columns.length) { + return { + row, + cell: prevCell, + posX, + posY: row, + }; } + return null; } - protected gotoNext(row: number, cell: number, posX?: number) { + protected gotoNext(row: number, cell: number, posY: number, posX: number) { if (!Utils.isDefined(row) && !Utils.isDefined(cell)) { - row = cell = posX = 0; + row = cell = posY = posX = 0; if (this.canCellBeActive(row, cell)) { return { row, cell, - posX: cell + posX: cell, + posY, }; } } - const pos = this.gotoRight(row, cell, posX); - if (pos) { - return pos; - } - - let firstFocusableCell: number | null = null; - const dataLengthIncludingAddNew = this.getDataLengthIncludingAddNew(); - - // if at last row, cycle through columns rather than get stuck in the last one - if (row === dataLengthIncludingAddNew - 1) { row--; } - - while (++row < dataLengthIncludingAddNew) { - firstFocusableCell = this.findFirstFocusableCell(row); - if (firstFocusableCell !== null) { - return { - row, - cell: firstFocusableCell, - posX: firstFocusableCell - }; + let pos = this.gotoRight(row, cell, posY, posX); + if (!pos) { + let ff; + while (!pos && ++posY < this.getDataLength() + (this._options.enableAddRow ? 1 : 0)) { + ff = this.findFirstFocusableCell(posY); + if (ff.cell !== null) { + row = this.getParentRowSpanByCell(posY, ff.cell)?.start ?? posY; + pos = { + row, + cell: ff.cell, + posX: ff.cell, + posY, + }; + } } } - return null; + return pos; } - protected gotoPrev(row: number, cell: number, posX?: number) { + protected gotoPrev(row: number, cell: number, posY: number, posX: number) { if (!Utils.isDefined(row) && !Utils.isDefined(cell)) { - row = this.getDataLengthIncludingAddNew() - 1; + row = posY = this.getDataLengthIncludingAddNew() - 1; cell = posX = this.columns.length - 1; if (this.canCellBeActive(row, cell)) { return { row, cell, - posX: cell + posX: cell, + posY, }; } } - let pos; - let lastSelectableCell; - while (!pos) { - pos = this.gotoLeft(row, cell, posX); - if (pos) { - break; - } - if (--row < 0) { - return null; - } - - cell = 0; - lastSelectableCell = this.findLastFocusableCell(row); - if (lastSelectableCell !== null) { - pos = { - row, - cell: lastSelectableCell, - posX: lastSelectableCell - }; + let pos = this.gotoLeft(row, cell, posY, posX); + if (!pos) { + let lf; + while (!pos && --posY >= 0) { + lf = this.findLastFocusableCell(posY); + if (lf.cell > -1) { + row = this.getParentRowSpanByCell(posY, lf.cell)?.start ?? posY; + pos = { + row, + cell: lf.cell, + posX: lf.cell, + posY, + }; + } } } return pos; } - protected gotoRowStart(row: number, _cell: number, _posX?: number) { - const newCell = this.findFirstFocusableCell(row); - if (newCell === null) { return null; } + protected gotoRowStart(row: number, _cell: number, _posY: number, _posX: number) { + const ff = this.findFirstFocusableCell(row); + if (ff.cell === null) { return null; } return { - row, - cell: newCell, - posX: newCell + row: ff.row, + cell: ff.cell, + posX: ff.cell, + posY: row, }; } - protected gotoRowEnd(row: number, _cell: number, _posX?: number) { - const newCell = this.findLastFocusableCell(row); - if (newCell === null) { return null; } + protected gotoRowEnd(row: number, _cell: number, _posY: number, _posX: number) { + const lf = this.findLastFocusableCell(row); + if (lf.cell === -1) { return null; } return { - row, - cell: newCell, - posX: newCell + row: lf.row, + cell: lf.cell, + posX: lf.cell, + posY: row }; } @@ -6728,7 +7131,11 @@ export class SlickGrid = Column, O e 'end': this.gotoRowEnd }; const stepFn = stepFunctions[dir]; - const pos = stepFn.call(this, this.activeRow, this.activeCell, this.activePosX); + const pos = stepFn.call(this, this.activeRow, this.activeCell, this.activePosY, this.activePosX); + return this.navigateToPos(pos); + } + + protected navigateToPos(pos: CellPosition | null) { if (pos) { if (this.hasFrozenRows && this._options.frozenBottom && pos.row === this.getDataLength()) { return; @@ -6743,6 +7150,7 @@ export class SlickGrid = Column, O e } this.setActiveCellInternal(this.getCellNode(pos.row, pos.cell)); this.activePosX = pos.posX; + this.activePosY = pos.posY; return true; } else { this.setActiveCellInternal(this.getCellNode(this.activeRow, this.activeCell)); @@ -6827,7 +7235,14 @@ export class SlickGrid = Column, O e return false; } - const rowMetadata = (this.data as CustomDataView)?.getItemMetadata?.(row); + // cell not found in rows that are spanned (rowspan of 1 or more) are invalid + // i.e.: if the 5th cell has rowspan that reaches the end of the grid, then the last cell that can be active is 5 (anything above 5 on same column is invalid) + const spanRow = this.getParentRowSpanByCell(row, cell)?.start ?? row; + if (spanRow !== row) { + return false; + } + + const rowMetadata = this.getItemMetadaWhenExists(row); if (rowMetadata?.focusable !== undefined) { return !!rowMetadata.focusable; } @@ -6857,7 +7272,7 @@ export class SlickGrid = Column, O e return false; } - const rowMetadata = (this.data as CustomDataView)?.getItemMetadata?.(row); + const rowMetadata = this.getItemMetadaWhenExists(row); if (rowMetadata?.selectable !== undefined) { return !!rowMetadata.selectable; } diff --git a/src/slick.groupitemmetadataprovider.ts b/src/slick.groupitemmetadataprovider.ts index 1e98f8fbd..befa60073 100644 --- a/src/slick.groupitemmetadataprovider.ts +++ b/src/slick.groupitemmetadataprovider.ts @@ -164,7 +164,7 @@ export class SlickGroupItemMetadataProvider implements SlickPlugin { } } - getGroupRowMetadata(item: GroupingFormatterItem, _row: number): ItemMetadata { + getGroupRowMetadata(item: GroupingFormatterItem, _row?: number, _cell?: number): ItemMetadata { const groupLevel = item?.level; return { selectable: false, @@ -181,7 +181,7 @@ export class SlickGroupItemMetadataProvider implements SlickPlugin { }; } - getTotalsRowMetadata(item: { group: GroupingFormatterItem }, _row: number): ItemMetadata | null { + getTotalsRowMetadata(item: { group: GroupingFormatterItem }, _row?: number, _cell?: number): ItemMetadata | null { const groupLevel = item?.group?.level; return { selectable: false, diff --git a/src/styles/example-demo.scss b/src/styles/example-demo.scss index f163ddb02..d575ea21f 100644 --- a/src/styles/example-demo.scss +++ b/src/styles/example-demo.scss @@ -24,6 +24,12 @@ h2 { } } +code { + color: #f96079; + font-family: 'Courier New', Courier, monospace; + font-weight: 600; +} + ul { margin-left: 0; padding-left: 20px; @@ -46,4 +52,28 @@ ul { font-family: var(--alpine-font-family, $alpine-font-family); font-size: 12px; } +} + +button.btn { + cursor: pointer; + background-color: white; + border: 1px solid #9a9a9a; + border-radius: 3px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 6px 8px; + gap: 4px; +} +.rotate-90 { + transform: rotate(90deg); +} +.rotate-180 { + transform: rotate(180deg); +} +.rotate-270 { + transform: rotate(270deg); +} +.text-italic { + font-style: italic; } \ No newline at end of file diff --git a/src/styles/slick-alpine-theme.scss b/src/styles/slick-alpine-theme.scss index f8d6cbc2d..3fc660ea7 100644 --- a/src/styles/slick-alpine-theme.scss +++ b/src/styles/slick-alpine-theme.scss @@ -707,6 +707,9 @@ &.editable { box-shadow: var(--alpine-cell-editable-box-shadow, v.$alpine-cell-editable-box-shadow); } + &.hidden { + display: none; + } } input.editor-checkbox, diff --git a/src/styles/slick-default-theme.scss b/src/styles/slick-default-theme.scss index 1a0763f44..d3d61f609 100644 --- a/src/styles/slick-default-theme.scss +++ b/src/styles/slick-default-theme.scss @@ -130,6 +130,9 @@ classes should alter those! border-color: gray; border-style: solid; } +.slick-cell.hidden { + display: none; +} .slick-sortable-placeholder { background: #d9d9d9 !important;