diff --git a/playwright/e2e/custom-template.spec.ts b/playwright/e2e/custom-template.spec.ts new file mode 100644 index 000000000..568584a66 --- /dev/null +++ b/playwright/e2e/custom-template.spec.ts @@ -0,0 +1,203 @@ +import { Page } from '@playwright/test'; +import { expect, test } from '../support/test-helpers'; + +test.describe('summary row', () => { + test.describe('simple summary row', () => { + const example = 'simple-summary'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const summaryRow = page.locator('datatable-summary-row'); + await expect(summaryRow).toHaveCount(1); + + const enableSummaryRow = page.locator('#enable-summary'); + + await enableSummaryRow.click(); + await expect(summaryRow).toHaveCount(0); + + await si.runVisualAndA11yTests('no-summary-row', [ + { + id: 'aria-required-children', + enabled: false + } + ]); + + enableSummaryRow.click(); + await expect(summaryRow).toHaveCount(1); + + await si.runVisualAndA11yTests('show-summary-row', [ + { + id: 'aria-required-children', + enabled: false + }, + { + id: 'aria-required-parent', + enabled: false + } + ]); + + const scrollerElement = await page.locator('datatable-scroller').boundingBox(); + let summaryElementBox = await summaryRow.boundingBox(); + + expect(scrollerElement.y).toBe(summaryElementBox.y); + + await si.runVisualAndA11yTests('summary-row-at-top', [ + { + id: 'aria-required-children', + enabled: false + }, + { + id: 'aria-required-parent', + enabled: false + } + ]); + + await page.getByLabel('Position').selectOption('bottom'); + + summaryElementBox = await summaryRow.boundingBox(); + const lastElement = await page.locator('datatable-row-wrapper').last().boundingBox(); + + expect(summaryElementBox.y).toBe(lastElement.y + lastElement.height); + + await si.runVisualAndA11yTests('summary-row-at-bottom', [ + { + id: 'aria-required-children', + enabled: false + } + ]); + + await testSummaryRowData(page); + }); + }); + + test.describe('custom template summary', () => { + const example = 'custom-template-summary'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const summaryRow = page.locator('datatable-summary-row'); + await expect(summaryRow).toHaveCount(1); + + await testSummaryRowData(page); + + await si.runVisualAndA11yTests('custom-template-default', [ + { + id: 'aria-required-children', + enabled: false + }, + { + id: 'aria-required-parent', + enabled: false + } + ]); + }); + }); + + test.describe('server side template summary', () => { + const example = 'paging-summary'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + await expect(page.locator('.empty-row')).toBeVisible(); + + await page.waitForSelector('datatable-row-wrapper'); + await expect(page.locator('.empty-row')).toBeHidden(); + + const summaryRow = page.locator('datatable-summary-row'); + + const femaleCells = await page + .locator('datatable-body-cell') + .locator('span[title="female"]') + .all(); + + const maleCells = await page + .locator('datatable-body-cell') + .locator('span[title="male"]') + .all(); + + const genderColumn = summaryRow.locator('datatable-body-cell').nth(1).locator('span'); + const nameColumn = summaryRow.locator('datatable-body-cell').first().locator('span'); + + await expect(genderColumn).toContainText(`${femaleCells.length} females`); + await expect(genderColumn).toContainText(`${maleCells.length} males`); + await expect(nameColumn).toContainText(`${maleCells.length + femaleCells.length} total`); + + await si.runVisualAndA11yTests('custom-template', [ + { + id: 'aria-required-children', + enabled: false + }, + { + id: 'aria-required-parent', + enabled: false + } + ]); + }); + }); + + test.describe('inline html template summary', () => { + const example = 'inline-html-summary'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const summaryRow = page.locator('datatable-summary-row'); + await expect(summaryRow).toHaveCount(1); + + await testSummaryRowData(page); + + const rows = await page.locator('datatable-row-wrapper').locator('datatable-body-row').all(); + + const nameColumn = summaryRow.locator('datatable-body-cell').first().locator('span'); + + expect(rows).toHaveLength(5); + + const names: string[] = []; + + for (const row of rows) { + const fullName = await row.locator('datatable-body-cell').first().innerText(); + names.push(fullName.split(' ')[1]); + } + + expect(names).toHaveLength(rows.length); + + for (let name of names) { + await expect(nameColumn.getByText(name, { exact: true })).toHaveCount(1); + } + + await si.runVisualAndA11yTests('custom-template-lastname-only', [ + { + id: 'aria-required-children', + enabled: false + }, + { + id: 'aria-required-parent', + enabled: false + }, + { + id: 'scrollable-region-focusable', + enabled: false + } + ]); + }); + }); +}); + +const testSummaryRowData = async (page: Page) => { + const summaryRow = page.locator('datatable-summary-row'); + + const femaleCells = await page + .locator('datatable-body-cell') + .locator('span[title="female"]') + .all(); + + const maleCells = await page.locator('datatable-body-cell').locator('span[title="male"]').all(); + + const genderColumn = summaryRow.locator('datatable-body-cell').nth(1).locator('span'); + + await expect(genderColumn).toContainText(`females: ${femaleCells.length}`); + await expect(genderColumn).toContainText(`males: ${maleCells.length}`); +}; diff --git a/playwright/e2e/drag-drop.spec.ts b/playwright/e2e/drag-drop.spec.ts new file mode 100644 index 000000000..0734a8b71 --- /dev/null +++ b/playwright/e2e/drag-drop.spec.ts @@ -0,0 +1,58 @@ +import { expect, test } from '../support/test-helpers'; + +test.describe('drag drop using angular cdk', () => { + const example = 'drag-drop'; + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const loadingIndicator = page.locator('datatable-progress').locator('div[class="bar"]'); + await expect(loadingIndicator).toHaveCount(0); + + const originRow = page.getByRole('row', { name: 'Ethel Price' }); + const destinationRow = page.getByRole('row', { name: 'Georgina Schultz' }); + + const originBoundingBox = await originRow.boundingBox(); + const destinationBoundingBox = await destinationRow.boundingBox(); + + await expect( + page.locator('datatable-row-wrapper').nth(0).locator('span[title="Ethel Price"]') + ).toHaveCount(1); + + await expect( + page.locator('datatable-row-wrapper').nth(4).locator('span[title="Georgina Schultz"]') + ).toHaveCount(1); + + await originRow.click(); + + await page.mouse.move( + originBoundingBox.x + originBoundingBox.width / 2, + originBoundingBox.y + originBoundingBox.height / 2 + ); + + await page.mouse.down(); + + await page.mouse.move( + destinationBoundingBox.x + destinationBoundingBox.width / 2, + destinationBoundingBox.y + destinationBoundingBox.height / 2, + { + steps: 20 + } + ); + + await page.mouse.up(); + + await expect( + page.locator('datatable-row-wrapper').nth(0).locator('span[title="Claudine Neal"]') + ).toHaveCount(1); + + await expect( + page.locator('datatable-row-wrapper').nth(3).locator('span[title="Georgina Schultz"]') + ).toHaveCount(1); + + await expect( + page.locator('datatable-row-wrapper').nth(4).locator('span[title="Ethel Price"]') + ).toHaveCount(1); + + await si.runVisualAndA11yTests('switch-rows'); + }); +}); diff --git a/playwright/e2e/paging.spec.ts b/playwright/e2e/paging.spec.ts new file mode 100644 index 000000000..4909de211 --- /dev/null +++ b/playwright/e2e/paging.spec.ts @@ -0,0 +1,227 @@ +import { Page } from '@playwright/test'; +import { expect, test } from '../support/test-helpers'; + +test.describe('paging', () => { + test.describe('client side', () => { + const example = 'client-paging'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + await pagerTest(page, 10); + + await si.runVisualAndA11yTests('default-paginator'); + }); + }); + + test.describe('server side', () => { + const example = 'server-paging'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + await page.waitForSelector('datatable-row-wrapper'); + await pagerTest(page, 20); + await expect(page.locator('.empty-row')).not.toBeVisible(); + await page.waitForSelector('span[title="Tonya Bray"]'); + await expect(page.getByRole('cell', { name: 'Ethel Price' })).not.toBeVisible(); + await si.runVisualAndA11yTests('server-side-paginator'); + }); + }); + + test.describe('paging scrolling with no virtualization', () => { + const example = 'paging-scrolling-novirtualization'; + + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + + await expect(page.locator('ghost-loader')).toBeVisible(); + await page.waitForSelector('datatable-row-wrapper'); + await pagerTest(page, 20); + await page.waitForSelector('span[title="Tonya Bray"]'); + await expect(page.locator('ghost-loader').first()).not.toBeVisible(); + await expect(page.getByRole('cell', { name: 'Ethel Price' })).not.toBeVisible(); + await si.runVisualAndA11yTests('novirtualization'); + }); + }); + + test.describe('server scrolling', () => { + const example = 'server-scrolling'; + + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + + await expect(page.locator('ghost-loader')).toBeVisible(); + await expect(page.locator('.bar')).toBeVisible(); + + await page.waitForSelector('datatable-scroller'); + + const pager = page.locator('datatable-pager'); + expect(pager).not.toBeVisible(); + + await expect(page.locator('ghost-loader')).not.toBeVisible(); + await expect(page.locator('.bar')).not.toBeVisible(); + + await si.runVisualAndA11yTests('infinite-scroll-initial', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + }, + { + id: 'aria-progressbar-name', + enabled: false + }, + { + id: 'aria-required-children', + enabled: false + } + ]); + + await page.getByRole('row', { name: 'Sarah Massey' }).click(); + + await page.mouse.wheel(0, 1000); + + await expect(page.locator('ghost-loader').first()).toBeVisible(); + await expect(page.locator('.bar')).toBeVisible(); + + await si.runVisualAndA11yTests('infinite-scroll-after-scroll', [ + { + id: 'aria-progressbar-name', + enabled: false + }, + { + id: 'aria-required-children', + enabled: false + } + ]); + }); + }); + + test.describe('virtual server side paging', () => { + const example = 'virtual-paging'; + + test(example + ' paginator test', async ({ si, page }) => { + await si.visitExample(example, false); + await pagerTest(page, 9); + await si.runVisualAndA11yTests('paginator'); + }); + + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + + await expect(page.locator('ghost-loader')).toBeVisible(); + await expect(page.locator('.custom-loading-content')).toBeVisible(); + + await page.waitForSelector('span[title="Claudine Neal"]'); + + await expect(page.locator('ghost-loader').first()).not.toBeVisible(); + await expect(page.locator('.custom-loading-content')).not.toBeVisible(); + + await si.runVisualAndA11yTests('virtual-scroll-initial'); + + await page.getByLabel('page 4').click(); + + await expect(page.locator('ghost-loader').first()).toBeVisible(); + await expect(page.locator('.custom-loading-content')).toBeVisible(); + + await page.waitForSelector('span[title="Freda Mason"]'); + + await si.runVisualAndA11yTests('virtual-server-side-navigate', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + }, + { + id: 'aria-progressbar-name', + enabled: false + }, + { + id: 'aria-required-children', + enabled: false + } + ]); + + await page.getByRole('row', { name: 'Freda Mason' }).click(); + await page.mouse.wheel(0, 500); + + await expect(page.locator('.custom-loading-content')).toBeVisible(); + + await page.waitForSelector('.datatable-body-cell-label'); + + await expect(page.locator('.custom-loading-content')).toHaveCount(0); + + await si.runVisualAndA11yTests('virtual-server-side-scroll'); + }); + }); +}); + +const pagerTest = async ( + page: Page, + numberOfRows: number, + checkForGhostLoader: boolean = false +) => { + const pager = page.locator('datatable-pager'); + expect(pager).toBeTruthy(); + + if (checkForGhostLoader) { + await expect(page.locator('ghost-loader').first()).toBeVisible(); + await expect(page.locator('.custom-loading-content')).toBeVisible(); + } + + const firstPageButton = page.getByLabel('go to first page').locator('..'); + const previousButton = page.getByLabel('go to previous page').locator('..'); + const nextButton = page.getByLabel('go to next page').locator('..'); + const lastButton = page.getByLabel('go to last page').locator('..'); + + await page.waitForSelector('span[title="Ethel Price"]'); + + if (checkForGhostLoader) { + await expect(page.locator('ghost-loader').first()).not.toBeVisible(); + await expect(page.locator('.custom-loading-content')).not.toBeVisible(); + } + + const displayedRows = await page.locator('datatable-row-wrapper').all(); + expect(displayedRows).toHaveLength(numberOfRows); + + await expect(page.getByRole('cell', { name: 'Ethel Price' })).toBeVisible(); + await expect(page.getByRole('cell', { name: 'Beryl Rice' })).toBeVisible(); + + await expect(firstPageButton).toHaveClass(/disabled/); + await expect(previousButton).toHaveClass(/disabled/); + + await expect(nextButton).not.toHaveClass(/disabled/); + await expect(lastButton).not.toHaveClass(/disabled/); + + await lastButton.click(); + + if (checkForGhostLoader) { + await expect(page.locator('ghost-loader').first()).toBeVisible(); + await expect(page.locator('.custom-loading-content')).toBeVisible(); + } + + await page.waitForSelector('span[title="Humphrey Curtis"]'); + + if (checkForGhostLoader) { + await expect(page.locator('ghost-loader').first()).not.toBeVisible(); + await expect(page.locator('.custom-loading-content')).not.toBeVisible(); + } + + await expect(page.getByRole('cell', { name: 'Ethel Price' })).not.toBeVisible(); + await expect(page.getByRole('cell', { name: 'Beryl Rice' })).not.toBeVisible(); + + await expect(firstPageButton).not.toHaveClass(/disabled/); + await expect(previousButton).not.toHaveClass(/disabled/); + + await expect(nextButton).toHaveClass(/disabled/); + await expect(lastButton).toHaveClass(/disabled/); + + await expect(page.getByRole('cell', { name: 'Humphrey Curtis' })).toBeVisible(); + await expect(page.getByRole('cell', { name: 'Christine Compton' })).toBeVisible(); +}; diff --git a/playwright/e2e/resize.spec.ts b/playwright/e2e/resize.spec.ts new file mode 100644 index 000000000..743fafa55 --- /dev/null +++ b/playwright/e2e/resize.spec.ts @@ -0,0 +1,66 @@ +import { expect, test } from '../support/test-helpers'; + +test.describe('resize and pinning', () => { + const example = 'pinning'; + test(example + ' resize column', async ({ si, page }) => { + await si.visitExample(example); + + const nameColumn = page.getByRole('columnheader', { name: 'Name' }); + const genderColumn = page.getByRole('columnheader', { name: 'Gender' }); + const cityColumn = page.getByRole('columnheader', { name: 'City' }); + const stateColumn = page.getByRole('columnheader', { name: 'State' }); + + const originalWidth = await nameColumn.boundingBox(); + + await expect(nameColumn).toHaveAttribute('resizeable'); + await expect(genderColumn).toHaveAttribute('resizeable'); + await expect(cityColumn).toHaveAttribute('resizeable'); + await expect(stateColumn).toHaveAttribute('resizeable'); + + await si.runVisualAndA11yTests('check-resize-attribute'); + + // Resize name column + const resizeHandler = nameColumn.locator('span[class="resize-handle"]'); + const originBoundingBox = await resizeHandler.boundingBox(); + const increaseWidthBy = 300; + + await page.mouse.click(originBoundingBox.x, originBoundingBox.y); + await page.mouse.down(); + await page.mouse.click(originBoundingBox.x + increaseWidthBy, originBoundingBox.y); + await page.mouse.up(); + + const updatedWidth = await nameColumn.boundingBox(); + + expect(updatedWidth.width).toBe(originalWidth.width + increaseWidthBy); + + await si.runVisualAndA11yTests('resize-name-column'); + }); + + test(example + ' pinning column', async ({ si, page }) => { + await si.visitExample(example); + + const nameColumn = page.getByRole('columnheader', { name: 'Name' }); + const genderColumn = page.getByRole('columnheader', { name: 'Gender' }); + const stateColumn = page.getByRole('columnheader', { name: 'State' }); + + const boundingBox = await page.locator('datatable-body').boundingBox(); + + await page.mouse.click(boundingBox.x, boundingBox.height - 2); + await page.mouse.down(); + + await page.mouse.move(boundingBox.x + 1000, boundingBox.height - 2, { steps: 20 }); + + await page.mouse.up(); + + await expect(nameColumn.locator('..')).toHaveCSS('z-index', '9'); + await expect(nameColumn.locator('..')).toHaveCSS('position', 'sticky'); + + await expect(stateColumn.locator('..')).toHaveCSS('z-index', '9'); + await expect(stateColumn.locator('..')).toHaveCSS('position', 'sticky'); + + await expect(genderColumn.locator('..')).not.toHaveCSS('z-index', '9'); + await expect(genderColumn.locator('..')).not.toHaveCSS('position', 'sticky'); + + await si.runVisualAndA11yTests('pinning-name-and-state-column'); + }); +}); diff --git a/playwright/e2e/selection.spec.ts b/playwright/e2e/selection.spec.ts new file mode 100644 index 000000000..2e2baf01d --- /dev/null +++ b/playwright/e2e/selection.spec.ts @@ -0,0 +1,316 @@ +import { expect, test } from '../support/test-helpers'; + +test.describe('selection', () => { + test.describe('cell selection', () => { + const example = 'cell-selection'; + + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + const nameCell = page.getByRole('cell', { name: 'Ethel Price' }); + await expect(nameCell).toBeVisible(); + + const nameCellParentEl = nameCell.locator('..').locator('..'); + + await nameCell.click(); + + await expect(nameCell).toHaveClass(/active/); + await expect(nameCellParentEl).toHaveClass(/active/); + + await si.runVisualAndA11yTests('name-cell-selection'); + + const companyCell = page.getByRole('cell', { name: 'Dogspa' }); + await expect(companyCell).toBeVisible(); + + const companyCellParentEl = companyCell.locator('..').locator('..'); + + await companyCell.click(); + + await expect(companyCell).toHaveClass(/active/); + await expect(companyCellParentEl).toHaveClass(/active/); + + await si.runVisualAndA11yTests('company-cell-selection'); + }); + }); + + test.describe('single row selection', () => { + const example = 'single-selection'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + const selectedRow = page.getByRole('row', { name: 'Claudine Neal' }); + + await selectedRow.click(); + + const cellsInRow = await selectedRow.locator('datatable-body-cell').all(); + + await expect(selectedRow).toHaveClass(/active/); + expect(cellsInRow).toHaveLength(3); + + await expect(cellsInRow.at(0)).toHaveClass(/active/); + + for (const cell of cellsInRow) { + await expect(cell).toHaveAttribute('ng-reflect-is-selected', 'true'); + } + + const selectedColumnLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedColumnLi).toHaveLength(1); + + await expect(selectedColumnLi[0]).toContainText('Claudine Neal'); + + await si.runVisualAndA11yTests('row-selection-initial'); + }); + }); + + test.describe('multi row selection', () => { + const example = 'multi-selection'; + + test(example + ' using Shift', async ({ si, page }) => { + await si.visitExample(example); + await page.getByRole('row', { name: 'Ethel Price' }).click(); + + await page.getByRole('row', { name: 'Wilder Gonzales' }).click({ + modifiers: ['Shift'] + }); + + const rows = await page.locator('datatable-body-row.active').all(); + + expect(rows).toHaveLength(4); + + const names = []; + rows.map(async row => { + names.push(await row.locator('datatable-body-cell').first().innerText()); + }); + + const selectedColumnLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedColumnLi.length).toBe(names.length); + + for (const li of selectedColumnLi) { + const name = await li.innerText(); + const namePresentInSelectedRow = names.includes(name); + expect(namePresentInSelectedRow).toBeTruthy(); + } + + await si.runVisualAndA11yTests('using-shift'); + }); + + test(example + ' using Ctrl', async ({ si, page }) => { + await si.visitExample(example); + await page.getByRole('row', { name: 'Ethel Price' }).click(); + + await page.getByRole('row', { name: 'Wilder Gonzales' }).click({ + modifiers: ['ControlOrMeta'] + }); + + const selectedRows = await page.locator('datatable-body-row.active').all(); + + expect(selectedRows).toHaveLength(2); + + const names = []; + selectedRows.map(async row => { + names.push(await row.locator('datatable-body-cell').first().innerText()); + }); + + const selectedColumnLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedColumnLi.length).toBe(names.length); + + for (const li of selectedColumnLi) { + const name = await li.innerText(); + const namePresentInSelectedRow = names.includes(name); + expect(namePresentInSelectedRow).toBeTruthy(); + } + + await si.runVisualAndA11yTests('using-ctrl'); + }); + }); + + test.describe('disable row selection', () => { + const example = 'multidisable-selection'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const disabledRow = page.getByRole('row', { name: 'Ethel Price' }); + await disabledRow.click(); + + let selectedRows = await page.locator('datatable-body-row.active').all(); + expect(selectedRows).toHaveLength(0); + + let selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(1); + expect(await selectedNamesLi.at(0).innerText()).toBe('No Selections'); + + await page.getByRole('row', { name: 'Beryl Rice' }).click(); + + selectedRows = await page.locator('datatable-body-row.active').all(); + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedRows).toHaveLength(1); + expect(selectedNamesLi).toHaveLength(1); + + await disabledRow.click(); + + selectedRows = await page.locator('datatable-body-row.active').all(); + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedRows).toHaveLength(0); + expect(selectedNamesLi).toHaveLength(1); + expect(await selectedNamesLi.at(0).innerText()).toBe('No Selections'); + + await si.runVisualAndA11yTests('disable-row-selection'); + }); + }); + + test.describe('checkbox selection', () => { + const example = 'chkbox-selection'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const noCheckBoxRow = page.getByRole('row', { name: 'Ethel Price' }); + await expect(noCheckBoxRow).toBeVisible(); + + const noCheckbox = noCheckBoxRow.locator('input[type=checkbox]'); + await expect(noCheckbox).not.toBeVisible(); + + const rowsWithCheckbox = await page.locator('datatable-body-row input[type=checkbox]').all(); + + expect(rowsWithCheckbox).toHaveLength(4); + + for (let row of rowsWithCheckbox) { + await row.check(); + } + + let selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(4); + + await si.runVisualAndA11yTests('checkbox-selection-all-checked', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + } + ]); + + await rowsWithCheckbox[0].uncheck(); + await rowsWithCheckbox[1].uncheck(); + + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(2); + + await si.runVisualAndA11yTests('checkbox-selection-uncheck', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + } + ]); + }); + }); + + test.describe('multi click row selection', () => { + const example = 'multi-click-selection'; + + test(example, async ({ si, page }) => { + await si.visitExample(example); + + await page.getByRole('row', { name: 'Claudine Neal' }).click(); + await page.getByRole('row', { name: 'Beryl Rice' }).click(); + await page.getByRole('row', { name: 'Wilder Gonzales' }).click(); + + let selectedRows = await page.locator('datatable-body-row.active').all(); + expect(selectedRows).toHaveLength(3); + + let selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(3); + + await page.getByRole('row', { name: 'Beryl Rice' }).click(); + + selectedRows = await page.locator('datatable-body-row.active').all(); + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedRows).toHaveLength(2); + expect(selectedNamesLi).toHaveLength(2); + + await page.getByRole('row', { name: 'Claudine Neal' }).click(); + + selectedRows = await page.locator('datatable-body-row.active').all(); + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + + expect(selectedRows).toHaveLength(1); + expect(selectedNamesLi).toHaveLength(1); + + await si.runVisualAndA11yTests('click-selection'); + }); + }); + + test.describe('multi click with checkbox selection', () => { + const example = 'multi-click-chkbox-selection'; + + test(example + ' using keyboard', async ({ si, page }) => { + await si.visitExample(example); + + const firstRow = page.getByRole('row', { name: 'Ethel Price' }); + await firstRow.focus(); + + await firstRow.locator('input[type=checkbox]').check(); + + // Move to 2nd row. + await page.keyboard.press('Tab'); + await page.keyboard.press('Space'); + + // Move to 4th row as 3rd row is disabled. + await page.keyboard.press('Tab'); + await page.keyboard.press('Space'); + + let selectedRows = await page.locator('datatable-body-row.active').all(); + expect(selectedRows).toHaveLength(3); + + const disabledElement = page.getByRole('row', { name: 'Beryl Rice' }); + + await expect(disabledElement).not.toHaveClass(/active/); + + let selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(3); + + await si.runVisualAndA11yTests('navigation-using-tab-and-space', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + } + ]); + + await page.keyboard.press('Shift+Tab'); + await page.keyboard.press('Space'); + + selectedRows = await page.locator('datatable-body-row.active').all(); + expect(selectedRows).toHaveLength(2); + + selectedNamesLi = await page.locator('.selected-column').locator('ul > li').all(); + expect(selectedNamesLi).toHaveLength(2); + + await si.runVisualAndA11yTests('backward-navigation-shift+tab+space', [ + { + id: 'label', + enabled: false + }, + { + id: 'empty-table-header', + enabled: false + } + ]); + }); + }); +}); diff --git a/playwright/e2e/sorting.spec.ts b/playwright/e2e/sorting.spec.ts new file mode 100644 index 000000000..057d87416 --- /dev/null +++ b/playwright/e2e/sorting.spec.ts @@ -0,0 +1,171 @@ +import { expect, test } from '../support/test-helpers'; + +test.describe('sorting', () => { + test.describe('client side sorting', () => { + const example = 'client-sorting'; + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const companyHeader = page.locator('datatable-header-cell[title="Company"]'); + const companyHeaderIcon = companyHeader.locator('span').nth(2); + const firstRow = page.locator('datatable-body-row').first(); + + await si.runVisualAndA11yTests('default-sorting'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-asc/); + + expect(firstRow.getByRole('cell', { name: 'Accidency' })).toBeTruthy(); + + await si.runVisualAndA11yTests('sorting-asc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-desc/); + + expect(firstRow.getByRole('cell', { name: 'Zolar' })).toBeTruthy(); + + await si.runVisualAndA11yTests('sorting-desc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).not.toHaveClass(/sort-active/); + await expect(companyHeader).not.toHaveClass(/sort-asc/); + await expect(companyHeader).not.toHaveClass(/sort-desc/); + expect( + firstRow.getByRole('cell', { name: 'Johnson, Johnson and Partners, LLC CMP DDC' }) + ).toBeTruthy(); + + await si.runVisualAndA11yTests('sorting-unset'); + }); + }); + + test.describe('default sorting', () => { + const example = 'default-sorting'; + test(example, async ({ si, page }) => { + await si.visitExample(example); + + const nameHeader = page.locator('datatable-header-cell[title="Name"]'); + const nameHeaderIcon = nameHeader.locator('span').nth(2); + const firstRow = page.locator('datatable-body-row').first(); + + await expect(nameHeader).toHaveClass(/sort-active/); + await expect(nameHeader).toHaveClass(/sort-desc/); + + expect(firstRow.getByRole('cell', { name: 'Yvonne Parsons' })).toBeTruthy(); + + await si.runVisualAndA11yTests('default-name-sorting-desc'); + + await nameHeaderIcon.click(); + + await expect(nameHeader).toHaveClass(/sort-active/); + await expect(nameHeader).toHaveClass(/sort-asc/); + + expect(firstRow.getByRole('cell', { name: 'Alexander Foley' })).toBeTruthy(); + + await si.runVisualAndA11yTests('sort-name-by-asc'); + + await nameHeaderIcon.click(); + + await expect(nameHeader).not.toHaveClass(/sort-active/); + await expect(nameHeader).not.toHaveClass(/sort-asc/); + await expect(nameHeader).not.toHaveClass(/sort-desc/); + + expect(firstRow.getByRole('cell', { name: 'Ethel Price' })).toBeTruthy(); + + await si.runVisualAndA11yTests('unset-name-sort'); + }); + }); + + test.describe('server side sorting', () => { + const example = 'server-sorting'; + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + + const companyHeader = page.locator('datatable-header-cell[title="Company"]'); + const companyHeaderIcon = companyHeader.locator('span').nth(2); + const firstRow = page.locator('datatable-body-row').first(); + const loadingIndicator = page.locator('datatable-progress').locator('div[class="bar"]'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-asc/); + await expect(loadingIndicator).toBeVisible(); + + await expect(loadingIndicator).toHaveCount(0); + + expect(firstRow.getByRole('cell', { name: 'Aquamate' })).toBeTruthy(); + + await si.runVisualAndA11yTests('sorting-asc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-desc/); + + await expect(loadingIndicator).toBeVisible(); + + await expect(loadingIndicator).toHaveCount(0); + + expect(firstRow.getByRole('cell', { name: 'Xyqag' })).toBeTruthy(); + + await si.runVisualAndA11yTests('sorting-desc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-asc/); + + await si.runVisualAndA11yTests('sorting-asc-again', [ + { + id: 'aria-progressbar-name', + enabled: false + }, + { + id: 'aria-required-children', + enabled: false + } + ]); + }); + }); + + test.describe('custom sorting comparator', () => { + const example = 'comparator-sorting'; + test(example, async ({ si, page }) => { + await si.visitExample(example, false); + + const companyHeader = page.locator('datatable-header-cell[title="Company"]'); + const companyHeaderIcon = companyHeader.locator('span').nth(2); + const firstRow = page.locator('datatable-body-row').first(); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-asc/); + + expect(firstRow.getByRole('cell', { name: 'Aquamate' })).toBeTruthy(); + + await si.runVisualAndA11yTests('comparator-sorting-asc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-desc/); + + expect(firstRow.getByRole('cell', { name: 'Xyqag' })).toBeTruthy(); + + await si.runVisualAndA11yTests('comparator-sorting-desc'); + + await companyHeaderIcon.click(); + + await expect(companyHeader).toHaveClass(/sort-active/); + await expect(companyHeader).toHaveClass(/sort-asc/); + + await si.runVisualAndA11yTests('comparator-sorting-asc-again'); + }); + }); +}); diff --git a/src/app/app.component.scss b/src/app/app.component.scss index e69de29bb..5ddb875f1 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -0,0 +1,5 @@ +$datatable-pager-color: rgba(0, 0, 0, 0.64); + +@import '../../projects/ngx-datatable/src/lib/themes/material.scss'; +@import '../../projects/ngx-datatable/src/lib/themes/dark.scss'; +@import '../../projects/ngx-datatable/src/lib/themes/bootstrap.scss'; diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 79344f580..46754c3b7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -8,12 +8,7 @@ import { RouterOutlet } from '@angular/router'; templateUrl: './app.component.html', // eslint-disable-next-line @angular-eslint/use-component-view-encapsulation encapsulation: ViewEncapsulation.None, - styleUrls: [ - './app.component.scss', - '../../projects/ngx-datatable/src/lib/themes/material.scss', - '../../projects/ngx-datatable/src/lib/themes/dark.scss', - '../../projects/ngx-datatable/src/lib/themes/bootstrap.scss' - ], + styleUrls: ['./app.component.scss'], providers: [ Location, { diff --git a/src/app/paging/mock-server-results-service.ts b/src/app/paging/mock-server-results-service.ts index 8d9e9ded6..d54e48f7c 100644 --- a/src/app/paging/mock-server-results-service.ts +++ b/src/app/paging/mock-server-results-service.ts @@ -20,7 +20,7 @@ export class MockServerResultsService { public getResults(page: Page): Observable> { return of(companyData) .pipe(map(d => this.getPagedData(page))) - .pipe(delay(1000 * Math.random())); + .pipe(delay(1500 * Math.random())); } /** diff --git a/src/app/paging/scrolling-server.component.ts b/src/app/paging/scrolling-server.component.ts index ae617f91d..3f6ecb35c 100644 --- a/src/app/paging/scrolling-server.component.ts +++ b/src/app/paging/scrolling-server.component.ts @@ -19,7 +19,7 @@ class PagedData { export class MockServerResultsService { public getResults(offset: number, limit: number): Observable> { return of(companyData.slice(offset, offset + limit)).pipe( - delay(new Date(Date.now() + 500)), + delay(new Date(Date.now() + 1500)), map(d => ({ data: d })) ); } diff --git a/src/app/selection/selection-chkbox-template.component.ts b/src/app/selection/selection-chkbox-template.component.ts index 080308a21..e2858cc31 100644 --- a/src/app/selection/selection-chkbox-template.component.ts +++ b/src/app/selection/selection-chkbox-template.component.ts @@ -25,7 +25,7 @@ import { Employee } from '../data.model';