mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 18:27:19 +08:00
fix: preserve select migration behavior
This commit is contained in:
parent
df76733ed2
commit
3c99026328
@ -118,6 +118,25 @@ describe('BaseField', () => {
|
||||
expect(screen.queryByText('Beta')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not render current select value when it is filtered out by show_on conditions', () => {
|
||||
renderBaseField({
|
||||
formSchema: {
|
||||
type: FormTypeEnum.select,
|
||||
name: 'mode',
|
||||
label: 'Mode',
|
||||
required: false,
|
||||
options: [
|
||||
{ label: 'Alpha', value: 'alpha' },
|
||||
{ label: 'Beta', value: 'beta', show_on: [{ variable: 'enabled', value: 'yes' }] },
|
||||
],
|
||||
},
|
||||
defaultValues: { mode: 'beta', enabled: 'no' },
|
||||
})
|
||||
|
||||
expect(screen.getByRole('combobox', { name: 'Mode' })).not.toHaveTextContent('beta')
|
||||
expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('common.placeholder.input')
|
||||
})
|
||||
|
||||
it('should render dynamic select loading state', () => {
|
||||
mockDynamicOptions.mockReturnValue({
|
||||
data: undefined,
|
||||
|
||||
@ -51,6 +51,19 @@ const getTranslatedContent = ({ content, render }: {
|
||||
return ''
|
||||
}
|
||||
|
||||
type SelectOption = {
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
|
||||
const getSingleSelectValue = (value: unknown, options: SelectOption[]) => {
|
||||
return options.find(option => option.value === value)?.value ?? null
|
||||
}
|
||||
|
||||
const getSingleSelectLabel = (value: unknown, options: SelectOption[], placeholder: string | undefined) => {
|
||||
return options.find(option => option.value === value)?.label ?? placeholder
|
||||
}
|
||||
|
||||
const VALIDATE_STATUS_STYLE_MAP: Record<FormItemValidateStatusEnum, { componentClassName: string, textClassName: string, infoFieldName: string }> = {
|
||||
[FormItemValidateStatusEnum.Error]: {
|
||||
componentClassName: 'border-components-input-border-destructive focus:border-components-input-border-destructive',
|
||||
@ -267,7 +280,7 @@ const BaseField = ({
|
||||
: (
|
||||
<Select
|
||||
items={memorizedOptions}
|
||||
value={value || null}
|
||||
value={getSingleSelectValue(value, memorizedOptions)}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) => {
|
||||
if (next == null)
|
||||
@ -276,7 +289,9 @@ const BaseField = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id={field.name} aria-label={translatedLabel || field.name} className="px-2">
|
||||
<SelectValue placeholder={translatedPlaceholder} />
|
||||
<SelectValue placeholder={translatedPlaceholder}>
|
||||
{nextValue => getSingleSelectLabel(nextValue, memorizedOptions, translatedPlaceholder)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="max-h-[320px] w-(--anchor-width) bg-components-panel-bg-blur">
|
||||
{memorizedOptions.map(option => (
|
||||
@ -339,7 +354,7 @@ const BaseField = ({
|
||||
: (
|
||||
<Select
|
||||
items={dynamicOptions}
|
||||
value={value || null}
|
||||
value={getSingleSelectValue(value, dynamicOptions)}
|
||||
disabled={disabled || isDynamicOptionsLoading}
|
||||
onValueChange={(next) => {
|
||||
if (next == null)
|
||||
@ -348,7 +363,9 @@ const BaseField = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id={field.name} aria-label={translatedLabel || field.name} className="px-2">
|
||||
<SelectValue placeholder={dynamicPlaceholder} />
|
||||
<SelectValue placeholder={dynamicPlaceholder}>
|
||||
{nextValue => getSingleSelectLabel(nextValue, dynamicOptions, dynamicPlaceholder)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName="w-(--anchor-width) bg-components-panel-bg-blur">
|
||||
{dynamicNoticeTitle && (
|
||||
|
||||
@ -33,6 +33,22 @@ describe('SelectField', () => {
|
||||
expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('Alpha')
|
||||
})
|
||||
|
||||
it('should render the option label when selected value is an empty string', () => {
|
||||
mockField.state.value = ''
|
||||
|
||||
render(
|
||||
<SelectField
|
||||
label="Mode"
|
||||
options={[
|
||||
{ label: 'No default selected', value: '' },
|
||||
{ label: 'Alpha', value: 'alpha' },
|
||||
]}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('combobox', { name: 'Mode' })).toHaveTextContent('No default selected')
|
||||
})
|
||||
|
||||
it('should update value when users select another option', async () => {
|
||||
const user = userEvent.setup()
|
||||
render(
|
||||
|
||||
@ -21,10 +21,11 @@ describe('InputTypeSelectField', () => {
|
||||
})
|
||||
|
||||
it('should render label and selected option', () => {
|
||||
render(<InputTypeSelectField label="Input type" supportFile={true} />)
|
||||
const { container } = render(<InputTypeSelectField label="Input type" supportFile={true} />)
|
||||
|
||||
expect(screen.getByText('Input type')).toBeInTheDocument()
|
||||
expect(screen.getByText('appDebug.variableConfig.text-input')).toBeInTheDocument()
|
||||
expect(container.querySelector('[role="combobox"] span > div')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should update value when users choose another input type', async () => {
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import type { FileTypeSelectOption } from './types'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
|
||||
type TriggerProps = {
|
||||
option: FileTypeSelectOption | undefined
|
||||
@ -16,13 +15,13 @@ const Trigger = ({
|
||||
return <span className="grow p-1">{t('placeholder.select', { ns: 'common' })}</span>
|
||||
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-x-0.5">
|
||||
<>
|
||||
<option.Icon className="h-4 w-4 shrink-0 text-text-tertiary" />
|
||||
<span className="min-w-0 grow truncate p-1">{option.label}</span>
|
||||
<div className="pr-0.5">
|
||||
<Badge text={option.type} uppercase={false} />
|
||||
</div>
|
||||
</div>
|
||||
<span className="relative inline-flex h-5 shrink-0 items-center rounded-[5px] border border-divider-deep px-[5px] system-xs-medium leading-3 whitespace-nowrap text-text-tertiary">
|
||||
{option.type}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -18,6 +18,14 @@ export type Option = {
|
||||
value: string
|
||||
}
|
||||
|
||||
const getSelectedValue = (value: string | undefined, options: Option[]) => {
|
||||
return options.some(option => option.value === value) ? value : null
|
||||
}
|
||||
|
||||
const getDisplayLabel = (value: string | null, options: Option[], placeholder: string) => {
|
||||
return options.find(option => option.value === value)?.label ?? placeholder
|
||||
}
|
||||
|
||||
type SelectFieldPopupProps = {
|
||||
className?: string
|
||||
title?: string
|
||||
@ -58,7 +66,7 @@ const SelectField = ({
|
||||
/>
|
||||
<Select
|
||||
items={options}
|
||||
value={field.state.value ?? null}
|
||||
value={getSelectedValue(field.state.value, options)}
|
||||
disabled={disabled}
|
||||
onValueChange={(next) => {
|
||||
if (next == null)
|
||||
@ -68,7 +76,9 @@ const SelectField = ({
|
||||
}}
|
||||
>
|
||||
<SelectTrigger id={field.name} className="px-2">
|
||||
<SelectValue placeholder={placeholderText} />
|
||||
<SelectValue placeholder={placeholderText}>
|
||||
{(nextValue: string | null) => getDisplayLabel(nextValue, options, placeholderText)}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent popupClassName={cn('w-(--anchor-width) bg-components-panel-bg-blur', popupProps?.className)}>
|
||||
{popupProps?.title && (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user