From 26c09b7de6f5182a357f11f3668ff8cf9d645ca8 Mon Sep 17 00:00:00 2001 From: "chihao.ou" Date: Sat, 19 Sep 2026 17:56:21 +0800 Subject: [PATCH] fix(users): persist cleared custom attribute hints --- .../user/UserAttributesConfigModal.vue | 4 +- .../UserAttributesConfigModal.spec.ts | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/user/__tests__/UserAttributesConfigModal.spec.ts diff --git a/frontend/src/components/user/UserAttributesConfigModal.vue b/frontend/src/components/user/UserAttributesConfigModal.vue index 9aa41a47a2..2d8e050034 100644 --- a/frontend/src/components/user/UserAttributesConfigModal.vue +++ b/frontend/src/components/user/UserAttributesConfigModal.vue @@ -353,8 +353,8 @@ const handleSave = async () => { key: form.key, name: form.name, type: form.type, - description: form.description || undefined, - placeholder: form.placeholder || undefined, + description: form.description, + placeholder: form.placeholder, required: form.required, enabled: form.enabled, options: (form.type === 'select' || form.type === 'multi_select') ? form.options : undefined diff --git a/frontend/src/components/user/__tests__/UserAttributesConfigModal.spec.ts b/frontend/src/components/user/__tests__/UserAttributesConfigModal.spec.ts new file mode 100644 index 0000000000..c38a2f9732 --- /dev/null +++ b/frontend/src/components/user/__tests__/UserAttributesConfigModal.spec.ts @@ -0,0 +1,57 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' +import UserAttributesConfigModal from '../UserAttributesConfigModal.vue' + +const mocks = vi.hoisted(() => ({ listDefinitions: vi.fn(), updateDefinition: vi.fn(), createDefinition: vi.fn() })) +vi.mock('@/api/admin', () => ({ adminAPI: { userAttributes: mocks } })) +vi.mock('@/stores/app', () => ({ useAppStore: () => ({ showSuccess: vi.fn(), showError: vi.fn() }) })) +vi.mock('vue-i18n', () => ({ useI18n: () => ({ t: (key: string) => key }) })) +enableAutoUnmount(afterEach) +beforeEach(() => { + vi.clearAllMocks() + mocks.listDefinitions.mockResolvedValue([{ id: 1, key: 'note', name: 'Note', type: 'text', description: 'Old description', placeholder: 'Old placeholder', required: false, enabled: true }]) + mocks.updateDefinition.mockResolvedValue(undefined) + mocks.createDefinition.mockResolvedValue(undefined) +}) +async function openConfig() { + const wrapper = mount(UserAttributesConfigModal, { + props: { show: false }, global: { stubs: { + BaseDialog: { props: ['show'], template: '
' }, + ConfirmDialog: true, Select: true, Icon: true + } } + }) + await wrapper.setProps({ show: true }) + await flushPromises() + return wrapper +} + +describe('UserAttributesConfigModal optional text fields', () => { + it.each([ + ['', 'Old placeholder'], + ['Old description', ''], + ['', ''], + ['New description', 'New placeholder'] + ])('persists description %j and placeholder %j explicitly', async (description, placeholder) => { + const wrapper = await openConfig() + await wrapper.get('button[title="common.edit"]').trigger('click') + await wrapper.get('input[placeholder="admin.users.attributes.fieldDescriptionHint"]').setValue(description) + await wrapper.get('input[placeholder="admin.users.attributes.placeholderHint"]').setValue(placeholder) + await wrapper.get('form').trigger('submit') + await flushPromises() + expect(mocks.updateDefinition).toHaveBeenCalledTimes(1) + const [id, data] = mocks.updateDefinition.mock.calls[0]! + expect(id).toBe(1) + expect(JSON.parse(JSON.stringify(data))).toEqual({ key: 'note', name: 'Note', type: 'text', description, placeholder, required: false, enabled: true }) + }) + + it('still creates an attribute without optional text', async () => { + const wrapper = await openConfig() + await wrapper.findAll('button').find(b => b.text() === 'admin.users.attributes.addAttribute')!.trigger('click') + await wrapper.get('input[placeholder="admin.users.attributes.keyHint"]').setValue('note') + await wrapper.get('input[placeholder="admin.users.attributes.nameHint"]').setValue('Note') + await wrapper.get('form').trigger('submit') + await flushPromises() + expect(mocks.createDefinition).toHaveBeenCalledWith(expect.objectContaining({ key: 'note', name: 'Note', type: 'text' })) + expect(mocks.updateDefinition).not.toHaveBeenCalled() + }) +})