mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-20 15:33:37 +00:00
fix(form): select 在 model 值变化时补拉 init 选项
配置 config.option 时监听 model 字段变化,若当前 options 缺少对应项则重新 getInitOption,并补充单测覆盖。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f00e84793d
commit
aa2ee9fd4b
@ -78,7 +78,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { inject, nextTick, onBeforeMount, ref, watch, watchEffect } from 'vue';
|
||||
import { inject, nextTick, ref, watch, watchEffect } from 'vue';
|
||||
|
||||
import { getDesignConfig, TMagicSelect } from '@tmagic/design';
|
||||
import { getValueByKeyPath } from '@tmagic/utils';
|
||||
@ -123,6 +123,26 @@ const equalValue = (value: any, v: any): boolean => {
|
||||
return value === v;
|
||||
};
|
||||
|
||||
const hasOption = (value: any): boolean => {
|
||||
const { config } = props;
|
||||
|
||||
if (Array.isArray(value) && !value.length) return true;
|
||||
|
||||
if (config.group) {
|
||||
const groups = options.value as SelectGroupOption[];
|
||||
if (config.multiple && Array.isArray(value)) {
|
||||
return value.every((v) => groups.some((group) => group.options.some((item) => equalValue(item.value, v))));
|
||||
}
|
||||
return groups.some((group) => group.options.some((item) => equalValue(item.value, value)));
|
||||
}
|
||||
|
||||
const opts = options.value as SelectOption[];
|
||||
if (config.multiple && Array.isArray(value)) {
|
||||
return value.every((v) => opts.some((item) => equalValue(item.value, v)));
|
||||
}
|
||||
return opts.some((item) => equalValue(item.value, value));
|
||||
};
|
||||
|
||||
const mapOptions = (data: any[]) => {
|
||||
const {
|
||||
option = {
|
||||
@ -392,15 +412,22 @@ if (typeof props.config.options === 'function') {
|
||||
setOptions(props.config.options as SelectOption[] | SelectGroupOption[]);
|
||||
});
|
||||
} else if (props.config.option) {
|
||||
onBeforeMount(() => {
|
||||
const initOption = async () => {
|
||||
if (!props.model) return;
|
||||
const v = props.model[props.name];
|
||||
if (Array.isArray(v) ? v.length : typeof v !== 'undefined') {
|
||||
getInitOption().then((data) => {
|
||||
setOptions(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
if (Array.isArray(v) ? !v.length : typeof v === 'undefined') return;
|
||||
if (typeof v === 'undefined' || hasOption(v)) return;
|
||||
const data = await getInitOption();
|
||||
setOptions(data);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.model?.[props.name],
|
||||
() => {
|
||||
void initOption();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
}
|
||||
|
||||
if (props.config.remote) {
|
||||
|
||||
@ -203,3 +203,132 @@ describe('Select - getInitOption empty value', () => {
|
||||
expect(wrapper.findComponent(MSelect).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Select - config.option model value watch', () => {
|
||||
let request: ReturnType<typeof vi.fn>;
|
||||
|
||||
const mountFormWithRequest = (config: any[], initValues: any = {}) =>
|
||||
mount(MForm, {
|
||||
global: { plugins: [ElementPlus as any, [MagicForm as any, { request }]] },
|
||||
props: { config, initValues },
|
||||
});
|
||||
|
||||
const flushAsync = async () => {
|
||||
await nextTick();
|
||||
await new Promise((r) => setTimeout(r, 0));
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
const buildConfig = (extra: any = {}) => [
|
||||
{
|
||||
name: 's',
|
||||
type: 'select',
|
||||
text: 's',
|
||||
option: {
|
||||
url: 'https://example.com/list',
|
||||
initUrl: 'https://example.com/init',
|
||||
initRoot: 'data.list',
|
||||
...extra,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
request = vi.fn((postOptions: Record<string, any>) => {
|
||||
const id = postOptions.data?.id;
|
||||
const ids = Array.isArray(id) ? id : [id];
|
||||
return Promise.resolve({
|
||||
data: {
|
||||
list: ids.map((value: string) => ({ text: `Label-${value}`, value })),
|
||||
},
|
||||
});
|
||||
});
|
||||
setConfig({ request });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setConfig({});
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
test('model 值变化且 options 中无对应项时重新 getInitOption', async () => {
|
||||
const wrapper = mountFormWithRequest(buildConfig(), { s: 'x' });
|
||||
await flushAsync();
|
||||
|
||||
expect(request).toHaveBeenCalledTimes(1);
|
||||
expect(request.mock.calls[0][0].data).toMatchObject({ id: 'x' });
|
||||
|
||||
const select = wrapper.findComponent(MSelect);
|
||||
expect((select.vm as any).options[0]).toMatchObject({ text: 'Label-x', value: 'x' });
|
||||
|
||||
(wrapper.vm as any).values.s = 'y';
|
||||
await flushAsync();
|
||||
|
||||
expect(request).toHaveBeenCalledTimes(2);
|
||||
expect(request.mock.calls[1][0].url).toBe('https://example.com/init');
|
||||
expect(request.mock.calls[1][0].data).toMatchObject({ id: 'y' });
|
||||
expect((select.vm as any).options[0]).toMatchObject({ text: 'Label-y', value: 'y' });
|
||||
});
|
||||
|
||||
test('model 值变化但 options 已包含对应项时不重复请求', async () => {
|
||||
const wrapper = mountFormWithRequest(buildConfig(), { s: 'x' });
|
||||
await flushAsync();
|
||||
|
||||
(wrapper.vm as any).values.s = 'y';
|
||||
await flushAsync();
|
||||
expect(request).toHaveBeenCalledTimes(2);
|
||||
|
||||
request.mockClear();
|
||||
(wrapper.vm as any).values.s = 'y';
|
||||
await flushAsync();
|
||||
|
||||
expect(request).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('model 值变为 undefined 时不发起 init 请求', async () => {
|
||||
const wrapper = mountFormWithRequest(buildConfig(), { s: 'x' });
|
||||
await flushAsync();
|
||||
const callCount = request.mock.calls.length;
|
||||
|
||||
(wrapper.vm as any).values.s = undefined;
|
||||
await flushAsync();
|
||||
|
||||
expect(request.mock.calls.length).toBe(callCount);
|
||||
});
|
||||
|
||||
test('multiple:model 值变化且缺少选项时重新 getInitOption', async () => {
|
||||
const wrapper = mountFormWithRequest(
|
||||
[
|
||||
{
|
||||
name: 's',
|
||||
type: 'select',
|
||||
text: 's',
|
||||
multiple: true,
|
||||
option: {
|
||||
url: 'https://example.com/list',
|
||||
initUrl: 'https://example.com/init',
|
||||
initRoot: 'data.list',
|
||||
},
|
||||
},
|
||||
],
|
||||
{ s: ['x'] },
|
||||
);
|
||||
await flushAsync();
|
||||
|
||||
const select = wrapper.findComponent(MSelect);
|
||||
expect((select.vm as any).options).toEqual(
|
||||
expect.arrayContaining([expect.objectContaining({ text: 'Label-x', value: 'x' })]),
|
||||
);
|
||||
|
||||
(wrapper.vm as any).values.s = ['x', 'y'];
|
||||
await flushAsync();
|
||||
|
||||
expect(request.mock.calls.at(-1)?.[0].data).toMatchObject({ id: ['x', 'y'] });
|
||||
expect((select.vm as any).options).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ text: 'Label-x', value: 'x' }),
|
||||
expect.objectContaining({ text: 'Label-y', value: 'y' }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user