/* @vitest-environment happy-dom */
|
|
import { flushPromises, mount } from '@vue/test-utils';
|
import { defineComponent, h, nextTick } from 'vue';
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import GlobalFormModal from '../index.vue';
|
|
const apiMocks = vi.hoisted(() => ({
|
createModel: vi.fn(),
|
getConfigData: vi.fn(),
|
getModelInfo: vi.fn(),
|
updateModel: vi.fn(),
|
}));
|
const emitterMocks = vi.hoisted(() => ({ emit: vi.fn(), off: vi.fn(), on: vi.fn() }));
|
|
vi.mock('#/api/onlineDev/visualDev', () => apiMocks);
|
vi.mock('#/store', () => ({ useBaseStore: () => ({ getSysConfig: {} }) }));
|
vi.mock('#/components/FormExtraPanel/index.vue', () => ({
|
default: defineComponent({ name: 'FormExtraPanel', setup: () => () => h('div') }),
|
}));
|
vi.mock('#/components/FormGenerator/src/helper/displayOnly', () => ({
|
buildDisplayOnlySubmitData: (_fields: unknown, data: unknown) => data,
|
normalizeVirtualFields: vi.fn(),
|
}));
|
vi.mock('#/directives/disablePasswordAutofill', () => ({ vDisablePasswordAutofill: {} }));
|
vi.mock('#/utils/constants/electronicSignature', () => ({ isElectronicSignatureModelId: () => false }));
|
vi.mock('#/utils/jnpf', () => ({
|
getJnpfAppEnCode: () => '',
|
onlineUtils: { getEmitter: () => emitterMocks },
|
}));
|
vi.mock('@vben/stores', () => ({ useUserStore: () => ({ getUserInfo: {} }) }));
|
vi.mock('@jnpf/hooks', () => ({ useMessage: () => ({ createMessage: { error: vi.fn(), warning: vi.fn() } }) }));
|
vi.mock('@jnpf/utils', async (importOriginal) => {
|
const actual = await importOriginal<Record<string, unknown>>();
|
return {
|
...actual,
|
createAsyncComponent: () =>
|
defineComponent({
|
name: 'AsyncFormStub',
|
props: { formData: Object },
|
setup: (props) => () => h('div', { 'data-test': 'async-form' }, JSON.stringify(props.formData)),
|
}),
|
};
|
});
|
|
function containerStub(name: string, testId: string) {
|
return defineComponent({
|
name,
|
inheritAttrs: false,
|
props: { loading: Boolean, open: Boolean, wrapperProps: { default: () => ({}), type: Object } },
|
setup(props, { attrs, slots }) {
|
return () =>
|
props.open
|
? h(
|
'section',
|
{
|
'data-loading': String(props.loading),
|
'data-loading-size': (props.wrapperProps as any).loadingSize,
|
'data-test': testId,
|
'data-width': attrs.width,
|
},
|
slots.default?.(),
|
)
|
: null;
|
},
|
});
|
}
|
|
vi.mock('@jnpf/ui/popup', () => ({ BasicPopup: containerStub('BasicPopup', 'popup') }));
|
vi.mock('@jnpf/ui/modal', () => ({ BasicModal: containerStub('BasicModal', 'modal') }));
|
vi.mock('@jnpf/ui/drawer', () => ({ BasicDrawer: containerStub('BasicDrawer', 'drawer') }));
|
|
function mountFormModal(props: { listenGlobal?: boolean } = {}) {
|
return mount(GlobalFormModal, { global: { stubs: { 'a-button': true } }, props });
|
}
|
|
describe('globalFormModal local instance', () => {
|
beforeEach(() => {
|
vi.clearAllMocks();
|
apiMocks.getConfigData.mockResolvedValue({
|
data: {
|
formData: JSON.stringify({ fields: [], popupType: 'fullScreen' }),
|
fullName: '低代码表单',
|
webType: 2,
|
},
|
});
|
});
|
|
it('notifies the caller only after the modal is visible', async () => {
|
const wrapper = mountFormModal({ listenGlobal: false });
|
const onOpen = vi.fn(() => expect(wrapper.find('[data-test="modal"]').exists()).toBe(true));
|
const onOpenError = vi.fn();
|
await (wrapper.vm as any).open({ modelId: 'model-1', type: 'modal', onOpen, onOpenError });
|
expect(onOpen).toHaveBeenCalledOnce();
|
expect(onOpenError).not.toHaveBeenCalled();
|
wrapper.unmount();
|
});
|
|
it('notifies the caller when fetching modal configuration fails', async () => {
|
const error = new Error('configuration failed');
|
apiMocks.getConfigData.mockRejectedValueOnce(error);
|
const errorLog = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const wrapper = mountFormModal({ listenGlobal: false });
|
const onOpen = vi.fn();
|
const onOpenError = vi.fn();
|
try {
|
await (wrapper.vm as any).open({ modelId: 'model-1', onOpen, onOpenError });
|
expect(onOpen).not.toHaveBeenCalled();
|
expect(onOpenError).toHaveBeenCalledWith(error);
|
} finally {
|
wrapper.unmount();
|
errorLog.mockRestore();
|
}
|
});
|
|
it('notifies the caller when the form type cannot open in a modal', async () => {
|
apiMocks.getConfigData.mockResolvedValueOnce({ data: { webType: 4 } });
|
const wrapper = mountFormModal({ listenGlobal: false });
|
const onOpen = vi.fn();
|
const onOpenError = vi.fn();
|
await (wrapper.vm as any).open({ modelId: 'model-1', onOpen, onOpenError });
|
expect(onOpen).not.toHaveBeenCalled();
|
expect(onOpenError).toHaveBeenCalledOnce();
|
wrapper.unmount();
|
});
|
|
it('forwards container attributes without fragment inheritance warnings', async () => {
|
const warnings: string[] = [];
|
|
const wrapper = mount(GlobalFormModal, {
|
attrs: { width: '1000px' },
|
global: {
|
config: { warnHandler: (message) => warnings.push(message) },
|
stubs: { 'a-button': true },
|
},
|
props: { listenGlobal: false },
|
});
|
|
await (wrapper.vm as any).open({ modelId: 'model-1' });
|
await flushPromises();
|
await nextTick();
|
|
expect(wrapper.find('[data-test="popup"]').attributes('data-width')).toBe('1000px');
|
expect(warnings.some((message) => message.includes('Extraneous non-props attributes (width)'))).toBe(false);
|
});
|
|
it('can open inside its mounting container without listening to global events', async () => {
|
const wrapper = mountFormModal({ listenGlobal: false });
|
|
expect(emitterMocks.on).not.toHaveBeenCalled();
|
expect(typeof (wrapper.vm as any).open).toBe('function');
|
|
await (wrapper.vm as any).open({ modelId: 'model-1', title: '页面内表单' });
|
await flushPromises();
|
await nextTick();
|
|
expect(apiMocks.getConfigData).toHaveBeenCalledWith('model-1', { onlineUtilsOpen: true });
|
expect(wrapper.find('[data-test="popup"]').exists()).toBe(true);
|
});
|
|
it('continues listening to global open events by default', () => {
|
const wrapper = mountFormModal();
|
|
expect(emitterMocks.on).toHaveBeenCalledWith('OPEN_FORM_MODAL', expect.any(Function));
|
wrapper.unmount();
|
expect(emitterMocks.off).toHaveBeenCalledWith('OPEN_FORM_MODAL', expect.any(Function));
|
});
|
|
it('shows modal loading while template detail data is loading', async () => {
|
apiMocks.getConfigData.mockResolvedValue({
|
data: {
|
formData: JSON.stringify({ fields: [], popupType: 'modal' }),
|
fullName: '电子记录模板',
|
webType: 2,
|
},
|
});
|
let resolveDetail!: (value: unknown) => void;
|
apiMocks.getModelInfo.mockReturnValue(
|
new Promise((resolve) => {
|
resolveDetail = resolve;
|
}),
|
);
|
const wrapper = mountFormModal({ listenGlobal: false });
|
|
await (wrapper.vm as any).open({ id: 'template-1', mode: 'detail', modelId: 'model-1', type: 'modal' });
|
await flushPromises();
|
await nextTick();
|
|
expect(wrapper.find('[data-test="modal"]').attributes('data-loading')).toBe('true');
|
expect(wrapper.find('[data-test="modal"]').attributes('data-loading-size')).toBe('default');
|
|
resolveDetail({ data: { data: '{}', id: 'template-1' } });
|
await flushPromises();
|
|
expect(wrapper.find('[data-test="modal"]').attributes('data-loading')).toBe('false');
|
});
|
|
it('overrides incomplete low-code detail data with mapped business detail values', async () => {
|
apiMocks.getConfigData.mockResolvedValue({
|
data: {
|
formData: JSON.stringify({
|
fields: [
|
{ __config__: { children: [] }, __vModel__: 'muban_fenlei' },
|
{ __config__: { children: [] }, __vModel__: 'muban_mincheng' },
|
{ __config__: { children: [] }, __vModel__: 'jianyan_xiangmu' },
|
{ __config__: { children: [] }, __vModel__: 'shiyan_buzhou' },
|
{ __config__: { children: [] }, __vModel__: 'chuangjian_renyuan' },
|
],
|
popupType: 'fullScreen',
|
}),
|
fullName: '电子记录模板',
|
webType: 2,
|
},
|
});
|
apiMocks.getModelInfo.mockResolvedValue({
|
data: {
|
data: JSON.stringify({
|
chuangjian_renyuan: '',
|
jianyan_xiangmu: 'inspection-id',
|
muban_fenlei: 'category-id',
|
muban_mincheng: '',
|
shiyan_buzhou: '',
|
}),
|
id: 'template-1',
|
},
|
});
|
const wrapper = mountFormModal({ listenGlobal: false });
|
|
await (wrapper.vm as any).open({
|
fieldMapping: {
|
chuangjian_renyuan: 'detail.chuangjianRenyuanMingcheng',
|
jianyan_xiangmu: 'detail.jianyanXiangmuMingcheng',
|
muban_fenlei: 'detail.mubanFenleiMingcheng',
|
muban_mincheng: 'detail.mubanMingcheng',
|
shiyan_buzhou: 'detail.shiyanBuzhou',
|
},
|
id: 'template-1',
|
mode: 'detail',
|
modelId: 'model-1',
|
params: {
|
detail: {
|
chuangjianRenyuanMingcheng: '张三',
|
jianyanXiangmuMingcheng: '微生物限度',
|
mubanFenleiMingcheng: '检验记录',
|
mubanMingcheng: '微生物限度模板',
|
shiyanBuzhou: '按规程执行',
|
},
|
},
|
});
|
await flushPromises();
|
await nextTick();
|
|
expect(wrapper.findComponent({ name: 'AsyncFormStub' }).props('formData')).toMatchObject({
|
chuangjian_renyuan: '张三',
|
jianyan_xiangmu: '微生物限度',
|
muban_fenlei: '检验记录',
|
muban_mincheng: '微生物限度模板',
|
shiyan_buzhou: '按规程执行',
|
});
|
});
|
});
|