mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-23 14:38:26 +00:00
feat(form,editor): submitForm/validateForm 静默挂载时注入静默标记,vs-code 字段跳过 monaco 渲染
This commit is contained in:
parent
2e031cc134
commit
3b9d5da0bc
@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<MagicCodeEditor
|
||||
v-if="!silentMode"
|
||||
:height="config.height"
|
||||
:type="diffMode ? 'diff' : undefined"
|
||||
:init-values="diffMode ? (lastValues || {})[name] : model[name]"
|
||||
@ -17,9 +18,9 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, inject } from 'vue';
|
||||
|
||||
import type { CodeConfig, FieldProps } from '@tmagic/form';
|
||||
import { type CodeConfig, type FieldProps, FORM_SILENT_MODE_KEY } from '@tmagic/form';
|
||||
|
||||
import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
|
||||
|
||||
@ -27,6 +28,13 @@ defineOptions({
|
||||
name: 'MFieldsVsCode',
|
||||
});
|
||||
|
||||
/**
|
||||
* 静默模式(submitForm/validateForm 隐藏挂载)下跳过 monaco 渲染:
|
||||
* 校验由 FormItem 针对 model 中的值完成,与编辑器实例无关;本组件挂载无值副作用
|
||||
* (save 仅由用户操作/编辑器内容变更触发),跳过可省去 monaco worker/model 的无谓实例化。
|
||||
*/
|
||||
const silentMode = inject(FORM_SILENT_MODE_KEY, false);
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: string | any];
|
||||
}>();
|
||||
|
||||
@ -7,6 +7,8 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { defineComponent, h } from 'vue';
|
||||
import { mount } from '@vue/test-utils';
|
||||
|
||||
import { FORM_SILENT_MODE_KEY } from '@tmagic/form';
|
||||
|
||||
import Code from '@editor/fields/Code.vue';
|
||||
|
||||
// 用一个简单的桩组件代替 MagicCodeEditor,把所有 props 原样渲染到 data-* 属性上,
|
||||
@ -45,7 +47,7 @@ vi.mock('@editor/layouts/CodeEditor.vue', () => ({
|
||||
}),
|
||||
}));
|
||||
|
||||
const mountCode = (props: Record<string, any>) =>
|
||||
const mountCode = (props: Record<string, any>, global?: Record<string, any>) =>
|
||||
mount(Code, {
|
||||
props: {
|
||||
// FieldProps 必填字段,用 as any 绕过测试中类型严格匹配
|
||||
@ -55,6 +57,7 @@ const mountCode = (props: Record<string, any>) =>
|
||||
prop: 'codeField',
|
||||
...props,
|
||||
} as any,
|
||||
global,
|
||||
});
|
||||
|
||||
const getEl = (wrapper: ReturnType<typeof mountCode>) => wrapper.find('.fake-code-editor').element as HTMLElement;
|
||||
@ -168,6 +171,28 @@ describe('Code', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('静默模式', () => {
|
||||
test('注入 FORM_SILENT_MODE_KEY=true 时不渲染 CodeEditor', () => {
|
||||
const wrapper = mountCode(
|
||||
{},
|
||||
{
|
||||
provide: { [FORM_SILENT_MODE_KEY as symbol]: true },
|
||||
},
|
||||
);
|
||||
expect(wrapper.find('.fake-code-editor').exists()).toBe(false);
|
||||
});
|
||||
|
||||
test('注入 FORM_SILENT_MODE_KEY=false 时正常渲染 CodeEditor', () => {
|
||||
const wrapper = mountCode(
|
||||
{},
|
||||
{
|
||||
provide: { [FORM_SILENT_MODE_KEY as symbol]: false },
|
||||
},
|
||||
);
|
||||
expect(wrapper.find('.fake-code-editor').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('diff 模式 (对比)', () => {
|
||||
test('isCompare=true 且有 lastValues 时切换为 diff 模式', () => {
|
||||
const wrapper = mountCode({
|
||||
|
||||
@ -29,6 +29,17 @@ export interface FormDiffConfig {
|
||||
export const FORM_DIFF_CONFIG_KEY: InjectionKey<FormDiffConfig> = Symbol('mFormDiffConfig');
|
||||
export const FORM_TYPE_MATCH_VALID_KEY: InjectionKey<ComputedRef<boolean>> = Symbol('mFormTypeMatchValid');
|
||||
|
||||
/**
|
||||
* 静默模式标记,由 `submitForm` / `validateForm` 以隐藏方式挂载临时表单时 provide(debug 弹层模式不提供)。
|
||||
*
|
||||
* 重型字段组件(如 vs-code)可 inject 该标记并跳过自身渲染:字段校验由 FormItem 基于 model 值完成,
|
||||
* 与叶子 UI 组件实例无关,跳过可省去无谓的实例化开销(如 monaco worker / model 的创建)。
|
||||
*
|
||||
* 注意:仅「挂载无值副作用」(不在 onMounted/watch immediate 中 emit change、不依赖 DOM 计算值)的
|
||||
* 字段组件可以安全跳过,接入前需逐一审计。
|
||||
*/
|
||||
export const FORM_SILENT_MODE_KEY: InjectionKey<boolean> = Symbol('mFormSilentMode');
|
||||
|
||||
export interface ValidateError {
|
||||
message: string;
|
||||
field: string;
|
||||
|
||||
@ -16,11 +16,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { type AppContext, type Component, createApp, defineComponent, h, nextTick, type Ref, ref, watch } from 'vue';
|
||||
import {
|
||||
type AppContext,
|
||||
type Component,
|
||||
createApp,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
provide,
|
||||
type Ref,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { applyExtendState } from './utils/form';
|
||||
import Form from './Form.vue';
|
||||
import type { ChangeRecord, FormConfig, FormState } from './schema';
|
||||
import { type ChangeRecord, FORM_SILENT_MODE_KEY, type FormConfig, type FormState } from './schema';
|
||||
|
||||
// #region SubmitFormOptions
|
||||
/**
|
||||
@ -210,7 +221,21 @@ const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T>
|
||||
})
|
||||
: userWrapper;
|
||||
|
||||
const app = createApp(wrapperComponent);
|
||||
// 静默(隐藏挂载)模式下注入静默标记:vs-code 等重型字段组件可据此跳过自身渲染,
|
||||
// 校验/取值依赖 FormItem 与 model 值,与叶子 UI 组件无关(见 FORM_SILENT_MODE_KEY 注释)。
|
||||
// 用组件级 provide 而非 app.provide:appContext 合并后 app._context.provides 与父级应用
|
||||
// 共享引用,app.provide 会把标记泄漏到父级应用。
|
||||
const rootComponent = hidden
|
||||
? defineComponent({
|
||||
name: 'MFormSilentProvider',
|
||||
setup() {
|
||||
provide(FORM_SILENT_MODE_KEY, true);
|
||||
return () => h(wrapperComponent);
|
||||
},
|
||||
})
|
||||
: wrapperComponent;
|
||||
|
||||
const app = createApp(rootComponent);
|
||||
instance.app = app;
|
||||
|
||||
// 继承父级应用上下文(components / directives / provides / config 等)
|
||||
|
||||
@ -16,18 +16,29 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { afterEach, beforeAll, describe, expect, test, vi } from 'vitest';
|
||||
import { type AppContext, createApp, defineComponent, h, nextTick } from 'vue';
|
||||
import MagicForm, { submitForm } from '@form/index';
|
||||
import { type AppContext, createApp, defineComponent, h, inject, nextTick } from 'vue';
|
||||
import MagicForm, { FORM_SILENT_MODE_KEY, submitForm, validateForm } from '@form/index';
|
||||
import ElementPlus from 'element-plus';
|
||||
|
||||
let appContext: AppContext;
|
||||
|
||||
// 探针字段:挂载时注入静默标记并记录,用于验证 submitForm/validateForm 的 provide 行为
|
||||
const silentProbeValues: (boolean | undefined)[] = [];
|
||||
const SilentProbe = defineComponent({
|
||||
name: 'MFieldsSilentProbe',
|
||||
setup() {
|
||||
silentProbeValues.push(inject(FORM_SILENT_MODE_KEY, undefined));
|
||||
return () => h('div');
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(() => {
|
||||
// 构造一个父级 app,把 element-plus 与 m-form 插件装上,
|
||||
// 之后通过 appContext 传给 submitForm 复用全局注册
|
||||
const parentApp = createApp(defineComponent({ render: () => h('div') }));
|
||||
parentApp.use(ElementPlus);
|
||||
parentApp.use(MagicForm);
|
||||
parentApp.component('m-fields-silent-probe', SilentProbe);
|
||||
appContext = parentApp._context;
|
||||
});
|
||||
|
||||
@ -216,6 +227,44 @@ describe('submitForm', () => {
|
||||
expect(caught).toBeInstanceOf(Error);
|
||||
});
|
||||
|
||||
test('静默(隐藏挂载)模式下向字段 provide FORM_SILENT_MODE_KEY=true', async () => {
|
||||
silentProbeValues.length = 0;
|
||||
|
||||
const values = await submitForm({
|
||||
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
appContext,
|
||||
});
|
||||
|
||||
expect(values).toEqual({ text: 'hello' });
|
||||
expect(silentProbeValues).toEqual([true]);
|
||||
});
|
||||
|
||||
test('静默标记不会泄漏到父级应用的 provides', async () => {
|
||||
silentProbeValues.length = 0;
|
||||
|
||||
await submitForm({
|
||||
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
appContext,
|
||||
});
|
||||
|
||||
expect((appContext as any).provides[FORM_SILENT_MODE_KEY as symbol]).toBeUndefined();
|
||||
});
|
||||
|
||||
test('validateForm 静默模式下同样 provide FORM_SILENT_MODE_KEY=true', async () => {
|
||||
silentProbeValues.length = 0;
|
||||
|
||||
const error = await validateForm({
|
||||
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
appContext,
|
||||
});
|
||||
|
||||
expect(error).toBe('');
|
||||
expect(silentProbeValues).toEqual([true]);
|
||||
});
|
||||
|
||||
test('timeout > 0 时会注册定时器,timeout <= 0 时不注册', async () => {
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
||||
|
||||
@ -354,6 +403,24 @@ describe('submitForm —— debug 模式', () => {
|
||||
expect(document.body.querySelector('.m-form')).toBeNull();
|
||||
});
|
||||
|
||||
test('debug 模式不提供静默标记(表单可见,字段应正常渲染)', async () => {
|
||||
silentProbeValues.length = 0;
|
||||
|
||||
const pending = submitForm({
|
||||
config: [{ type: 'silent-probe', name: 'text', text: 'text' }],
|
||||
initValues: { text: 'hello' },
|
||||
debug: true,
|
||||
appContext,
|
||||
});
|
||||
await nextTick();
|
||||
await nextTick();
|
||||
|
||||
expect(silentProbeValues).toEqual([undefined]);
|
||||
|
||||
findButton('确定').click();
|
||||
await pending;
|
||||
});
|
||||
|
||||
test('debug 模式不注册超时定时器(等待人工操作)', async () => {
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, 'setTimeout');
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user