From 4f4e9d92aa8dc8786cb395594bc16797b56a9e86 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 19 Sep 2025 12:48:04 -0700 Subject: [PATCH 1/5] fix(vue): add component-specific overlay events --- packages/vue/src/utils/overlays.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/vue/src/utils/overlays.ts b/packages/vue/src/utils/overlays.ts index 91133e3490a..e076d9da2e4 100644 --- a/packages/vue/src/utils/overlays.ts +++ b/packages/vue/src/utils/overlays.ts @@ -1,5 +1,5 @@ -import type { VNode, ComponentOptions } from "vue"; -import { defineComponent, h, ref, onMounted } from "vue"; +import type { ComponentOptions, VNode } from "vue"; +import { defineComponent, h, onMounted, ref } from "vue"; // TODO(FW-2969): types @@ -147,23 +147,30 @@ export const defineOverlayContainer = ( const elementRef = ref(); onMounted(() => { + // Convert name from kebab-case to camelCase + const componentName = name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); elementRef.value.addEventListener("ionMount", (ev: Event) => { emit("ionMount", ev); + emit(componentName + "IonMount", ev); isOpen.value = true; }); elementRef.value.addEventListener("willPresent", (ev: Event) => { emit("willPresent", ev); + emit(componentName + "WillPresent", ev); isOpen.value = true; }); elementRef.value.addEventListener("didDismiss", (ev: Event) => { emit("didDismiss", ev); + emit(componentName + "DidDismiss", ev); isOpen.value = false; }); elementRef.value.addEventListener("willDismiss", (ev: Event) => { emit("willDismiss", ev); + emit(componentName + "WillDismiss", ev); }); elementRef.value.addEventListener("didPresent", (ev: Event) => { emit("didPresent", ev); + emit(componentName + "DidPresent", ev); }); }); From 1b1988c92700760110735798fe9467bd992717ef Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 19 Sep 2025 12:56:04 -0700 Subject: [PATCH 2/5] fix(overlays): lint --- packages/vue/src/utils/overlays.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/vue/src/utils/overlays.ts b/packages/vue/src/utils/overlays.ts index e076d9da2e4..b917e750541 100644 --- a/packages/vue/src/utils/overlays.ts +++ b/packages/vue/src/utils/overlays.ts @@ -148,7 +148,9 @@ export const defineOverlayContainer = ( onMounted(() => { // Convert name from kebab-case to camelCase - const componentName = name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); + const componentName = name.replace(/-([a-z])/g, (_, letter) => + letter.toUpperCase() + ); elementRef.value.addEventListener("ionMount", (ev: Event) => { emit("ionMount", ev); emit(componentName + "IonMount", ev); From 7b0e1c589afa340578ceb16a686435dee7011ca0 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Fri, 19 Sep 2025 13:56:12 -0700 Subject: [PATCH 3/5] test(overlays): improving overlay tests --- packages/vue/test/base/src/views/Overlays.vue | 24 ++++++ .../test/base/tests/e2e/specs/overlays.cy.js | 73 +++++++++++++++++-- 2 files changed, 91 insertions(+), 6 deletions(-) diff --git a/packages/vue/test/base/src/views/Overlays.vue b/packages/vue/test/base/src/views/Overlays.vue index 49b956e0ddd..6d9af45623d 100644 --- a/packages/vue/test/base/src/views/Overlays.vue +++ b/packages/vue/test/base/src/views/Overlays.vue @@ -64,6 +64,10 @@ Modal onDidPresent:
{{ didPresent }}

Modal onWillDismiss:
{{ willDismiss }}

Modal onDidDismiss:
{{ didDismiss }}

+ Modal ionModalWillPresent:
{{ ionModalWillPresent }}

+ Modal ionModalDidPresent:
{{ ionModalDidPresent }}

+ Modal ionModalWillDismiss:
{{ ionModalWillDismiss }}

+ Modal ionModalDidDismiss:
{{ ionModalDidDismiss }}

@@ -335,21 +343,37 @@ export default defineComponent({ const didPresent = ref(0); const willDismiss = ref(0); const didDismiss = ref(0); + const ionModalWillPresent = ref(0); + const ionModalDidPresent = ref(0); + const ionModalWillDismiss = ref(0); + const ionModalDidDismiss = ref(0); const onModalWillPresent = () => willPresent.value += 1; const onModalDidPresent = () => { didPresent.value += 1; setModalRef(true); } const onModalWillDismiss = () => willDismiss.value += 1; const onModalDidDismiss = () => { didDismiss.value += 1; setModalRef(false); } + const onModalIonWillPresent = () => ionModalWillPresent.value += 1; + const onModalIonDidPresent = () => ionModalDidPresent.value += 1; + const onModalIonWillDismiss = () => ionModalWillDismiss.value += 1; + const onModalIonDidDismiss = () => ionModalDidDismiss.value += 1; return { onModalWillPresent, onModalDidPresent, onModalWillDismiss, onModalDidDismiss, + onModalIonWillPresent, + onModalIonDidPresent, + onModalIonWillDismiss, + onModalIonDidDismiss, willPresent, didPresent, willDismiss, didDismiss, + ionModalWillPresent, + ionModalDidPresent, + ionModalWillDismiss, + ionModalDidDismiss, changeLoadingProps, overlayProps, present, diff --git a/packages/vue/test/base/tests/e2e/specs/overlays.cy.js b/packages/vue/test/base/tests/e2e/specs/overlays.cy.js index 79f4ff4efb1..8badc32e6aa 100644 --- a/packages/vue/test/base/tests/e2e/specs/overlays.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/overlays.cy.js @@ -1,7 +1,11 @@ const testController = (overlay, shadow = false) => { const selector = `.${overlay}-controller`; - cy.get(`ion-radio#${overlay}`).click(); - cy.get('ion-radio#controller').click(); + cy.get(`ion-radio#${overlay}`) + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); + cy.get('ion-radio#controller') + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); cy.get('ion-button#present-overlay').click(); cy.get(selector).should('exist').should('be.visible'); @@ -16,8 +20,12 @@ const testController = (overlay, shadow = false) => { } const testComponent = (overlay, shadow = false) => { - cy.get(`ion-radio#${overlay}`).click(); - cy.get('ion-radio#component').click(); + cy.get(`ion-radio#${overlay}`) + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); + cy.get('ion-radio#component') + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); cy.get('ion-button#present-overlay').click(); cy.get(overlay).should('exist').should('be.visible'); @@ -40,8 +48,12 @@ const testComponent = (overlay, shadow = false) => { } const testInlineOverlay = (overlay, shadow = false) => { - cy.get(`ion-radio#${overlay}`).click(); - cy.get('ion-radio#component').click(); + cy.get(`ion-radio#${overlay}`) + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); + cy.get('ion-radio#component') + .scrollIntoView({ offset: { top: -100, left: 0 } }) + .click({ force: true }); cy.get('ion-button#present-overlay').click(); cy.get(overlay).should('exist').should('be.visible'); @@ -214,6 +226,49 @@ describe('Overlays', () => { }); }); + it('should fire long-form lifecycle events on overlays', () => { + cy.get('ion-radio#ion-modal').click(); + cy.get('ion-radio#component').click(); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLongLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 0, + didDismiss: 0 + }); + + cy.get('ion-modal #dismiss').click(); + + testLongLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLongLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-modal #dismiss').click(); + + testLongLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 2, + didDismiss: 2 + }); + }); + it('should unmount modal via component', () => { cy.get('ion-radio#ion-modal').click(); cy.get('ion-radio#component').click(); @@ -260,3 +315,9 @@ const testLifecycle = (selector, expected = {}) => { cy.get(`[data-pageid=${selector}] #didDismiss`).should('have.text', expected.didDismiss); } +const testLongLifecycle = (selector, expected = {}) => { + cy.get(`[data-pageid=${selector}] #ionModalWillPresent`).should('have.text', expected.willPresent); + cy.get(`[data-pageid=${selector}] #ionModalDidPresent`).should('have.text', expected.didPresent); + cy.get(`[data-pageid=${selector}] #ionModalWillDismiss`).should('have.text', expected.willDismiss); + cy.get(`[data-pageid=${selector}] #ionModalDidDismiss`).should('have.text', expected.didDismiss); +} From e585c66adab0482ef5486cc55110de94f8df562e Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 23 Sep 2025 09:50:34 -0700 Subject: [PATCH 4/5] test(vue): adding controller variants of overlay event tests --- packages/vue/test/base/src/views/Overlays.vue | 13 +++ .../test/base/tests/e2e/specs/overlays.cy.js | 86 +++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/packages/vue/test/base/src/views/Overlays.vue b/packages/vue/test/base/src/views/Overlays.vue index 6d9af45623d..384a5434f4f 100644 --- a/packages/vue/test/base/src/views/Overlays.vue +++ b/packages/vue/test/base/src/views/Overlays.vue @@ -274,6 +274,19 @@ export default defineComponent({ const openModal = async () => { const modal = await modalController.create({ cssClass: "ion-modal-controller", component: ModalContent, componentProps: overlayProps }); + + // Attach lifecycle listeners for controller-created modal + modal.addEventListener('willPresent', () => { willPresent.value += 1; }); + modal.addEventListener('didPresent', () => { didPresent.value += 1; }); + modal.addEventListener('willDismiss', () => { willDismiss.value += 1; }); + modal.addEventListener('didDismiss', () => { didDismiss.value += 1; }); + + // Long-form event names + modal.addEventListener('ionModalWillPresent', () => { ionModalWillPresent.value += 1; }); + modal.addEventListener('ionModalDidPresent', () => { ionModalDidPresent.value += 1; }); + modal.addEventListener('ionModalWillDismiss', () => { ionModalWillDismiss.value += 1; }); + modal.addEventListener('ionModalDidDismiss', () => { ionModalDidDismiss.value += 1; }); + await modal.present(); } diff --git a/packages/vue/test/base/tests/e2e/specs/overlays.cy.js b/packages/vue/test/base/tests/e2e/specs/overlays.cy.js index 8badc32e6aa..8d34036799b 100644 --- a/packages/vue/test/base/tests/e2e/specs/overlays.cy.js +++ b/packages/vue/test/base/tests/e2e/specs/overlays.cy.js @@ -269,6 +269,92 @@ describe('Overlays', () => { }); }); + it('should fire lifecycle events on controller overlays', () => { + cy.get('ion-radio#ion-modal').click(); + cy.get('ion-radio#controller').click(); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 0, + didDismiss: 0 + }); + + cy.get('ion-modal #dismiss').click(); + + testLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-modal #dismiss').click(); + + testLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 2, + didDismiss: 2 + }); + }); + + it('should fire long-form lifecycle events on controller overlays', () => { + cy.get('ion-radio#ion-modal').click(); + cy.get('ion-radio#controller').click(); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLongLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 0, + didDismiss: 0 + }); + + cy.get('ion-modal #dismiss').click(); + + testLongLifecycle('overlays', { + willPresent: 1, + didPresent: 1, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-button#present-overlay').click(); + cy.get('ion-modal').should('exist'); + + testLongLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 1, + didDismiss: 1 + }); + + cy.get('ion-modal #dismiss').click(); + + testLongLifecycle('overlays', { + willPresent: 2, + didPresent: 2, + willDismiss: 2, + didDismiss: 2 + }); + }); + it('should unmount modal via component', () => { cy.get('ion-radio#ion-modal').click(); cy.get('ion-radio#component').click(); From aba8025d69a83d7cb6e6bc1ab8a42ff8f210ad47 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 23 Sep 2025 09:56:48 -0700 Subject: [PATCH 5/5] test(vue): basic variable name cleanup --- packages/vue/test/base/src/views/Overlays.vue | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vue/test/base/src/views/Overlays.vue b/packages/vue/test/base/src/views/Overlays.vue index 384a5434f4f..0b382b94b47 100644 --- a/packages/vue/test/base/src/views/Overlays.vue +++ b/packages/vue/test/base/src/views/Overlays.vue @@ -102,10 +102,10 @@ @didPresent="onModalDidPresent" @willDismiss="onModalWillDismiss" @didDismiss="onModalDidDismiss" - @ionModalWillPresent="onModalIonWillPresent" - @ionModalDidPresent="onModalIonDidPresent" - @ionModalWillDismiss="onModalIonWillDismiss" - @ionModalDidDismiss="onModalIonDidDismiss" + @ionModalWillPresent="onIonModalWillPresent" + @ionModalDidPresent="onIonModalDidPresent" + @ionModalWillDismiss="onIonModalWillDismiss" + @ionModalDidDismiss="onIonModalDidDismiss" > @@ -365,20 +365,20 @@ export default defineComponent({ const onModalDidPresent = () => { didPresent.value += 1; setModalRef(true); } const onModalWillDismiss = () => willDismiss.value += 1; const onModalDidDismiss = () => { didDismiss.value += 1; setModalRef(false); } - const onModalIonWillPresent = () => ionModalWillPresent.value += 1; - const onModalIonDidPresent = () => ionModalDidPresent.value += 1; - const onModalIonWillDismiss = () => ionModalWillDismiss.value += 1; - const onModalIonDidDismiss = () => ionModalDidDismiss.value += 1; + const onIonModalWillPresent = () => ionModalWillPresent.value += 1; + const onIonModalDidPresent = () => ionModalDidPresent.value += 1; + const onIonModalWillDismiss = () => ionModalWillDismiss.value += 1; + const onIonModalDidDismiss = () => ionModalDidDismiss.value += 1; return { onModalWillPresent, onModalDidPresent, onModalWillDismiss, onModalDidDismiss, - onModalIonWillPresent, - onModalIonDidPresent, - onModalIonWillDismiss, - onModalIonDidDismiss, + onIonModalWillPresent, + onIonModalDidPresent, + onIonModalWillDismiss, + onIonModalDidDismiss, willPresent, didPresent, willDismiss,