import { describe, it, expect, vi } from 'vitest';
import { mount, VueWrapper } from '@vue/test-utils';
import { ComponentPublicInstance, nextTick, ref } from 'vue';
import { getBodyElement } from '@coast/__tests__/utils/utils';
import Dialog from '../Dialog.vue';
import { openDialog } from './../openDialog';
vi.mock('../../style/iconfont/index.js');
const AXIOM = 'Tomorrow will be even better';
const DEFAULT_TITLE = '提示';
const TITLE = 'I am title';
type DialogEventNameType = 'cancel' | 'confirm';
const _mount = (
props: {
fnName?: DialogEventNameType;
fn?: () => unknown;
title?: string;
overlayClosable?: boolean;
onChange?: boolean;
} = {},
) => {
const { fnName, fn, title, overlayClosable, onChange } = props;
const bindEvent = fnName ? `:${fnName}="handle"` : '';
const bindTitle = title ? `title="${title}"` : '';
const bindOnChange = onChange !== undefined ? `@update:visible="handleChange"` : '';
const bindOverlayClosable =
overlayClosable !== undefined ? `:overlayClosable="${overlayClosable}"` : '';
return mount({
components: { 'co-dialog': Dialog },
template: `
${AXIOM}
`,
setup() {
const visible = ref(true);
const handle = fn;
const handleChange = vi.fn();
return { visible, handle, handleChange };
},
});
};
const handleClickElementAndWait = async (element: HTMLElement) => {
element.click();
await nextTick();
};
const handleGetDialogButtonByIndex = (index: number): HTMLElement => {
const { lastElementChild } = getBodyElement();
return lastElementChild.querySelectorAll('.coast-button')[index] as HTMLElement;
};
const handleClickOverlay = async () => {
const { lastButOneElementChild } = getBodyElement();
await handleClickElementAndWait(lastButOneElementChild as HTMLElement);
};
const handleTriggerEventAndExpect = async (
fnName: DialogEventNameType,
fn: () => unknown = vi.fn(),
) => {
const wrapper = _mount({ fnName, fn });
const vm = wrapper.vm;
await expectEventTriggerCorrectly({
fn,
fnName,
wrapper,
expectFn: () => {
expect(vm.handle).toHaveBeenCalled();
},
});
};
const expectDialogExist = (title = DEFAULT_TITLE, bodyChildrenLength = 2) => {
const { bodyChildren, lastButOneElementChild, lastElementChild } = getBodyElement();
expect(bodyChildren.length).toBe(bodyChildrenLength);
expect(lastButOneElementChild.className).toContain('coast-dialog-overlay');
expect(lastElementChild.className).toContain('coast-dialog-wrapper');
expect(lastElementChild.firstElementChild.className).toContain('coast-dialog');
expect(lastElementChild.querySelector('header').textContent.trim()).toBe(title);
expect(lastElementChild.querySelector('main').textContent).toBe(AXIOM);
};
const expectDialogExistThenUnmount = (wrapper: VueWrapper) => {
expectDialogExist();
wrapper.unmount();
};
const expectDialogHasRemoved = () => {
const { bodyChildren } = getBodyElement();
expect(bodyChildren.length).toEqual(0);
};
const expectEventTriggerCorrectly = async ({
fn,
fnName,
wrapper,
expectFn,
}: {
fnName: DialogEventNameType;
fn: () => unknown;
wrapper?: VueWrapper;
expectFn?: () => void;
}) => {
const buttonIndex = fnName === 'confirm' ? 0 : 1;
await handleClickElementAndWait(handleGetDialogButtonByIndex(buttonIndex));
expect(fn).toHaveBeenCalled();
expectFn?.();
fn() === false ? wrapper && expectDialogExistThenUnmount(wrapper) : expectDialogHasRemoved();
};
describe('Dialog', () => {
it('create', () => {
const wrapper = _mount();
expectDialogExistThenUnmount(wrapper);
});
it('render title', () => {
const wrapper = _mount({ title: TITLE });
expectDialogExist(TITLE);
wrapper.unmount();
});
it('click header close button can close dialog', async () => {
_mount();
const { lastElementChild } = getBodyElement();
const closeButton = lastElementChild.querySelector('.coast-dialog-close') as HTMLElement;
await handleClickElementAndWait(closeButton);
expectDialogHasRemoved();
});
it('click overlay can close dialog', async () => {
_mount();
await handleClickOverlay();
expectDialogHasRemoved();
});
it('overlayClosable is false, click overlay cannot close dialog', async () => {
const wrapper = _mount({ overlayClosable: false });
await handleClickOverlay();
expectDialogExistThenUnmount(wrapper);
});
it('watch dialog visible change', async () => {
const wrapper = _mount({ onChange: true });
const vm = wrapper.vm;
await handleClickOverlay();
expect(vm.handleChange).toHaveBeenCalled();
});
it('confirm event, not return, close dialog', async () => {
await handleTriggerEventAndExpect('confirm');
});
it('confirm event, return true, close dialog', async () => {
await handleTriggerEventAndExpect(
'confirm',
vi.fn(() => true),
);
});
it('confirm event, return false, cannot close dialog', async () => {
await handleTriggerEventAndExpect(
'confirm',
vi.fn(() => false),
);
});
it('cancel event, not return, close dialog', async () => {
await handleTriggerEventAndExpect('cancel');
});
it('cancel event, return true, close dialog', async () => {
await handleTriggerEventAndExpect(
'cancel',
vi.fn(() => true),
);
});
it('cancel event, return false, cannot close dialog', async () => {
await handleTriggerEventAndExpect(
'cancel',
vi.fn(() => false),
);
});
});
describe('openDialog', () => {
it('create dialog', () => {
const wrapper = openDialog({ content: AXIOM, title: TITLE });
expectDialogExist(TITLE, 3);
wrapper.close();
expectDialogHasRemoved();
});
it('click overlay can close dialog', async () => {
openDialog({ content: AXIOM });
await handleClickOverlay();
expectDialogHasRemoved();
});
it('overlayClosable is false, click overlay cannot close dialog', async () => {
const wrapper = openDialog({ content: AXIOM, overlayClosable: false });
await handleClickOverlay();
expectDialogExist(DEFAULT_TITLE, 3);
wrapper.close();
});
it('confirm event', async () => {
const confirm = vi.fn();
openDialog({ content: AXIOM, confirm });
await expectEventTriggerCorrectly({
fn: confirm,
fnName: 'confirm',
});
});
it('cancel event', async () => {
const cancel = vi.fn();
openDialog({ content: AXIOM, cancel });
await expectEventTriggerCorrectly({
fn: cancel,
fnName: 'cancel',
});
});
});