mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:06:51 +08:00
fix: app icon could not only change background (#35537)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
5e336c47fd
commit
9bd5c2f8ec
@ -1080,11 +1080,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/emoji-picker/Inner.tsx": {
|
||||
"react/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"web/app/components/base/emoji-picker/index.tsx": {
|
||||
"no-restricted-imports": {
|
||||
"count": 1
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { ComponentProps } from 'react'
|
||||
import type { Area } from 'react-easy-crop'
|
||||
import type { ImageFile } from '@/types/app'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
@ -122,11 +123,11 @@ describe('AppIconPicker', () => {
|
||||
})
|
||||
}
|
||||
|
||||
const renderPicker = () => {
|
||||
const renderPicker = (props: Partial<ComponentProps<typeof AppIconPicker>> = {}) => {
|
||||
const onSelect = vi.fn()
|
||||
const onClose = vi.fn()
|
||||
|
||||
const { container } = render(<AppIconPicker onSelect={onSelect} onClose={onClose} />)
|
||||
const { container } = render(<AppIconPicker onSelect={onSelect} onClose={onClose} {...props} />)
|
||||
|
||||
return { onSelect, onClose, container }
|
||||
}
|
||||
@ -220,6 +221,20 @@ describe('AppIconPicker', () => {
|
||||
|
||||
expect(onSelect).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should submit the initial emoji when provided', async () => {
|
||||
const { onSelect } = renderPicker({ initialEmoji: { icon: 'rabbit', background: '#E4FBCC' } })
|
||||
|
||||
await userEvent.click(screen.getByText(/ok/i))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSelect).toHaveBeenCalledWith({
|
||||
type: 'emoji',
|
||||
icon: 'rabbit',
|
||||
background: '#E4FBCC',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Image Upload', () => {
|
||||
|
||||
@ -34,12 +34,17 @@ export type AppIconSelection = AppIconEmojiSelection | AppIconImageSelection
|
||||
type AppIconPickerProps = {
|
||||
onSelect?: (payload: AppIconSelection) => void
|
||||
onClose?: () => void
|
||||
initialEmoji?: {
|
||||
icon: string
|
||||
background?: string | null
|
||||
}
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
onSelect,
|
||||
onClose,
|
||||
initialEmoji,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
@ -138,7 +143,14 @@ const AppIconPicker: FC<AppIconPickerProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'emoji' && <EmojiPickerInner className={cn('flex-1 overflow-hidden pt-2')} onSelect={handleSelectEmoji} />}
|
||||
{activeTab === 'emoji' && (
|
||||
<EmojiPickerInner
|
||||
className={cn('flex-1 overflow-hidden pt-2')}
|
||||
emoji={initialEmoji?.icon}
|
||||
background={initialEmoji?.background ?? undefined}
|
||||
onSelect={handleSelectEmoji}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'image' && <ImageInput className={cn('flex-1 overflow-hidden')} onImageInput={handleImageInput} />}
|
||||
|
||||
<Divider className="m-0" />
|
||||
|
||||
@ -45,20 +45,21 @@ type IEmojiPickerInnerProps = {
|
||||
}
|
||||
|
||||
const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
||||
emoji,
|
||||
background,
|
||||
onSelect,
|
||||
className,
|
||||
}) => {
|
||||
const { categories } = data as EmojiMartData
|
||||
const [selectedEmoji, setSelectedEmoji] = useState('')
|
||||
const [selectedBackground, setSelectedBackground] = useState(backgroundColors[0])
|
||||
const [showStyleColors, setShowStyleColors] = useState(false)
|
||||
const [selectedEmoji, setSelectedEmoji] = useState(emoji || '')
|
||||
const [selectedBackground, setSelectedBackground] = useState(background || backgroundColors[0])
|
||||
const [showStyleColors, setShowStyleColors] = useState(!!emoji)
|
||||
|
||||
const [searchedEmojis, setSearchedEmojis] = useState<string[]>([])
|
||||
const [isSearching, setIsSearching] = useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (selectedEmoji) {
|
||||
setShowStyleColors(true)
|
||||
/* v8 ignore next 2 - @preserve */
|
||||
if (selectedBackground)
|
||||
onSelect?.(selectedEmoji, selectedBackground)
|
||||
@ -105,6 +106,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg"
|
||||
onClick={() => {
|
||||
setSelectedEmoji(emoji)
|
||||
setShowStyleColors(true)
|
||||
}}
|
||||
>
|
||||
<div className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg p-1 ring-components-input-border-hover ring-offset-1 hover:ring-1" data-testid={`emoji-search-result-${emoji}`}>
|
||||
@ -130,6 +132,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
||||
className="inline-flex h-10 w-10 items-center justify-center rounded-lg"
|
||||
onClick={() => {
|
||||
setSelectedEmoji(emoji)
|
||||
setShowStyleColors(true)
|
||||
}}
|
||||
>
|
||||
<div className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg p-1 ring-components-input-border-hover ring-offset-1 hover:ring-1" data-testid={`emoji-container-${emoji}`}>
|
||||
|
||||
@ -45,6 +45,15 @@ describe('EmojiPickerInner', () => {
|
||||
expect(screen.getByText('food'))!.toBeInTheDocument()
|
||||
expect(screen.getByPlaceholderText('Search emojis...'))!.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('initializes selected emoji and background when provided', async () => {
|
||||
render(<EmojiPickerInner emoji="rabbit" background="#E4FBCC" onSelect={mockOnSelect} />)
|
||||
|
||||
expect(screen.getByText('Choose Style'))!.toBeInTheDocument()
|
||||
await waitFor(() => {
|
||||
expect(mockOnSelect).toHaveBeenCalledWith('rabbit', '#E4FBCC')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('User Interactions', () => {
|
||||
|
||||
@ -359,7 +359,7 @@ describe('CreateAppModal', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should reset emoji icon to initial props when picker is cancelled', async () => {
|
||||
it('should allow changing only the background for the current emoji icon', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const { onConfirm } = await setup({
|
||||
@ -370,22 +370,14 @@ describe('CreateAppModal', () => {
|
||||
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
|
||||
const categoryLabel = screen.getByText('people')
|
||||
const emojiGrid = categoryLabel.nextElementSibling
|
||||
const clickableEmojiWrapper = emojiGrid?.firstElementChild
|
||||
if (!(clickableEmojiWrapper instanceof HTMLElement))
|
||||
throw new Error('Failed to locate emoji wrapper')
|
||||
fireEvent.click(clickableEmojiWrapper)
|
||||
const colorOption = Array.from(document.querySelectorAll('[style^="background:"]'))
|
||||
.find(element => element.getAttribute('style')?.includes('#E4FBCC'))
|
||||
if (!(colorOption instanceof HTMLElement) || !(colorOption.parentElement instanceof HTMLElement))
|
||||
throw new Error('Failed to locate background color option')
|
||||
|
||||
fireEvent.click(colorOption.parentElement)
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.ok' }))
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(getAppIconTrigger())
|
||||
fireEvent.click(screen.getByRole('button', { name: 'app.iconPicker.cancel' }))
|
||||
|
||||
expect(screen.queryByRole('button', { name: 'app.iconPicker.cancel' })).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /common\.operation\.create/ }))
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(300)
|
||||
@ -396,7 +388,7 @@ describe('CreateAppModal', () => {
|
||||
expect(payload).toMatchObject({
|
||||
icon_type: 'emoji',
|
||||
icon: '🤖',
|
||||
icon_background: '#FFEAD5',
|
||||
icon_background: '#E4FBCC',
|
||||
})
|
||||
}
|
||||
finally {
|
||||
|
||||
@ -206,14 +206,14 @@ const CreateAppModal = ({
|
||||
</Modal>
|
||||
{showAppIconPicker && (
|
||||
<AppIconPicker
|
||||
initialEmoji={appIcon.type === 'emoji'
|
||||
? { icon: appIcon.icon, background: appIcon.background }
|
||||
: undefined}
|
||||
onSelect={(payload) => {
|
||||
setAppIcon(payload)
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setAppIcon(appIconType === 'image'
|
||||
? { type: 'image' as const, url: appIconUrl, fileId: _appIcon }
|
||||
: { type: 'emoji' as const, icon: _appIcon, background: appIconBackground })
|
||||
setShowAppIconPicker(false)
|
||||
}}
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user