diff --git a/core/src/components/action-sheet/action-sheet.tsx b/core/src/components/action-sheet/action-sheet.tsx index 223f9cb6653..89a11da4a9a 100644 --- a/core/src/components/action-sheet/action-sheet.tsx +++ b/core/src/components/action-sheet/action-sheet.tsx @@ -255,12 +255,9 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { private async buttonClick(button: ActionSheetButton) { const role = button.role; - if (isCancel(role)) { - return this.dismiss(button.data, role); - } const shouldDismiss = await this.callButtonHandler(button); - if (shouldDismiss) { - return this.dismiss(button.data, button.role); + if (isCancel(role) || shouldDismiss) { + return this.dismiss(button.data, role); } return Promise.resolve(); } @@ -289,9 +286,12 @@ export class ActionSheet implements ComponentInterface, OverlayInterface { }; private dispatchCancelHandler = (ev: CustomEvent) => { - const role = ev.detail.role; + const button = ev.detail; + const role = button.role; if (isCancel(role)) { - const cancelButton = this.getButtons().find((b) => b.role === 'cancel'); + const cancelButton = this.getButtons().find((b) => { + return b === button && b.role === 'cancel'; + }); this.callButtonHandler(cancelButton); } }; diff --git a/core/src/components/alert/alert.tsx b/core/src/components/alert/alert.tsx index 3d5a64200c3..caf5c918f12 100644 --- a/core/src/components/alert/alert.tsx +++ b/core/src/components/alert/alert.tsx @@ -466,12 +466,9 @@ export class Alert implements ComponentInterface, OverlayInterface { private async buttonClick(button: AlertButton) { const role = button.role; const values = this.getValues(); - if (isCancel(role)) { - return this.dismiss({ values }, role); - } const returnData = await this.callButtonHandler(button, values); - if (returnData !== false) { - return this.dismiss({ values, ...returnData }, button.role); + if (isCancel(role) || returnData !== false) { + return this.dismiss({ values, ...returnData }, role); } return false; } @@ -673,9 +670,12 @@ export class Alert implements ComponentInterface, OverlayInterface { }; private dispatchCancelHandler = (ev: CustomEvent) => { - const role = ev.detail.role; + const button = ev.detail; + const role = button.role; if (isCancel(role)) { - const cancelButton = this.processedButtons.find((b) => b.role === 'cancel'); + const cancelButton = this.processedButtons.find((b) => { + return b === button && b.role === 'cancel'; + }); this.callButtonHandler(cancelButton); } }; diff --git a/core/src/components/alert/test/basic/alert.e2e.ts b/core/src/components/alert/test/basic/alert.e2e.ts index eed39b22996..f9664109ef8 100644 --- a/core/src/components/alert/test/basic/alert.e2e.ts +++ b/core/src/components/alert/test/basic/alert.e2e.ts @@ -135,6 +135,50 @@ configs({ themes: ['light', 'dark'] }).forEach(({ config, screenshot, title }) = }); }); +configs().forEach(({ config, screenshot, title }) => { + test.describe(title('Test cancel buttons'), () => { + let alertFixture!: AlertFixture; + + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/alert/test/basic', config); + alertFixture = new AlertFixture(page, screenshot); + }); + + test('cancel button 1', async ({ page }) => { + await alertFixture.open('#multipleCancelButtons'); + const optBtn1 = page.locator('ion-alert button.cancel1-btn'); + await optBtn1.click(); + const confirmOptBtn1Alert = page.locator('ion-alert[data-testid=cancel1-btn-clicked]'); + const optBtn1AlertInfo = confirmOptBtn1Alert.locator('.alert-message').innerText(); + const optBtn1AlertOkBtn = confirmOptBtn1Alert.locator('.alert-button-group button'); + expect(await optBtn1AlertInfo).toBe('cancel1-btn-clicked'); + await alertFixture.screenshot('alertCancelBtn1'); + await optBtn1AlertOkBtn.click(); + }); + + test('cancel button 2', async ({ page }) => { + await alertFixture.open('#multipleCancelButtons'); + const optBtn2 = page.locator('ion-alert button.cancel2-btn'); + await optBtn2.click(); + const confirmOptBtn2Alert = page.locator('ion-alert[data-testid=cancel2-btn-clicked]'); + const optBtn2AlertInfo = confirmOptBtn2Alert.locator('.alert-message').innerText(); + const optBtn2AlertOkBtn = confirmOptBtn2Alert.locator('.alert-button-group button'); + expect(await optBtn2AlertInfo).toBe('cancel2-btn-clicked'); + await alertFixture.screenshot('alertCancelBtn2'); + await optBtn2AlertOkBtn.click(); + }); + + test('cancel button 3', async ({ page }) => { + await alertFixture.open('#multipleCancelButtons'); + const ionAlertDidDismiss = await page.spyOnEvent('ionAlertDidDismiss'); + const optBtn3 = page.locator('ion-alert button.cancel3-btn'); + await optBtn3.click(); + await ionAlertDidDismiss.next(); + expect(ionAlertDidDismiss).toHaveReceivedEventDetail({ data: { values: undefined }, role: 'cancel' }); + }); + }); +}); + class AlertFixture { readonly page: E2EPage; readonly screenshotFn?: (file: string) => string; diff --git a/core/src/components/alert/test/basic/index.html b/core/src/components/alert/test/basic/index.html index 74a6f6c68d2..9fd07b39f05 100644 --- a/core/src/components/alert/test/basic/index.html +++ b/core/src/components/alert/test/basic/index.html @@ -31,6 +31,9 @@ + @@ -115,6 +118,50 @@ }); } + function confirmAlertCancelBtns(expectedDataTestId) { + openAlert({ + header: 'Alert', + subHeader: 'CancelBtnClicked', + message: expectedDataTestId, + buttons: ['OK'], + htmlAttributes: { + 'data-testid': expectedDataTestId, + }, + }); + } + + function presentAlertMultipleCancelButtons() { + openAlert({ + header: 'Alert', + subHeader: 'Subtitle', + message: 'This is an alert message.', + buttons: [ + { + text: 'Cancel 1', + role: 'cancel', + cssClass: 'cancel1-btn', + handler: () => { + confirmAlertCancelBtns('cancel1-btn-clicked'); + }, + }, + { + text: 'Cancel 2', + role: 'cancel', + cssClass: 'cancel2-btn', + handler: () => { + confirmAlertCancelBtns('cancel2-btn-clicked'); + }, + }, + { + text: 'Cancel 3', + role: 'cancel', + cssClass: 'cancel3-btn', + }, + 'Cancel', + ], + }); + } + function presentAlertNoMessage() { openAlert({ header: 'Alert', diff --git a/core/src/components/picker/picker.tsx b/core/src/components/picker/picker.tsx index 88e25e3efa1..e63630b268b 100644 --- a/core/src/components/picker/picker.tsx +++ b/core/src/components/picker/picker.tsx @@ -299,12 +299,9 @@ export class Picker implements ComponentInterface, OverlayInterface { private async buttonClick(button: PickerButton) { const role = button.role; - if (isCancel(role)) { - return this.dismiss(undefined, role); - } const shouldDismiss = await this.callButtonHandler(button); - if (shouldDismiss) { - return this.dismiss(this.getSelected(), button.role); + if (isCancel(role) || shouldDismiss) { + return this.dismiss(this.getSelected(), role); } return Promise.resolve(); } @@ -340,9 +337,12 @@ export class Picker implements ComponentInterface, OverlayInterface { }; private dispatchCancelHandler = (ev: CustomEvent) => { - const role = ev.detail.role; + const button = ev.detail; + const role = button.role; if (isCancel(role)) { - const cancelButton = this.buttons.find((b) => b.role === 'cancel'); + const cancelButton = this.buttons.find((b) => { + return b === button && b.role === 'cancel'; + }); this.callButtonHandler(cancelButton); } }; diff --git a/core/src/components/picker/test/basic/picker.e2e.ts b/core/src/components/picker/test/basic/picker.e2e.ts index a2cb087e535..62c982d650f 100644 --- a/core/src/components/picker/test/basic/picker.e2e.ts +++ b/core/src/components/picker/test/basic/picker.e2e.ts @@ -26,3 +26,54 @@ configs().forEach(({ title, screenshot, config }) => { }); }); }); + +configs({ modes: ['md'], directions: ['ltr', 'rtl'] }).forEach(({ title, config }) => { + test.describe(title('picker: button handlers'), () => { + test('should call cancel button handler', async ({ page }) => { + await page.setContent( + ` + + + + +
+ + + `, + config + ); + + const didPresent = await page.spyOnEvent('ionPickerDidPresent'); + + const openBtn = page.locator('#open'); + await openBtn.click(); + + await didPresent.next(); + + const cancelBtn = page.locator('ion-picker .cancel-btn'); + await cancelBtn.click(); + + const result = page.locator('#result'); + await expect(result).toHaveText('cancelled'); + }); + }); +}); diff --git a/core/src/components/toast/test/buttons/index.html b/core/src/components/toast/test/buttons/index.html index afc47f1337b..397dc6a340e 100644 --- a/core/src/components/toast/test/buttons/index.html +++ b/core/src/components/toast/test/buttons/index.html @@ -14,7 +14,8 @@ @@ -35,6 +36,10 @@ Multiple Buttons + + Multiple Cancel Buttons + + Really Long Button @@ -45,10 +50,16 @@ window.addEventListener('ionToastDidDismiss', function (e) { console.log('didDismiss', e); }); + window.addEventListener('ionToastWillDismiss', function (e) { console.log('willDismiss', e); }); + async function openAlert(opts) { + const alert = await alertController.create(opts); + await alert.present(); + } + async function presentCloseArrayToast() { const opts = { message: 'Are you sure you want to delete this?', @@ -118,6 +129,50 @@ return await presentToastWithOptions(opts); } + function confirmAlertCancelBtns(expectedDataTestId) { + openAlert({ + header: 'Alert', + subHeader: 'CancelBtnClicked', + message: expectedDataTestId, + buttons: ['OK'], + htmlAttributes: { + 'data-testid': expectedDataTestId, + }, + }); + } + + async function presentToastWithMultipleCancelButtons() { + const opts = { + message: 'Warning.', + buttons: [ + { + text: 'C1', + role: 'cancel', + cssClass: 'cancel1-btn', + handler: () => { + confirmAlertCancelBtns('cancel1-btn-clicked'); + }, + }, + { + text: 'C2', + role: 'cancel', + cssClass: 'cancel2-btn', + handler: () => { + confirmAlertCancelBtns('cancel2-btn-clicked'); + }, + }, + { + text: 'C3', + role: 'cancel', + cssClass: 'cancel3-btn', + }, + 'Cancel', + ], + }; + + return await presentToastWithOptions(opts); + } + async function presentToastWithLongButton() { const opts = { message: 'This item already has the label "travel". You can add a new label.', diff --git a/core/src/components/toast/test/buttons/toast.e2e.ts b/core/src/components/toast/test/buttons/toast.e2e.ts new file mode 100644 index 00000000000..bc6ce55b112 --- /dev/null +++ b/core/src/components/toast/test/buttons/toast.e2e.ts @@ -0,0 +1,89 @@ +import type { Locator } from '@playwright/test'; +import { expect } from '@playwright/test'; +import type { E2EPage, E2EPageOptions, ScreenshotFn, EventSpy } from '@utils/test/playwright'; +import { configs, test } from '@utils/test/playwright'; + +class ToastFixture { + readonly page: E2EPage; + + private ionToastDidPresent!: EventSpy; + + constructor(page: E2EPage) { + this.page = page; + } + + async goto(config: E2EPageOptions) { + const { page } = this; + await page.goto(`/src/components/toast/test/buttons`, config); + this.ionToastDidPresent = await page.spyOnEvent('ionToastDidPresent'); + } + + async openToast(selector: string) { + const { page, ionToastDidPresent } = this; + const button = page.locator(selector); + await button.click(); + + await ionToastDidPresent.next(); + + return { + toast: page.locator('ion-toast'), + container: page.locator('ion-toast .toast-container'), + }; + } + + async screenshot(screenshotModifier: string, screenshot: ScreenshotFn, el?: Locator) { + const { page } = this; + + const screenshotString = screenshot(`toast-${screenshotModifier}`); + + if (el === undefined) { + await expect(page).toHaveScreenshot(screenshotString); + } else { + await expect(el).toHaveScreenshot(screenshotString); + } + } +} + +configs().forEach(({ title, screenshot, config }) => { + test.describe(title('Test cancel buttons'), () => { + let toastFixture: ToastFixture; + + test.beforeEach(async ({ page }) => { + toastFixture = new ToastFixture(page); + await toastFixture.goto(config); + }); + + test('cancel button 1', async ({ page }) => { + await toastFixture.openToast('#multipleCancelButtons'); + const optBtn1 = page.locator('ion-toast button.cancel1-btn'); + await optBtn1.click(); + const confirmOptBtn1Alert = page.locator('ion-alert[data-testid=cancel1-btn-clicked]'); + const optBtn1AlertInfo = confirmOptBtn1Alert.locator('.alert-message').innerText(); + const optBtn1AlertOkBtn = confirmOptBtn1Alert.locator('.alert-button-group button'); + expect(await optBtn1AlertInfo).toBe('cancel1-btn-clicked'); + await toastFixture.screenshot('toastCancelBtn1', screenshot); + await optBtn1AlertOkBtn.click(); + }); + + test('cancel button 2', async ({ page }) => { + await toastFixture.openToast('#multipleCancelButtons'); + const optBtn2 = page.locator('ion-toast button.cancel2-btn'); + await optBtn2.click(); + const confirmOptBtn2Alert = page.locator('ion-alert[data-testid=cancel2-btn-clicked]'); + const optBtn2AlertInfo = confirmOptBtn2Alert.locator('.alert-message').innerText(); + const optBtn2AlertOkBtn = confirmOptBtn2Alert.locator('.alert-button-group button'); + expect(await optBtn2AlertInfo).toBe('cancel2-btn-clicked'); + await toastFixture.screenshot('toastCancelBtn2', screenshot); + await optBtn2AlertOkBtn.click(); + }); + + test('cancel button 3', async ({ page }) => { + await toastFixture.openToast('#multipleCancelButtons'); + const ionToastDidDismiss = await page.spyOnEvent('ionToastDidDismiss'); + const optBtn3 = page.locator('ion-toast button.cancel3-btn'); + await optBtn3.click(); + await ionToastDidDismiss.next(); + expect(ionToastDidDismiss).toHaveReceivedEventDetail({ data: undefined, role: 'cancel' }); + }); + }); +}); diff --git a/core/src/components/toast/toast.tsx b/core/src/components/toast/toast.tsx index a0938be7d60..e0aed8fc3d6 100644 --- a/core/src/components/toast/toast.tsx +++ b/core/src/components/toast/toast.tsx @@ -526,11 +526,8 @@ export class Toast implements ComponentInterface, OverlayInterface { private async buttonClick(button: ToastButton) { const role = button.role; - if (isCancel(role)) { - return this.dismiss(undefined, role); - } const shouldDismiss = await this.callButtonHandler(button); - if (shouldDismiss) { + if (isCancel(role) || shouldDismiss) { return this.dismiss(undefined, role); } return Promise.resolve(); @@ -554,9 +551,12 @@ export class Toast implements ComponentInterface, OverlayInterface { } private dispatchCancelHandler = (ev: CustomEvent) => { - const role = ev.detail.role; + const button = ev.detail; + const role = button.role; if (isCancel(role)) { - const cancelButton = this.getButtons().find((b) => b.role === 'cancel'); + const cancelButton = this.getButtons().find((b) => { + return b === button && b.role === 'cancel'; + }); this.callButtonHandler(cancelButton); } };