From bc525ae5b9283de7434f94b01998861f0693ef8a Mon Sep 17 00:00:00 2001 From: Andrei Fateev Date: Thu, 6 Aug 2026 12:52:56 +0200 Subject: [PATCH 1/2] test: add Playwright coverage and test ids for radio group --- .../components/cps-radio-group.spec.ts | 109 ++++++++++++++++++ .../radio-page/radio-page.component.html | 26 +++++ .../pages/radio-page/radio-page.component.ts | 20 ++-- .../pages/radio-page/radio-page.examples.ts | 40 +++++++ .../cps-radio-button.component.html | 6 +- .../cps-radio-group.component.html | 15 ++- .../cps-radio/cps-radio.component.ts | 3 +- 7 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 playwright/cps-ui-kit/components/cps-radio-group.spec.ts diff --git a/playwright/cps-ui-kit/components/cps-radio-group.spec.ts b/playwright/cps-ui-kit/components/cps-radio-group.spec.ts new file mode 100644 index 000000000..2226d4a1d --- /dev/null +++ b/playwright/cps-ui-kit/components/cps-radio-group.spec.ts @@ -0,0 +1,109 @@ +import { test, expect } from '@playwright/test'; + +test.describe('cps-radio-group', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/radio-group'); + }); + + test.describe('Real native keyboard navigation skips disabled radios', () => { + test('ArrowDown from an enabled radio real-skips a disabled one via native grouping', async ({ + page + }) => { + const group = page.getByRole('radiogroup', { + name: 'Radio group with partially disabled options and targeted tooltips' + }); + const option2 = group.getByRole('radio', { name: 'Option 2' }); + const option4 = group.getByRole('radio', { name: 'Option 4' }); + + await option2.focus(); + await page.keyboard.press('ArrowDown'); + + await expect(option4).toBeFocused(); + await expect(option4).toBeChecked(); + }); + }); + + test.describe('Real form validation shows and clears a real error', () => { + test('selecting the wrong option shows a real error; selecting the right one clears it', async ({ + page + }) => { + const group = page.getByRole('radiogroup', { + name: 'Radio group where 3rd option must be selected' + }); + const errorEl = group.getByTestId('cps-radio-group-error'); + + const option1 = group.getByRole('radio', { name: 'Option 1' }); + await option1.focus(); + await page.keyboard.press('Space'); + await page.evaluate(() => + (document.activeElement as HTMLElement)?.blur() + ); + + await expect(errorEl).toBeVisible(); + await expect(errorEl).toHaveText('Only third option must be selected'); + await expect(group).toHaveAttribute( + 'aria-describedby', + (await errorEl.getAttribute('id')) ?? '' + ); + + const option3 = group.getByRole('radio', { name: 'Option 3' }); + await option3.focus(); + await page.keyboard.press('Space'); + await page.evaluate(() => + (document.activeElement as HTMLElement)?.blur() + ); + + await expect(errorEl).toHaveCount(0); + }); + }); + + test.describe('Real inert blocks focus on unselected custom content', () => { + test('the nested control real-refuses focus until its radio is selected', async ({ + page + }) => { + const group = page.getByRole('radiogroup', { + name: 'Radio group with custom content' + }); + const customRadio = group.getByRole('radio', { + name: 'Custom option with inline selectors' + }); + const nestedCombobox = group.getByRole('combobox', { + name: 'Select day' + }); + + await expect(customRadio).not.toBeChecked(); + + await nestedCombobox.focus(); + await expect(nestedCombobox).not.toBeFocused(); + + await customRadio.click(); + await expect(customRadio).toBeChecked(); + + await nestedCombobox.focus(); + await expect(nestedCombobox).toBeFocused(); + }); + }); + + test.describe('Real hideDetails suppresses both a real hint and a real validation error', () => { + test('a hint stays real-absent untouched, and a real error stays real-absent once invalid', async ({ + page + }) => { + const group = page.getByRole('radiogroup', { + name: 'Required radio group with hidden details' + }); + + await expect(group.getByTestId('cps-radio-group-hint')).toHaveCount(0); + + const option1 = group.getByRole('radio', { name: 'Option 1' }); + await option1.focus(); + await page.keyboard.press('Space'); + await page.evaluate(() => + (document.activeElement as HTMLElement)?.blur() + ); + + await expect(group).toHaveAttribute('aria-invalid', 'true'); + await expect(group).not.toHaveAttribute('aria-describedby', /.+/); + await expect(group.getByTestId('cps-radio-group-error')).toHaveCount(0); + }); + }); +}); diff --git a/projects/composition/src/app/pages/radio-page/radio-page.component.html b/projects/composition/src/app/pages/radio-page/radio-page.component.html index 70fde3e68..047dc62ba 100644 --- a/projects/composition/src/app/pages/radio-page/radio-page.component.html +++ b/projects/composition/src/app/pages/radio-page/radio-page.component.html @@ -13,6 +13,21 @@ + +
+ + +
+
+ + + + + + + this._checkThirdSelected(control) + ]; this.form = this._formBuilder.group({ - requiredRadio: [ - '', - [ - Validators.required, - (control: AbstractControl): ValidationErrors | null => - this._checkThirdSelected(control) - ] - ] + requiredRadio: ['', requiredThirdValidators], + requiredRadioHidden: ['', requiredThirdValidators] }); } diff --git a/projects/composition/src/app/pages/radio-page/radio-page.examples.ts b/projects/composition/src/app/pages/radio-page/radio-page.examples.ts index 85bc6fde3..c5a0432e6 100644 --- a/projects/composition/src/app/pages/radio-page/radio-page.examples.ts +++ b/projects/composition/src/app/pages/radio-page/radio-page.examples.ts @@ -46,6 +46,36 @@ private _checkThirdSelected(control: AbstractControl): ValidationErrors | null { }` }, + requiredHiddenRadioGroup: { + html: ` +
+ + +
`, + ts: ` +${radioOptionsTs.trim()} + +form!: UntypedFormGroup; + +ngOnInit() { + this.form = this._formBuilder.group({ + requiredRadioHidden: [ + '', + [ + Validators.required, + (control: AbstractControl): ValidationErrors | null => + this._checkThirdSelected(control) + ] + ] + }); +}` + }, + tooltipRadioGroup: { html: ` +`, + ts: radioOptionsTs + }, + twoWayBindingRadioGroup: { html: `
diff --git a/projects/cps-ui-kit/src/lib/components/cps-radio-group/cps-radio-button/cps-radio-button.component.html b/projects/cps-ui-kit/src/lib/components/cps-radio-group/cps-radio-button/cps-radio-button.component.html index 451efc2f4..abb5bc63a 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-radio-group/cps-radio-button/cps-radio-button.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-radio-group/cps-radio-button/cps-radio-button.component.html @@ -1,6 +1,7 @@ @if (option.tooltip) {
@@ -13,7 +14,7 @@ ">
} @else { -
+
@if (!contentRef.innerHTML.trim() && option.label) {