From 0cb1ae550174cd21a026a8f4690b00087262c662 Mon Sep 17 00:00:00 2001 From: Andrei Fateev Date: Thu, 13 Aug 2026 16:15:55 +0200 Subject: [PATCH] test: add Playwright coverage and test ids for CpsTableComponent --- .../cps-ui-kit/components/cps-table.spec.ts | 613 +++++++++++++++++- playwright/fixtures/composition-components.ts | 7 +- .../src/app/api-data/cps-table.json | 8 + .../table-page/table-page.component.html | 102 ++- .../table-page/table-page.component.scss | 4 + .../pages/table-page/table-page.component.ts | 74 ++- .../pages/table-page/table-page.examples.ts | 159 ++++- .../cps-sort-icon/cps-sort-icon.component.ts | 1 + .../table-column-filter.component.html | 18 +- ...le-column-visibility-toggle.component.html | 4 + .../table-row-menu.component.html | 1 + .../cps-table/cps-table.component.html | 105 ++- .../cps-table/cps-table.component.scss | 27 +- .../cps-table/cps-table.component.spec.ts | 16 + .../cps-table/cps-table.component.ts | 15 +- .../cps-table-column-resizable.directive.ts | 5 + .../cps-table-header-selectable.directive.ts | 7 +- 17 files changed, 1122 insertions(+), 44 deletions(-) diff --git a/playwright/cps-ui-kit/components/cps-table.spec.ts b/playwright/cps-ui-kit/components/cps-table.spec.ts index a15c88b4a..77ad95b23 100644 --- a/playwright/cps-ui-kit/components/cps-table.spec.ts +++ b/playwright/cps-ui-kit/components/cps-table.spec.ts @@ -1,18 +1,623 @@ -import { test, expect } from '@playwright/test'; +import { test, expect, type Page, type Locator } from '@playwright/test'; import * as fs from 'fs'; import * as path from 'path'; +function example(page: Page, testId: string): Locator { + return page.getByTestId(testId); +} + +test.describe('cps-table', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/table'); + await page.addStyleTag({ + content: '* { scroll-behavior: auto !important; }' + }); + }); + + test.describe('Real sort on the default-rendered header (Table 2)', () => { + test('clicking a sortable+filterable header cycles real aria-sort through ascending/descending/none', async ({ + page + }) => { + await page.getByRole('tab', { name: 'Table 2', exact: true }).click(); + const wrapper = example(page, 'table-2-virtual-default-render'); + const header = wrapper.getByTestId('cps-table-header-cell-a'); + await expect(header).toBeVisible(); + + await expect(header).toHaveAttribute('aria-sort', 'none'); + await header.click({ position: { x: 5, y: 5 } }); + await expect(header).toHaveAttribute('aria-sort', 'ascending'); + await header.click({ position: { x: 5, y: 5 } }); + await expect(header).toHaveAttribute('aria-sort', 'descending'); + await header.click({ position: { x: 5, y: 5 } }); + await expect(header).toHaveAttribute('aria-sort', 'none'); + }); + }); + + test.describe('Real multi-sort badge (Table 1)', () => { + test('sorting a second column shows the real order-2 badge on its sort icon', async ({ + page + }) => { + const wrapper = example(page, 'table-1-paginated-filters'); + const headerA = wrapper.locator('th[cpstcolsortable="a"]'); + const headerC = wrapper.locator('th[cpstcolsortable="c"]'); + + await headerA.click({ position: { x: 5, y: 5 } }); + await headerC.click({ position: { x: 5, y: 5 } }); + + await expect(wrapper.getByTestId('cps-table-sort-icon-c')).toHaveText( + '2' + ); + await expect(headerA).toHaveAttribute('aria-sort', 'ascending'); + await expect(headerC).toHaveAttribute('aria-sort', 'ascending'); + }); + }); + + test.describe('Real per-column filtering (Table 1)', () => { + test('a text filter real-narrows the rendered rows', async ({ page }) => { + const wrapper = example(page, 'table-1-paginated-filters'); + + await wrapper.getByTestId('cps-table-col-filter-btn-a').click(); + const constraint = page.getByTestId( + 'cps-table-col-filter-constraint-a-0' + ); + await expect(constraint).toBeVisible(); + await constraint.locator('input').first().fill('a13'); + await page.getByTestId('cps-table-col-filter-apply-btn').click(); + + await expect(wrapper.locator('tbody tr')).toHaveCount(1); + await expect(wrapper.locator('tbody tr')).toHaveText(/A13/); + }); + }); + + test.describe('Real empty state (Table 8)', () => { + test('shows the configured message and real emptyBodyHeight layout, with no toolbar', async ({ + page + }) => { + await page.getByRole('tab', { name: 'Table 8', exact: true }).click(); + const wrapper = example(page, 'table-8-empty'); + const emptyMessage = wrapper.getByTestId('cps-table-empty-message'); + + await expect(emptyMessage).toBeVisible(); + await expect(emptyMessage).toHaveText('No records match your criteria'); + + const box = await emptyMessage.boundingBox(); + expect(box?.height).toBe(352); // 22rem + + await expect(wrapper.locator('.p-datatable-header')).toHaveCount(0); + }); + }); + + test.describe('Real loading state (Table 9)', () => { + test('shows aria-busy and a real visible loading spinner', async ({ + page + }) => { + await page.getByRole('tab', { name: 'Table 9', exact: true }).click(); + const wrapper = example(page, 'table-9-loading'); + + await expect(wrapper.getByTestId('cps-table')).toHaveAttribute( + 'aria-busy', + 'true' + ); + await expect( + wrapper + .getByTestId('cps-table-loading') + .getByTestId('cps-loader-spinner') + ).toBeVisible(); + }); + }); + + test.describe('Real HTML rendering and sanitization (Table 10)', () => { + test('safe HTML renders unescaped, and an injected