fix(vue): account for event name changes in vue 3.0.6+ for overlay components (#23100)

This commit is contained in:
Liam DeBeasi
2021-03-29 15:30:31 -04:00
committed by GitHub
parent ba51daf17c
commit 27318cf585
6 changed files with 108 additions and 12 deletions

View File

@ -1,8 +1,7 @@
import { App, Plugin } from 'vue'; import { App, Plugin } from 'vue';
import { IonicConfig, setupConfig } from '@ionic/core'; import { IonicConfig, setupConfig } from '@ionic/core';
import { applyPolyfills, defineCustomElements } from '@ionic/core/loader'; import { applyPolyfills, defineCustomElements } from '@ionic/core/loader';
import { needsKebabCase } from './utils';
const needsKebabCase = (version: string) => !['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5'].includes(version);
/** /**
* We need to make sure that the web component fires an event * We need to make sure that the web component fires an event

View File

@ -57,3 +57,5 @@ export const getConfig = (): CoreConfig | null => {
} }
return null; return null;
}; };
export const needsKebabCase = (version: string) => !['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.0.4', '3.0.5'].includes(version);

View File

@ -1,20 +1,37 @@
import { defineComponent, h, ref, VNode } from 'vue'; import { defineComponent, h, ref, VNode, getCurrentInstance } from 'vue';
import { needsKebabCase } from '../utils';
export interface OverlayProps { export interface OverlayProps {
isOpen?: boolean; isOpen?: boolean;
} }
export const defineOverlayContainer = <Props extends object>(name: string, componentProps: string[] = [], controller: any) => { export const defineOverlayContainer = <Props extends object>(name: string, componentProps: string[] = [], controller: any) => {
// TODO /**
* Vue 3.0.6 fixed a bug where events on custom elements
* were always converted to lower case, so "ionRefresh"
* became "ionRefresh". We need to account for the old
* issue as well as the new behavior where "ionRefresh"
* is converted to "ion-refresh".
* See https://github.com/vuejs/vue-next/pull/2847
*/
const eventPrefix = name.toLowerCase().split('-').join(''); const eventPrefix = name.toLowerCase().split('-').join('');
const eventListeners = [ const lowerCaseListeners = [
{ componentEv: `${eventPrefix}willpresent`, frameworkEv: 'onWillPresent' }, { componentEv: `${eventPrefix}willpresent`, frameworkEv: 'onWillPresent' },
{ componentEv: `${eventPrefix}didpresent`, frameworkEv: 'onDidPresent' }, { componentEv: `${eventPrefix}didpresent`, frameworkEv: 'onDidPresent' },
{ componentEv: `${eventPrefix}willdismiss`, frameworkEv: 'onWillDismiss' }, { componentEv: `${eventPrefix}willdismiss`, frameworkEv: 'onWillDismiss' },
{ componentEv: `${eventPrefix}diddismiss`, frameworkEv: 'onDidDismiss' }, { componentEv: `${eventPrefix}diddismiss`, frameworkEv: 'onDidDismiss' },
]; ];
const kebabCaseListeners = [
{ componentEv: `${name}-will-present`, frameworkEv: 'onWillPresent' },
{ componentEv: `${name}-did-present`, frameworkEv: 'onDidPresent' },
{ componentEv: `${name}-will-dismiss`, frameworkEv: 'onWillDismiss' },
{ componentEv: `${name}-did-dismiss`, frameworkEv: 'onDidDismiss' },
];
const Container = defineComponent<Props & OverlayProps>((props, { slots, emit }) => { const Container = defineComponent<Props & OverlayProps>((props, { slots, emit }) => {
const instance = getCurrentInstance();
const adjustedEventListeners = needsKebabCase(instance.appContext.app.version) ? kebabCaseListeners : lowerCaseListeners;
const overlay = ref(); const overlay = ref();
const onVnodeMounted = async () => { const onVnodeMounted = async () => {
const isOpen = props.isOpen; const isOpen = props.isOpen;
@ -76,7 +93,7 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
overlay.value = await overlay.value; overlay.value = await overlay.value;
eventListeners.forEach(eventListener => { adjustedEventListeners.forEach(eventListener => {
overlay.value.addEventListener(eventListener.componentEv, () => { overlay.value.addEventListener(eventListener.componentEv, () => {
emit(eventListener.frameworkEv); emit(eventListener.frameworkEv);
}); });
@ -101,7 +118,7 @@ export const defineOverlayContainer = <Props extends object>(name: string, compo
Container.displayName = name; Container.displayName = name;
Container.props = [...componentProps, 'isOpen']; Container.props = [...componentProps, 'isOpen'];
Container.emits = eventListeners.map(ev => ev.frameworkEv); Container.emits = [...lowerCaseListeners.map(ev => ev.frameworkEv), ...kebabCaseListeners.map(ev => ev.frameworkEv)];
return Container; return Container;
} }

View File

@ -3,7 +3,7 @@
<ion-header> <ion-header>
<ion-toolbar> <ion-toolbar>
<ion-buttons> <ion-buttons>
<ion-button @click="dismiss">Dismiss</ion-button> <ion-button @click="dismiss" id="dismiss">Dismiss</ion-button>
</ion-buttons> </ion-buttons>
<ion-title>Modal</ion-title> <ion-title>Modal</ion-title>
</ion-toolbar> </ion-toolbar>

View File

@ -1,5 +1,5 @@
<template> <template>
<ion-page> <ion-page data-pageid="overlays">
<ion-header :translucent="true"> <ion-header :translucent="true">
<ion-toolbar> <ion-toolbar>
<ion-buttons> <ion-buttons>
@ -66,6 +66,13 @@
<ion-button @click="changeLoadingProps()" id="change-loading-props">Quickly Change Loading Props</ion-button> <ion-button @click="changeLoadingProps()" id="change-loading-props">Quickly Change Loading Props</ion-button>
<br /><br />
Modal onWillPresent: <div id="willPresent">{{ willPresent }}</div><br />
Modal onDidPresent: <div id="didPresent">{{ didPresent }}</div><br />
Modal onWillDismiss: <div id="willDismiss">{{ willDismiss }}</div><br />
Modal onDidDismiss: <div id="didDismiss">{{ didDismiss }}</div><br />
<ion-action-sheet <ion-action-sheet
:is-open="isActionSheetOpen" :is-open="isActionSheetOpen"
:buttons="actionSheetButtons" :buttons="actionSheetButtons"
@ -93,7 +100,10 @@
<ion-modal <ion-modal
:is-open="isModalOpen" :is-open="isModalOpen"
:componentProps="overlayProps" :componentProps="overlayProps"
@onDidDismiss="setModalRef(false)" @onWillPresent="onModalWillPresent"
@onDidPresent="onModalDidPresent"
@onWillDismiss="onModalWillDismiss"
@onDidDismiss="onModalDidDismiss"
> >
<ModalContent></ModalContent> <ModalContent></ModalContent>
</ion-modal> </ion-modal>
@ -326,7 +336,25 @@ export default defineComponent({
}, 10); }, 10);
} }
const willPresent = ref(0);
const didPresent = ref(0);
const willDismiss = ref(0);
const didDismiss = 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); }
return { return {
onModalWillPresent,
onModalDidPresent,
onModalWillDismiss,
onModalDidDismiss,
willPresent,
didPresent,
willDismiss,
didDismiss,
changeLoadingProps, changeLoadingProps,
overlayProps, overlayProps,
present, present,

View File

@ -1,5 +1,6 @@
describe('Overlays', () => { describe('Overlays', () => {
beforeEach(() => { beforeEach(() => {
cy.viewport(1000, 900);
cy.visit('http://localhost:8080/overlays') cy.visit('http://localhost:8080/overlays')
}) })
@ -7,7 +8,6 @@ describe('Overlays', () => {
for (let overlay of overlays) { for (let overlay of overlays) {
it(`should open and close ${overlay} via controller`, () => { it(`should open and close ${overlay} via controller`, () => {
console.log(overlay)
cy.get(`ion-radio#${overlay}`).click(); cy.get(`ion-radio#${overlay}`).click();
cy.get('ion-radio#controller').click(); cy.get('ion-radio#controller').click();
@ -34,7 +34,6 @@ describe('Overlays', () => {
for (let overlay of overlays) { for (let overlay of overlays) {
it(`should open and close ${overlay} via component`, () => { it(`should open and close ${overlay} via component`, () => {
console.log(overlay)
cy.get(`ion-radio#${overlay}`).click(); cy.get(`ion-radio#${overlay}`).click();
cy.get('ion-radio#component').click(); cy.get('ion-radio#component').click();
@ -104,4 +103,55 @@ describe('Overlays', () => {
cy.get('ion-loading').should('have.length', 1); cy.get('ion-loading').should('have.length', 1);
}); });
it('should fire 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');
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
});
});
}) })
const testLifecycle = (selector, expected = {}) => {
cy.get(`[data-pageid=${selector}] #willPresent`).should('have.text', expected.willPresent);
cy.get(`[data-pageid=${selector}] #didPresent`).should('have.text', expected.didPresent);
cy.get(`[data-pageid=${selector}] #willDismiss`).should('have.text', expected.willDismiss);
cy.get(`[data-pageid=${selector}] #didDismiss`).should('have.text', expected.didDismiss);
}