From e64d86660d83769b498de05d221b900a8c9c5b3c Mon Sep 17 00:00:00 2001 From: roymondchen Date: Fri, 15 May 2026 19:32:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(form):=20=E4=BF=AE=E5=A4=8D=20Select=20?= =?UTF-8?q?=E5=9C=A8=20value=20=E4=B8=BA=E7=A9=BA=E6=97=B6=E4=BB=8D?= =?UTF-8?q?=E5=8F=91=E8=B5=B7=20initUrl=20=E8=AF=B7=E6=B1=82=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- packages/form/src/fields/Select.vue | 8 ++ .../form/tests/unit/fields/Select.spec.ts | 111 +++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/packages/form/src/fields/Select.vue b/packages/form/src/fields/Select.vue index 81303bf2..26cc8ddf 100644 --- a/packages/form/src/fields/Select.vue +++ b/packages/form/src/fields/Select.vue @@ -291,6 +291,14 @@ const getInitOption = async () => { return getInitLocalOption(); } + if ( + typeof props.model[props.name] === 'undefined' || + props.model[props.name] === '' || + props.model[props.name] === null + ) { + return []; + } + if (typeof url === 'function') { url = await url(mForm, { model: props.model, formValue: mForm?.values }); } diff --git a/packages/form/tests/unit/fields/Select.spec.ts b/packages/form/tests/unit/fields/Select.spec.ts index d7460d32..c36abefa 100644 --- a/packages/form/tests/unit/fields/Select.spec.ts +++ b/packages/form/tests/unit/fields/Select.spec.ts @@ -3,9 +3,10 @@ * * Copyright (C) 2025 Tencent. */ -import { describe, expect, test } from 'vitest'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { nextTick } from 'vue'; import MagicForm, { MForm, MSelect } from '@form/index'; +import { setConfig } from '@form/utils/config'; import { mount } from '@vue/test-utils'; import ElementPlus from 'element-plus'; @@ -94,3 +95,111 @@ describe('Select', () => { expect(wrapper.findComponent(MSelect).exists()).toBe(true); }); }); + +describe('Select - getInitOption empty value', () => { + let request: ReturnType; + + const mountFormWithRequest = (config: any[], initValues: any = {}) => + mount(MForm, { + global: { plugins: [ElementPlus as any, [MagicForm as any, { request }]] }, + props: { config, initValues }, + }); + + beforeEach(() => { + request = vi.fn(async () => ({ data: { list: [{ text: 'X', value: 'x' }] } })); + setConfig({ request }); + }); + + afterEach(() => { + setConfig({}); + vi.restoreAllMocks(); + }); + + const buildConfig = (extra: any = {}) => [ + { + name: 's', + type: 'select', + text: 's', + option: { + url: 'https://example.com/list', + initUrl: 'https://example.com/init', + ...extra, + }, + }, + ]; + + test('value 为空字符串时不发起 init 请求且 options 为空', async () => { + const wrapper = mountFormWithRequest(buildConfig(), { s: '' }); + await nextTick(); + await new Promise((r) => setTimeout(r, 0)); + await nextTick(); + + expect(request).not.toHaveBeenCalled(); + const select = wrapper.findComponent(MSelect); + expect(select.exists()).toBe(true); + expect((select.vm as any).options).toEqual([]); + }); + + test('value 为 null 时不发起 init 请求且 options 为空', async () => { + const wrapper = mountFormWithRequest(buildConfig(), { s: null }); + await nextTick(); + await new Promise((r) => setTimeout(r, 0)); + await nextTick(); + + expect(request).not.toHaveBeenCalled(); + const select = wrapper.findComponent(MSelect); + expect((select.vm as any).options).toEqual([]); + }); + + test('value 非空时正常发起 init 请求并填充 options', async () => { + const wrapper = mountFormWithRequest(buildConfig({ initRoot: 'data.list' }), { s: 'x' }); + await nextTick(); + await new Promise((r) => setTimeout(r, 0)); + await nextTick(); + + expect(request).toHaveBeenCalledTimes(1); + const callArg = request.mock.calls[0][0]; + expect(callArg.url).toBe('https://example.com/init'); + expect(callArg.data).toMatchObject({ id: 'x' }); + + const select = wrapper.findComponent(MSelect); + const opts = (select.vm as any).options; + expect(Array.isArray(opts)).toBe(true); + expect(opts.length).toBeGreaterThan(0); + expect(opts[0]).toMatchObject({ text: 'X', value: 'x' }); + }); + + test('value 为 undefined 时不会调用 getInitOption(onBeforeMount 已过滤)', async () => { + const wrapper = mountFormWithRequest(buildConfig(), {}); + await nextTick(); + await new Promise((r) => setTimeout(r, 0)); + await nextTick(); + + expect(request).not.toHaveBeenCalled(); + const select = wrapper.findComponent(MSelect); + expect((select.vm as any).options).toEqual([]); + }); + + test('未配置 initUrl 时(仅 url)走本地选项分支并发起请求', async () => { + const wrapper = mountFormWithRequest( + [ + { + name: 's', + type: 'select', + text: 's', + option: { + url: 'https://example.com/list', + }, + }, + ], + { s: 'x' }, + ); + await nextTick(); + await new Promise((r) => setTimeout(r, 0)); + await nextTick(); + + expect(request).toHaveBeenCalled(); + expect(request.mock.calls[0][0].url).toBe('https://example.com/list'); + expect(wrapper.findComponent(MSelect).exists()).toBe(true); + }); +});