test(radio-group): migrate tests to playwright (#25713)
@@ -22,161 +22,35 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="outer-content">
|
||||
<p class="ion-text-center">
|
||||
<ion-button onClick="addRadio()">Add Radio</ion-button>
|
||||
<ion-button onClick="addChecked()">Add Checked</ion-button>
|
||||
<ion-button id="removeButton" onClick="removeRadio()">Remove Radio</ion-button>
|
||||
</p>
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<ion-radio-group id="dynamicDisabled" disabled name="tannen" id="group" value="biff">
|
||||
<ion-radio-group name="items" id="group" value="1">
|
||||
<ion-list-header>
|
||||
<ion-label>Luckiest Man On Earth <span id="group-value"></span></ion-label>
|
||||
<ion-label>Radio Group Header</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<ion-label
|
||||
>Biff
|
||||
<span id="biff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="biff" slot="start"></ion-radio>
|
||||
<ion-label>Item 1</ion-label>
|
||||
<ion-radio value="1" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label
|
||||
>Griff
|
||||
<span id="griff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="griff" slot="start"></ion-radio>
|
||||
<ion-label>Item 2</ion-label>
|
||||
<ion-radio value="2" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label
|
||||
>Buford
|
||||
<span id="buford"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="buford" slot="start"></ion-radio>
|
||||
<ion-label>Item 3</ion-label>
|
||||
<ion-radio value="3" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>George</ion-label>
|
||||
<ion-radio value="george" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item-divider>
|
||||
<ion-label> Maintenance Drone </ion-label>
|
||||
</ion-item-divider>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie"></ion-radio>
|
||||
<ion-label>Item 4</ion-label>
|
||||
<ion-radio value="4" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
<style>
|
||||
.outer-content {
|
||||
--background: #f2f2f2;
|
||||
}
|
||||
|
||||
ion-list {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let count = 0;
|
||||
const removeButton = document.querySelector('#removeButton');
|
||||
|
||||
const valueEl = document.querySelector('#group-value');
|
||||
const group = document.querySelector('ion-radio-group');
|
||||
|
||||
group.addEventListener('ionChange', (ev) => {
|
||||
valueEl.textContent = group.value;
|
||||
});
|
||||
|
||||
customElements
|
||||
.whenDefined('ion-radio-group')
|
||||
.then(() => group.componentOnReady && group.componentOnReady())
|
||||
.then(() => {
|
||||
valueEl.textContent = group.value;
|
||||
});
|
||||
|
||||
function addRadio() {
|
||||
const item = document.createElement('ion-item');
|
||||
|
||||
item.innerHTML = `
|
||||
<ion-label>Item ${count}</ion-label>
|
||||
<ion-radio value="item-${count}" slot="start"></ion-radio>
|
||||
`;
|
||||
group.appendChild(item);
|
||||
count++;
|
||||
|
||||
removeButton.disabled = false;
|
||||
}
|
||||
|
||||
function addChecked() {
|
||||
const item = document.createElement('ion-item');
|
||||
item.innerHTML = `
|
||||
<ion-label>Item ${count}</ion-label>
|
||||
<ion-radio value="item-${count}" slot="start"></ion-radio>
|
||||
`;
|
||||
group.appendChild(item);
|
||||
|
||||
group.value = `item-${count}`;
|
||||
count++;
|
||||
|
||||
removeButton.disabled = false;
|
||||
}
|
||||
|
||||
function removeRadio() {
|
||||
const removeEl = group.children[group.children.length - 1];
|
||||
|
||||
if (removeEl && removeEl.tagName === 'ION-ITEM') {
|
||||
removeEl.remove();
|
||||
|
||||
// No more radios to remove, disable button
|
||||
if (!group.querySelector('ion-item')) {
|
||||
removeButton.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
110
core/src/components/radio-group/test/basic/radio-group.e2e.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { Locator } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('radio-group: basic', () => {
|
||||
test('should not have visual regressions', async ({ page }) => {
|
||||
await page.goto(`/src/components/radio-group/test/basic`);
|
||||
|
||||
const list = page.locator('ion-list');
|
||||
|
||||
expect(await list.screenshot()).toMatchSnapshot(`radio-group-diff-${page.getSnapshotSettings()}.png`);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('radio-group: interaction', () => {
|
||||
let radioFixture: RadioFixture;
|
||||
|
||||
test.beforeEach(({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.');
|
||||
radioFixture = new RadioFixture(page);
|
||||
});
|
||||
|
||||
test('spacebar should not deselect without allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('keyboard');
|
||||
await radioFixture.expectChecked(true);
|
||||
});
|
||||
|
||||
test('spacebar should deselect with allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('keyboard');
|
||||
await radioFixture.expectChecked(false);
|
||||
});
|
||||
|
||||
test('click should not deselect without allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('mouse');
|
||||
await radioFixture.expectChecked(true);
|
||||
});
|
||||
|
||||
test('click should deselect with allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('mouse');
|
||||
await radioFixture.expectChecked(false);
|
||||
});
|
||||
});
|
||||
|
||||
class RadioFixture {
|
||||
readonly page: E2EPage;
|
||||
|
||||
private radio!: Locator;
|
||||
|
||||
constructor(page: E2EPage) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
|
||||
const { page } = this;
|
||||
const radio = (this.radio = page.locator(selector));
|
||||
|
||||
if (method === 'keyboard') {
|
||||
await radio.focus();
|
||||
await page.keyboard.press('Space');
|
||||
} else {
|
||||
await radio.click();
|
||||
}
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
return radio;
|
||||
}
|
||||
|
||||
async expectChecked(state: boolean) {
|
||||
const { radio } = this;
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 28 KiB |
34
core/src/components/radio-group/test/form/radio-group.e2e.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('radio-group: form', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/src/components/radio-group/test/form');
|
||||
});
|
||||
|
||||
test('selecting an option should update the value', async ({ page }) => {
|
||||
const radioGroup = page.locator('ion-radio-group');
|
||||
const ionChange = await page.spyOnEvent('ionChange');
|
||||
const griffRadio = page.locator('ion-radio[value="griff"]');
|
||||
|
||||
await expect(radioGroup).toHaveAttribute('value', 'biff');
|
||||
|
||||
await griffRadio.click();
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(ionChange).toHaveReceivedEventDetail({ value: 'griff' });
|
||||
});
|
||||
|
||||
test('selecting a disabled option should not update the value', async ({ page }) => {
|
||||
const value = page.locator('#value');
|
||||
const disabledRadio = page.locator('ion-radio[value="george"]');
|
||||
|
||||
await expect(value).toHaveText('');
|
||||
await expect(disabledRadio).toHaveAttribute('disabled', '');
|
||||
|
||||
await disabledRadio.click({ force: true });
|
||||
await page.waitForChanges();
|
||||
|
||||
await expect(value).toHaveText('');
|
||||
});
|
||||
});
|
||||
@@ -1,133 +0,0 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import type { Locator } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
import type { E2EPage } from '@utils/test/playwright';
|
||||
|
||||
test.describe('radio-group', () => {
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
test.beforeEach(({}, testInfo) => {
|
||||
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.');
|
||||
});
|
||||
|
||||
test.describe('radio-group: interaction', () => {
|
||||
let radioFixture: RadioFixture;
|
||||
|
||||
test.beforeEach(({ page }) => {
|
||||
radioFixture = new RadioFixture(page);
|
||||
});
|
||||
|
||||
test('spacebar should not deselect without allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('keyboard');
|
||||
await radioFixture.expectChecked(true);
|
||||
});
|
||||
|
||||
test('spacebar should deselect with allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('keyboard');
|
||||
await radioFixture.expectChecked(false);
|
||||
});
|
||||
|
||||
test('click should not deselect without allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="false">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('mouse');
|
||||
await radioFixture.expectChecked(true);
|
||||
});
|
||||
|
||||
test('click should deselect with allowEmptySelection', async ({ page }) => {
|
||||
await page.setContent(`
|
||||
<ion-radio-group value="one" allow-empty-selection="true">
|
||||
<ion-item>
|
||||
<ion-label>One</ion-label>
|
||||
<ion-radio id="one" value="one"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
`);
|
||||
|
||||
await radioFixture.checkRadio('mouse');
|
||||
await radioFixture.expectChecked(false);
|
||||
});
|
||||
});
|
||||
test.describe('radio-group: state', () => {
|
||||
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
|
||||
await page.goto('/src/components/radio-group/test/search');
|
||||
|
||||
const radioGroup = page.locator('ion-radio-group');
|
||||
const radio = page.locator('ion-radio[value=two]');
|
||||
|
||||
// select radio
|
||||
await radio.click();
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
|
||||
|
||||
// filter radio so it is not in DOM
|
||||
await page.fill('ion-searchbar input', 'zero');
|
||||
await page.waitForChanges();
|
||||
expect(radio).toBeHidden();
|
||||
|
||||
// ensure radio group has the same value
|
||||
expect(radioGroup).toHaveJSProperty('value', 'two');
|
||||
|
||||
// clear the search so the radio appears
|
||||
await page.fill('ion-searchbar input', '');
|
||||
await page.waitForChanges();
|
||||
|
||||
// ensure that the new radio instance is still checked
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class RadioFixture {
|
||||
readonly page: E2EPage;
|
||||
|
||||
private radio!: Locator;
|
||||
|
||||
constructor(page: E2EPage) {
|
||||
this.page = page;
|
||||
}
|
||||
|
||||
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
|
||||
const { page } = this;
|
||||
const radio = (this.radio = page.locator(selector));
|
||||
|
||||
if (method === 'keyboard') {
|
||||
await radio.focus();
|
||||
await page.keyboard.press('Space');
|
||||
} else {
|
||||
await radio.click();
|
||||
}
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
return radio;
|
||||
}
|
||||
|
||||
async expectChecked(state: boolean) {
|
||||
const { radio } = this;
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { expect } from '@playwright/test';
|
||||
import { test } from '@utils/test/playwright';
|
||||
|
||||
test.describe('radio-group', () => {
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
test.skip(testInfo.project.metadata.rtl === true, 'This does not test LTR vs RTL logic.');
|
||||
await page.goto('/src/components/radio-group/test/search');
|
||||
});
|
||||
|
||||
test.describe('radio-group: state', () => {
|
||||
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
|
||||
const radioGroup = page.locator('ion-radio-group');
|
||||
const radio = page.locator('ion-radio[value=two]');
|
||||
|
||||
// select radio
|
||||
await radio.click();
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
|
||||
|
||||
// filter radio so it is not in DOM
|
||||
await page.fill('ion-searchbar input', 'zero');
|
||||
await page.waitForChanges();
|
||||
expect(radio).toBeHidden();
|
||||
|
||||
// ensure radio group has the same value
|
||||
expect(radioGroup).toHaveJSProperty('value', 'two');
|
||||
|
||||
// clear the search so the radio appears
|
||||
await page.fill('ion-searchbar input', '');
|
||||
await page.waitForChanges();
|
||||
|
||||
// ensure that the new radio instance is still checked
|
||||
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
|
||||
});
|
||||
});
|
||||
});
|
||||