Skip to content

Commit

Permalink
Merge branch 'main' into feature/drawer-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Aldemylla authored Oct 25, 2024
2 parents 83aaaf0 + ee261e5 commit 45b92ea
Show file tree
Hide file tree
Showing 3 changed files with 292 additions and 1 deletion.
13 changes: 12 additions & 1 deletion src/components/ModalDialog/ModalDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@
<section
v-if="modelValue"
class="unnnic-modal-dialog"
data-testid="modal-dialog"
>
<section
class="unnnic-modal-dialog__overlay"
data-testid="modal-overlay"
@click.stop="!persistent && close()"
/>
<section
:class="[
'unnnic-modal-dialog__container',
`unnnic-modal-dialog__container--${size}`,
]"
data-testid="modal-container"
>
<section
v-if="$slots.leftSidebar"
Expand All @@ -28,17 +31,22 @@
<section class="unnnic-modal-dialog__container__title-container">
<UnnnicIcon
v-if="icon || type"
data-testid="title-icon"
class="unnnic-modal-dialog__container__title-icon"
:icon="icon || iconsMapper[type]?.icon"
:scheme="iconScheme || iconsMapper[type]?.scheme"
size="md"
/>
<h1 class="unnnic-modal-dialog__container__title-text">
<h1
class="unnnic-modal-dialog__container__title-text"
data-testid="title-text"
>
{{ title }}
</h1>
</section>
<UnnnicIcon
v-if="showCloseIcon"
data-testid="close-icon"
icon="close"
clickable
@click="close()"
Expand All @@ -49,6 +57,7 @@
</section>
<section
v-if="primaryButtonProps.text"
data-testid="actions-section"
:class="[
'unnnic-modal-dialog__container__actions',
{
Expand All @@ -60,6 +69,7 @@
<UnnnicButton
v-if="!hideSecondaryButton"
v-bind="secondaryButtonProps"
data-testid="secondary-button"
type="tertiary"
:text="secondaryButtonProps.text || i18n('cancel')"
class="unnnic-modal-dialog__container__actions__secondary-button"
Expand All @@ -71,6 +81,7 @@
/>
<UnnnicButton
v-bind="primaryButtonProps"
data-testid="primary-button"
:type="
primaryButtonProps.type ||
primaryButtonTypeMapper[type] ||
Expand Down
256 changes: 256 additions & 0 deletions src/components/ModalDialog/__tests__/ModalDialog.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
import { mount } from '@vue/test-utils';
import { describe, it, vi } from 'vitest';
import ModalDialog from '../ModalDialog.vue';

describe('ModalDialog.vue', () => {
let wrapper;

beforeEach(() => {
wrapper = mount(ModalDialog, {
props: {
title: 'Test Title',
icon: 'test-icon',
modelValue: true,
showCloseIcon: true,
primaryButtonProps: { text: 'Confirm' },
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e }),
},
global: {
stubs: ['UnnnicIcon', 'UnnnicButton'],
},
});
});

describe('Elements rendering', () => {
it('matches the snapshot', () => {
expect(wrapper.html()).toMatchSnapshot();
});

it('should render correctly when modelValue is true', () => {
const modal = wrapper.find('[data-testid="modal-dialog"]');
expect(modal.exists()).toBe(true);
});

it('should not render when modelValue is false', async () => {
await wrapper.setProps({ modelValue: false });

const modal = wrapper.find('[data-testid="modal-dialog"]');
expect(modal.exists()).toBe(false);
});

it('should apply the correct size class based on the size prop', async () => {
const modalContainer = wrapper.find('[data-testid="modal-container"]');
const defaultClass = 'unnnic-modal-dialog__container';

expect(modalContainer.classes()).toContain(defaultClass + '--md');

await wrapper.setProps({ size: 'lg' });
expect(modalContainer.classes()).toContain(defaultClass + '--lg');

await wrapper.setProps({ size: 'sm' });
expect(modalContainer.classes()).toContain(defaultClass + '--sm');
});

it('should render the icon and title when provided', () => {
const title = wrapper.find('[data-testid="title-text"]');
expect(title.exists()).toBe(true);
expect(title.text()).toBe('Test Title');

const icon = wrapper.findComponent('[data-testid="title-icon"]');
expect(icon.exists()).toBe(true);
expect(icon.props('icon')).toBe('test-icon');
});

it('should render the icon from iconsMapper when type prop is provided', async () => {
await wrapper.setProps({
type: 'success',
icon: '',
});

const icon = wrapper.findComponent('[data-testid="title-icon"]');
expect(icon.exists()).toBe(true);
expect(icon.props().icon).toBe(wrapper.vm.iconsMapper['success'].icon);
});

it('should not render the icon when both icon and type props are not provided', async () => {
await wrapper.setProps({
icon: '',
type: '',
});

const icon = wrapper.find('[data-testid="title-icon"]');
expect(icon.exists()).toBe(false);
});

it('should render the close icon when showCloseIcon is true', () => {
const closeIcon = wrapper.find('[data-testid="close-icon"]');
expect(closeIcon.exists()).toBe(true);
});

it('should not render buttons when primaryButtonProps is not provided', async () => {
await wrapper.setProps({ primaryButtonProps: undefined });

const primaryButton = wrapper.find('[data-testid="primary-button"]');
const secondaryButton = wrapper.find('[data-testid="secondary-button"]');
expect(primaryButton.exists()).toBe(false);
expect(secondaryButton.exists()).toBe(false);
});
});

describe('Overlay behavior', () => {
it('should close the modal when clicking on the overlay if persistent is false', async () => {
expect(wrapper.props().modelValue).toBe(true);

const overlay = wrapper.find('[data-testid="modal-overlay"]');
await overlay.trigger('click');

expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')[0]).toEqual([false]);
expect(wrapper.props().modelValue).toBe(false);
});

it('should not close the modal when clicking on the overlay if persistent is true', async () => {
await wrapper.setProps({ persistent: true });

const overlay = wrapper.find('[data-testid="modal-overlay"]');
await overlay.trigger('click');

expect(wrapper.emitted('update:modelValue')).toBeFalsy();
expect(wrapper.props().modelValue).toBe(true);
});
});

describe('Slot rendering', () => {
it('should render leftSidebar slot when provided', () => {
const wrapper = mount(ModalDialog, {
props: {
modelValue: true,
},
slots: {
leftSidebar:
'<div data-testid="left-sidebar">Left Sidebar Content</div>',
},
});

const leftSidebar = wrapper.find('[data-testid="left-sidebar"]');
expect(leftSidebar.exists()).toBe(true);
expect(leftSidebar.text()).toBe('Left Sidebar Content');
});

it('should render default slot content', () => {
const wrapper = mount(ModalDialog, {
props: {
modelValue: true,
},
slots: {
default: '<div data-testid="default-slot">Default Slot Content</div>',
},
});

const defaultSlot = wrapper.find('[data-testid="default-slot"]');
expect(defaultSlot.exists()).toBe(true);
expect(defaultSlot.text()).toBe('Default Slot Content');
});
});

describe('Buttons actions', () => {
it('should close the modal when clicking on close icon', async () => {
const closeIcon = wrapper.find('[data-testid="close-icon"]');
await closeIcon.trigger('click');

expect(wrapper.emitted('update:modelValue')).toBeTruthy();
expect(wrapper.emitted('update:modelValue')[0]).toEqual([false]);
expect(wrapper.props().modelValue).toBe(false);
});

it('should emit primaryButtonClick event when the primary button is clicked', async () => {
const primaryButton = wrapper.find('[data-testid="primary-button"]');
await primaryButton.trigger('click');

expect(wrapper.emitted('primaryButtonClick')).toBeTruthy();
});

it('should emit secondaryButtonClick event when the secondary button is clicked', async () => {
await wrapper.setProps({
secondaryButtonProps: { text: 'Cancel' },
});

const secondaryButton = wrapper.find('[data-testid="secondary-button"]');
await secondaryButton.trigger('click');

expect(wrapper.emitted('secondaryButtonClick')).toBeTruthy();
});

it('should close the modal when the secondary button is clicked and no secondaryButtonClick event is provided', async () => {
const secondaryButton = wrapper.find('[data-testid="secondary-button"]');
await secondaryButton.trigger('click');

const emittedValue = wrapper.emitted('update:modelValue');

expect(emittedValue).toBeTruthy();
expect(emittedValue[0][0]).toEqual(false);
});
});

describe('Actions and appearance', () => {
it('should not render the secondary button when hideSecondaryButton is true', async () => {
await wrapper.setProps({
hideSecondaryButton: true,
});
const secondaryButton = wrapper.find('[data-testid="secondary-button"]');
expect(secondaryButton.exists()).toBe(false);
});

it('should apply a divider class to the actions section when showActionsDivider is true', async () => {
await wrapper.setProps({
showActionsDivider: true,
});

const actionsSection = wrapper.find('[data-testid="actions-section"]');
expect(actionsSection.classes()).toContain(
'unnnic-modal-dialog__container__actions--divider',
);
});
});

describe('Body overflow', () => {
it('should toggle body overflow based on modal visibility', async () => {
await wrapper.setProps({ modelValue: false });
const updateBodyOverflowSpy = vi.spyOn(wrapper.vm, 'updateBodyOverflow');

await wrapper.setProps({ modelValue: true });
expect(updateBodyOverflowSpy).toHaveBeenCalledWith(true);

await wrapper.setProps({ modelValue: false });
expect(updateBodyOverflowSpy).toHaveBeenCalledWith(false);
});
});

describe('Validators', () => {
describe('type prop validator', () => {
it('should validate type prop when a valid value is provided', () => {
const validTypes = ['success', 'warning', 'attention'];
validTypes.forEach((validType) => {
expect(ModalDialog.props.type.validate(validType)).toBe(true);
});
});

it('should invalidate type prop when an invalid value is provided', () => {
expect(ModalDialog.props.type.validate('invalidType')).toBe(false);
});
});

describe('size prop validator', () => {
it('should validate size prop when a valid value is provided', () => {
const validSizes = ['sm', 'md', 'lg'];
validSizes.forEach((validSize) => {
expect(ModalDialog.props.size.validate(validSize)).toBe(true);
});
});

it('should invalidate size prop when an invalid value is provided', () => {
expect(ModalDialog.props.size.validate('invalidSize')).toBe(false);
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`ModalDialog.vue > Elements rendering > matches the snapshot 1`] = `
"<section data-v-68ebadeb="" class="unnnic-modal-dialog" data-testid="modal-dialog">
<section data-v-68ebadeb="" class="unnnic-modal-dialog__overlay" data-testid="modal-overlay"></section>
<section data-v-68ebadeb="" class="unnnic-modal-dialog__container unnnic-modal-dialog__container--md" data-testid="modal-container">
<!--v-if-->
<section data-v-68ebadeb="" class="unnnic-modal-dialog__container__body">
<header data-v-68ebadeb="" class="unnnic-modal-dialog__container__header">
<section data-v-68ebadeb="" class="unnnic-modal-dialog__container__title-container">
<unnnic-icon-stub data-v-68ebadeb="" filled="false" next="false" icon="test-icon" clickable="false" size="md" scheme="neutral-darkest" data-testid="title-icon" class="unnnic-modal-dialog__container__title-icon"></unnnic-icon-stub>
<h1 data-v-68ebadeb="" class="unnnic-modal-dialog__container__title-text" data-testid="title-text">Test Title</h1>
</section>
<unnnic-icon-stub data-v-68ebadeb="" filled="false" next="false" icon="close" clickable="true" size="md" scheme="neutral-darkest" data-testid="close-icon"></unnnic-icon-stub>
</header>
<section data-v-68ebadeb="" class="unnnic-modal-dialog__container__content"></section>
<section data-v-68ebadeb="" data-testid="actions-section" class="unnnic-modal-dialog__container__actions">
<unnnic-button-stub data-v-68ebadeb="" size="large" text="Cancel" type="tertiary" float="false" iconleft="" iconright="" iconcenter="" next="false" disabled="false" loading="false" data-testid="secondary-button" class="unnnic-modal-dialog__container__actions__secondary-button"></unnnic-button-stub>
<unnnic-button-stub data-v-68ebadeb="" size="large" text="Confirm" type="primary" float="false" iconleft="" iconright="" iconcenter="" next="false" disabled="false" loading="false" data-testid="primary-button" class="unnnic-modal-dialog__container__actions__primary-button"></unnnic-button-stub>
</section>
</section>
</section>
</section>"
`;

0 comments on commit 45b92ea

Please sign in to comment.