Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(base-modal): migrate base-modal component and side effects #1479

Merged
merged 3 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/_vue3-migration-test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vue3-migration-test</title>
</head>
<body>
<body style="margin: 0;padding: 10px;">
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
Expand Down
2 changes: 1 addition & 1 deletion packages/_vue3-migration-test/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div id="app">
<h1>vue3-migration-test</h1>
<h1 class="header">vue3-migration-test</h1>
<nav>
<RouterLink v-for="route in $router.options.routes" :key="route.path" :to="route.path">
{{ route.name }}
Expand Down
1 change: 1 addition & 0 deletions packages/_vue3-migration-test/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './animations';
export { default as TestBaseColumnPickerDropdown } from './column-picker/test-base-column-picker-dropdown.vue';
export { default as TestBaseColumnPickerList } from './column-picker/test-base-column-picker-list.vue';
export { default as TestBaseModal } from './modals/test-base-modal.vue';
export { default as TestBaseDropdown } from './test-base-dropdown.vue';
export { default as TestBaseEventButton } from './test-base-event-button.vue';
export { default as TestUseLayouts } from './test-use-layouts.vue';
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<template>
<div class="base-modal">
<button @click="open = true">Open modal</button>
<BaseModal
@click:overlay="open = false"
@focusin:body="open = false"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

:open="open"
:focusOnOpen="true"
referenceSelector=".header"
:animation="Fade"
:overlayAnimation="Fade"
contentClass="content"
overlayClass="overlay"
>
<h1>Hello</h1>
<p>The modal is working</p>
<button @click="open = false">Close modal</button>
</BaseModal>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import BaseModal from '../../../../x-components/src/components/modals/base-modal.vue';
import Fade from '../../../../x-components/src/components/animations/fade.vue';

const open = ref(false);
</script>

<style>
.base-modal {
.content {
background: white;
margin: auto;
width: 50%;
border: 3px solid green;
padding: 10px;
}

.overlay {
background-color: red;
}
}
</style>
6 changes: 6 additions & 0 deletions packages/_vue3-migration-test/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
TestSortList,
TestSortPickerList,
TestBaseScroll,
TestBaseModal,
TestSearchBox,
TestEmpathize,
TestUseLayouts
Expand Down Expand Up @@ -47,6 +48,11 @@ const routes = [
name: 'Fade',
component: TestFade
},
{
path: '/base-modal',
name: 'BaseModal',
component: TestBaseModal
},
{
path: '/base-dropdown',
name: 'BaseDropdown',
Expand Down
2 changes: 1 addition & 1 deletion packages/x-components/src/components/animations/fade.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

&--leave-to,
&--enter {
opacity: 0;
opacity: 0 !important;
}
}
</style>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { createLocalVue, mount, Wrapper } from '@vue/test-utils';
import Vue from 'vue';
import { mount } from '@vue/test-utils';
import { getDataTestSelector } from '../../../__tests__/utils';
import BaseModal from '../base-modal.vue';

Expand All @@ -14,58 +13,52 @@ function mountBaseModal({
defaultSlot = '<span data-test="default-slot">Modal</span>',
open = false,
focusOnOpen = true,
contentClass,
overlayClass
}: MountBaseModalOptions = {}): MountBaseModalAPI {
const localVue = createLocalVue();
const wrapper = mount(BaseModal, {
localVue,
propsData: {
open,
focusOnOpen,
contentClass,
overlayClass
contentClass = '',
overlayClass = ''
} = {}) {
const wrapper = mount(
{
template: `
<BaseModal
:open="open"
:focusOnOpen="focusOnOpen"
:contentClass="contentClass"
:overlayClass="overlayClass"
>
<slot/>
</BaseModal>`,
components: { BaseModal },
props: ['open', 'focusOnOpen', 'contentClass', 'overlayClass']
},
slots: {
default: defaultSlot
{
propsData: { open, focusOnOpen, contentClass, overlayClass },
slots: { default: defaultSlot }
}
});
const appendToBody = (): void => {
document.body.appendChild(wrapper.element);
};
);

const baseModalWrapper = wrapper.findComponent(BaseModal);
const appendToBody = () => document.body.appendChild(wrapper.element);

return {
wrapper,
getModalContent() {
return wrapper.find(getDataTestSelector('modal-content'));
},
async setOpen(open) {
await wrapper.setProps({ open });
},
async closeModal() {
await wrapper.find(getDataTestSelector('modal-overlay'))?.trigger('click');
},
wrapper: baseModalWrapper,
getModalContent: () => baseModalWrapper.find(getDataTestSelector('modal-content')),
setOpen: async (open: boolean) => await wrapper.setProps({ open }),
closeModal: async () =>
await baseModalWrapper.find(getDataTestSelector('modal-overlay'))?.trigger('click'),
appendToBody,
async fakeFocusIn() {
const buttonWrapper = mount({
template: `<button>Button</button>`
});
fakeFocusIn: async () => {
const buttonWrapper = mount({ template: `<button>Button</button>` });
appendToBody();
document.body.appendChild(buttonWrapper.element);
jest.runAllTimers();
await buttonWrapper.trigger('focusin');
}
};
} as const;
}

describe('testing Base Modal component', () => {
beforeAll(() => {
jest.useFakeTimers();
});

afterAll(() => {
jest.useRealTimers();
});
beforeAll(() => jest.useFakeTimers());
afterAll(() => jest.useRealTimers());

it('renders only when the open prop is set to true', async () => {
const { getModalContent, setOpen } = mountBaseModal();
Expand Down Expand Up @@ -104,13 +97,15 @@ describe('testing Base Modal component', () => {
open: true
});

expect(wrapper.find(getDataTestSelector('default-slot-overridden')).exists()).toBe(true);
expect(wrapper.find(getDataTestSelector('default-slot-overridden')).exists()).toBeTruthy();
});

it('changes the focus to the correct element when the modal opens', async () => {
const dataTestSelector = 'expected-focus';
let { wrapper, setOpen, appendToBody } = mountBaseModal({
defaultSlot: `<div>
<button data-test="expected-focus">First button</button>
defaultSlot: `
<div>
<button data-test="${dataTestSelector}">First button</button>
<button>Second button</button>
</div>`,
open: false
Expand All @@ -119,29 +114,31 @@ describe('testing Base Modal component', () => {
appendToBody();
await setOpen(true);

expect(wrapper.find(getDataTestSelector('expected-focus')).element).toBe(
expect(wrapper.find(getDataTestSelector(dataTestSelector)).element).toEqual(
document.activeElement
);

({ wrapper, setOpen, appendToBody } = mountBaseModal({
defaultSlot: `<div>
defaultSlot: `
<div>
<button>First button</button>
<button tabindex="1" data-test="expected-focus">Second button</button>
<button tabindex="1" data-test="${dataTestSelector}">Second button</button>
</div>`,
open: false
}));

appendToBody();
await setOpen(true);

expect(wrapper.find(getDataTestSelector('expected-focus')).element).toBe(
expect(wrapper.find(getDataTestSelector(dataTestSelector)).element).toEqual(
document.activeElement
);
});

it("doesn't change the focus if the focusOnOpen prop is false", async () => {
const { setOpen, appendToBody } = mountBaseModal({
defaultSlot: `<div>
defaultSlot: `
<div>
<button tabindex="1">First button</button>
<button>Second button</button>
</div>`,
Expand All @@ -153,7 +150,7 @@ describe('testing Base Modal component', () => {
const focusedElementBeforeOpen = document.activeElement;
await setOpen(true);

expect(focusedElementBeforeOpen).toBe(document.activeElement);
expect(focusedElementBeforeOpen).toEqual(document.activeElement);
});

it('allows adding classes to the modal content', () => {
Expand All @@ -172,34 +169,6 @@ describe('testing Base Modal component', () => {
});

const overlay = wrapper.find(getDataTestSelector('modal-overlay'));
expect(overlay.classes('custom-class')).toBe(true);
expect(overlay.classes('custom-class')).toBeTruthy();
});
});

interface MountBaseModalOptions {
/** The default slot to render. */
defaultSlot?: string;
/** Events that when emitted should open the modal. */
open?: boolean;
/** Indicates if the focus changes to an element inside the modal when it opens. */
focusOnOpen?: boolean;
/** Class to add to the content element of the modal. */
contentClass?: string;
/** Class to add to the overlay element of the modal. */
overlayClass?: string;
}

interface MountBaseModalAPI {
/** The wrapper for the modal component. */
wrapper: Wrapper<Vue>;
/** Fakes a click on the close button. */
setOpen: (open: boolean) => Promise<void>;
/** Retrieves the modal container wrapper. */
getModalContent: () => Wrapper<Vue>;
/** Fakes a click on the modal close. */
closeModal: () => Promise<void>;
/** Appends the component's element the body. */
appendToBody: () => void;
/** Fakes a focusin event in another HTMLElement of the body. */
fakeFocusIn: () => Promise<void>;
}
Loading
Loading