chore: file type not show hidden

This commit is contained in:
Joel 2026-04-20 08:53:18 +08:00
parent 21420eab64
commit 27791ac121
5 changed files with 77 additions and 40 deletions

View File

@ -166,6 +166,12 @@ describe('ConfigModalFormFields', () => {
})
it('should wire file, json schema, and visibility controls', () => {
const textInputProps = createBaseProps()
const textInputView = render(<ConfigModalFormFields {...textInputProps} />)
expect(screen.getByText('variableConfig.hidden')).toBeInTheDocument()
expect(screen.getByText('variableConfig.hiddenDescription')).toBeInTheDocument()
textInputView.unmount()
const singleFileProps = createBaseProps()
singleFileProps.tempPayload = {
...singleFileProps.tempPayload,
@ -174,20 +180,20 @@ describe('ConfigModalFormFields', () => {
allowed_file_extensions: [],
allowed_file_upload_methods: ['remote_url'],
}
render(<ConfigModalFormFields {...singleFileProps} />)
expect(screen.getByText('variableConfig.hidden')).toBeInTheDocument()
expect(screen.getByText('variableConfig.hiddenDescription')).toBeInTheDocument()
const singleFileView = render(<ConfigModalFormFields {...singleFileProps} />)
expect(screen.queryByText('variableConfig.hidden')).not.toBeInTheDocument()
expect(screen.queryByText('variableConfig.hiddenDescription')).not.toBeInTheDocument()
fireEvent.click(screen.getByText('single-file-setting'))
fireEvent.click(screen.getByText('upload-file'))
fireEvent.click(screen.getAllByText('unchecked')[0])
fireEvent.click(screen.getAllByText('unchecked')[1])
fireEvent.click(screen.getByText('unchecked'))
expect(singleFileProps.onFilePayloadChange).toHaveBeenCalledWith({ number_limits: 1 })
expect(singleFileProps.payloadChangeHandlers.default).toHaveBeenCalledWith(expect.objectContaining({
fileId: 'file-1',
}))
expect(singleFileProps.payloadChangeHandlers.required).toHaveBeenCalledWith(true)
expect(singleFileProps.payloadChangeHandlers.hide).toHaveBeenCalledWith(true)
expect(singleFileProps.payloadChangeHandlers.hide).not.toHaveBeenCalled()
singleFileView.unmount()
const multiFileProps = createBaseProps()
multiFileProps.tempPayload = {
@ -198,8 +204,9 @@ describe('ConfigModalFormFields', () => {
allowed_file_upload_methods: ['remote_url'],
}
render(<ConfigModalFormFields {...multiFileProps} />)
expect(screen.queryByText('variableConfig.hidden')).not.toBeInTheDocument()
fireEvent.click(screen.getByText('multi-file-setting'))
fireEvent.click(screen.getAllByText('upload-file')[1])
fireEvent.click(screen.getByText('upload-file'))
expect(multiFileProps.onFilePayloadChange).toHaveBeenCalledWith({ number_limits: 3 })
expect(multiFileProps.payloadChangeHandlers.default).toHaveBeenCalledWith([
expect.objectContaining({ fileId: 'file-1' }),

View File

@ -25,6 +25,7 @@ vi.mock('../form-fields', () => ({
return (
<div data-testid="config-form-fields">
<div data-testid="payload-type">{String(props.tempPayload.type)}</div>
<div data-testid="payload-hide">{String(props.tempPayload.hide)}</div>
<div data-testid="payload-label">{String(props.tempPayload.label ?? '')}</div>
<div data-testid="payload-schema">{String(props.tempPayload.json_schema ?? '')}</div>
<div data-testid="payload-default">{String(props.tempPayload.default ?? '')}</div>
@ -115,7 +116,7 @@ describe('ConfigModal logic', () => {
})
it('should derive payload fields from mocked form-field callbacks', async () => {
renderConfigModal()
renderConfigModal(createPayload({ hide: true }))
fireEvent.click(screen.getByTestId('valid-key-blur'))
await waitFor(() => {
@ -138,6 +139,7 @@ describe('ConfigModal logic', () => {
fireEvent.click(screen.getByTestId('type-change'))
await waitFor(() => {
expect(screen.getByTestId('payload-type')).toHaveTextContent(InputVarType.singleFile)
expect(screen.getByTestId('payload-hide')).toHaveTextContent('false')
})
fireEvent.click(screen.getByTestId('file-payload-change'))

View File

@ -49,11 +49,13 @@ describe('config-modal utils', () => {
const payload = createInputVar({
type: InputVarType.textInput,
default: 'hello',
hide: true,
})
const nextPayload = createPayloadForType(payload, InputVarType.multiFiles)
expect(nextPayload.type).toBe(InputVarType.multiFiles)
expect(nextPayload.hide).toBe(false)
expect(nextPayload.max_length).toBe(DEFAULT_FILE_UPLOAD_SETTING.max_length)
expect(nextPayload.allowed_file_types).toEqual(DEFAULT_FILE_UPLOAD_SETTING.allowed_file_types)
expect(nextPayload.default).toBe('hello')
@ -249,6 +251,24 @@ describe('config-modal utils', () => {
})
})
it('should force file inputs to stay visible when saving', () => {
const result = validateConfigModalPayload({
tempPayload: createInputVar({
type: InputVarType.singleFile,
hide: true,
allowed_file_types: [SupportUploadFileTypes.document],
allowed_file_extensions: [],
}),
payload: createInputVar(),
checkVariableName: () => true,
t,
})
expect(result.payloadToSave).toEqual(expect.objectContaining({
hide: false,
}))
})
it('should stop validation when the variable name checker rejects the payload', () => {
const result = validateConfigModalPayload({
tempPayload: createInputVar({

View File

@ -68,6 +68,7 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
t,
}) => {
const { type, label, variable } = tempPayload
const isFileInput = [InputVarType.singleFile, InputVarType.multiFiles].includes(type)
return (
<div className="space-y-2">
@ -174,7 +175,7 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
</>
)}
{[InputVarType.singleFile, InputVarType.multiFiles].includes(type) && (
{isFileInput && (
<>
<FileUploadSetting
payload={tempPayload as UploadFileSetting}
@ -215,26 +216,28 @@ const ConfigModalFormFields: FC<ConfigModalFormFieldsProps> = ({
)}
<div className="mt-5! flex h-6 items-center space-x-2">
<Checkbox checked={tempPayload.required} disabled={tempPayload.hide} onCheck={() => onPayloadChange('required')(!tempPayload.required)} />
<Checkbox checked={tempPayload.required} disabled={!isFileInput && tempPayload.hide} onCheck={() => onPayloadChange('required')(!tempPayload.required)} />
<span className="system-sm-semibold text-text-secondary">{t('variableConfig.required', { ns: 'appDebug' })}</span>
</div>
<div className="mt-5! flex h-6 items-center space-x-2">
<Checkbox checked={tempPayload.hide} disabled={tempPayload.required} onCheck={() => onPayloadChange('hide')(!tempPayload.hide)} />
<div className="flex items-center gap-1">
<span className="system-sm-semibold text-text-secondary">{t('variableConfig.hidden', { ns: 'appDebug' })}</span>
<Tooltip>
<TooltipTrigger>
<div className="flex h-4 w-4 items-center justify-center">
<RiQuestionLine className="h-3.5 w-3.5 text-text-tertiary" />
</div>
</TooltipTrigger>
<TooltipContent>
{t('variableConfig.hiddenDescription', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
{!isFileInput && (
<div className="mt-5! flex h-6 items-center space-x-2">
<Checkbox checked={tempPayload.hide} disabled={tempPayload.required} onCheck={() => onPayloadChange('hide')(!tempPayload.hide)} />
<div className="flex items-center gap-1">
<span className="system-sm-semibold text-text-secondary">{t('variableConfig.hidden', { ns: 'appDebug' })}</span>
<Tooltip>
<TooltipTrigger>
<div className="flex h-4 w-4 items-center justify-center">
<RiQuestionLine className="h-3.5 w-3.5 text-text-tertiary" />
</div>
</TooltipTrigger>
<TooltipContent>
{t('variableConfig.hiddenDescription', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
</div>
</div>
</div>
)}
</div>
)
}

View File

@ -88,7 +88,9 @@ export const createPayloadForType = (payload: InputVar, type: InputVarType) => {
draft.default = undefined
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING) as Array<keyof typeof DEFAULT_FILE_UPLOAD_SETTING>).forEach((key) => {
draft.hide = false
const fileUploadSettingKeys = Object.keys(DEFAULT_FILE_UPLOAD_SETTING) as Array<keyof typeof DEFAULT_FILE_UPLOAD_SETTING>
fileUploadSettingKeys.forEach((key) => {
if (key !== 'max_length')
draft[key] = DEFAULT_FILE_UPLOAD_SETTING[key] as never
})
@ -158,38 +160,41 @@ export const validateConfigModalPayload = ({
checkVariableName,
t,
}: ValidateConfigModalPayloadOptions): ValidateConfigModalPayloadResult => {
const normalizedTempPayload = [InputVarType.singleFile, InputVarType.multiFiles].includes(tempPayload.type)
? { ...tempPayload, hide: false }
: tempPayload
const jsonSchemaValue = tempPayload.json_schema
const schemaEmpty = isJsonSchemaEmpty(jsonSchemaValue)
const normalizedJsonSchema = schemaEmpty ? undefined : jsonSchemaValue
const payloadToSave = tempPayload.type === InputVarType.jsonObject && schemaEmpty
? { ...tempPayload, json_schema: undefined }
: tempPayload
const payloadToSave = normalizedTempPayload.type === InputVarType.jsonObject && schemaEmpty
? { ...normalizedTempPayload, json_schema: undefined }
: normalizedTempPayload
const moreInfo = tempPayload.variable === payload?.variable
const moreInfo = normalizedTempPayload.variable === payload?.variable
? undefined
: {
type: ChangeType.changeVarName,
payload: { beforeKey: payload?.variable || '', afterKey: tempPayload.variable },
payload: { beforeKey: payload?.variable || '', afterKey: normalizedTempPayload.variable },
}
if (!checkVariableName(tempPayload.variable))
if (!checkVariableName(normalizedTempPayload.variable))
return {}
if (!tempPayload.label) {
if (!normalizedTempPayload.label) {
return {
errorMessage: t('variableConfig.errorMsg.labelNameRequired', { ns: 'appDebug' }),
}
}
if (tempPayload.type === InputVarType.select) {
if (!tempPayload.options?.length) {
if (normalizedTempPayload.type === InputVarType.select) {
if (!normalizedTempPayload.options?.length) {
return {
errorMessage: t('variableConfig.errorMsg.atLeastOneOption', { ns: 'appDebug' }),
}
}
const duplicated = new Set<string>()
const hasRepeatedItem = tempPayload.options.some((option) => {
const hasRepeatedItem = normalizedTempPayload.options.some((option) => {
if (duplicated.has(option))
return true
@ -204,8 +209,8 @@ export const validateConfigModalPayload = ({
}
}
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(tempPayload.type)) {
if (!tempPayload.allowed_file_types?.length) {
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(normalizedTempPayload.type)) {
if (!normalizedTempPayload.allowed_file_types?.length) {
return {
errorMessage: t('errorMsg.fieldRequired', {
ns: 'workflow',
@ -214,7 +219,7 @@ export const validateConfigModalPayload = ({
}
}
if (tempPayload.allowed_file_types.includes(SupportUploadFileTypes.custom) && !tempPayload.allowed_file_extensions?.length) {
if (normalizedTempPayload.allowed_file_types.includes(SupportUploadFileTypes.custom) && !normalizedTempPayload.allowed_file_extensions?.length) {
return {
errorMessage: t('errorMsg.fieldRequired', {
ns: 'workflow',
@ -224,7 +229,7 @@ export const validateConfigModalPayload = ({
}
}
if (tempPayload.type === InputVarType.jsonObject && !schemaEmpty && typeof normalizedJsonSchema === 'string') {
if (normalizedTempPayload.type === InputVarType.jsonObject && !schemaEmpty && typeof normalizedJsonSchema === 'string') {
try {
const schema = JSON.parse(normalizedJsonSchema)
if (schema?.type !== 'object') {