diff --git a/web/app/components/explore/app-card/index.spec.tsx b/web/app/components/explore/app-card/index.spec.tsx
new file mode 100644
index 0000000000..ee09a2ad26
--- /dev/null
+++ b/web/app/components/explore/app-card/index.spec.tsx
@@ -0,0 +1,96 @@
+import React from 'react'
+import { fireEvent, render, screen } from '@testing-library/react'
+import AppCard, { type AppCardProps } from './index'
+import type { App } from '@/models/explore'
+import { AppModeEnum } from '@/types/app'
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}))
+
+jest.mock('@/app/components/base/app-icon', () => ({
+ __esModule: true,
+ default: ({ children }: any) =>
{children}
,
+}))
+
+jest.mock('../../app/type-selector', () => ({
+ AppTypeIcon: ({ type }: any) => {type}
,
+}))
+
+const createApp = (overrides?: Partial): App => ({
+ app_id: 'app-id',
+ description: 'App description',
+ copyright: '2024',
+ privacy_policy: null,
+ custom_disclaimer: null,
+ category: 'Assistant',
+ position: 1,
+ is_listed: true,
+ install_count: 0,
+ installed: false,
+ editable: true,
+ is_agent: false,
+ ...overrides,
+ app: {
+ id: 'id-1',
+ mode: AppModeEnum.CHAT,
+ icon_type: null,
+ icon: '🤖',
+ icon_background: '#fff',
+ icon_url: '',
+ name: 'Sample App',
+ description: 'App description',
+ use_icon_as_answer_icon: false,
+ ...overrides?.app,
+ },
+})
+
+describe('AppCard', () => {
+ const onCreate = jest.fn()
+
+ const renderComponent = (props?: Partial) => {
+ const mergedProps: AppCardProps = {
+ app: createApp(),
+ canCreate: false,
+ onCreate,
+ isExplore: false,
+ ...props,
+ }
+ return render()
+ }
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ it('should render app info with correct mode label when mode is CHAT', () => {
+ renderComponent({ app: createApp({ app: { ...createApp().app, mode: AppModeEnum.CHAT } }) })
+
+ expect(screen.getByText('Sample App')).toBeInTheDocument()
+ expect(screen.getByText('App description')).toBeInTheDocument()
+ expect(screen.getByText('APP.TYPES.CHATBOT')).toBeInTheDocument()
+ expect(screen.getByTestId('app-type-icon')).toHaveTextContent(AppModeEnum.CHAT)
+ })
+
+ it('should show create button in explore mode and trigger action', () => {
+ renderComponent({
+ app: createApp({ app: { ...createApp().app, mode: AppModeEnum.WORKFLOW } }),
+ canCreate: true,
+ isExplore: true,
+ })
+
+ const button = screen.getByText('explore.appCard.addToWorkspace')
+ expect(button).toBeInTheDocument()
+ fireEvent.click(button)
+ expect(onCreate).toHaveBeenCalledTimes(1)
+ expect(screen.getByText('APP.TYPES.WORKFLOW')).toBeInTheDocument()
+ })
+
+ it('should hide create button when not allowed', () => {
+ renderComponent({ canCreate: false, isExplore: true })
+
+ expect(screen.queryByText('explore.appCard.addToWorkspace')).not.toBeInTheDocument()
+ })
+})
diff --git a/web/app/components/share/text-generation/no-data/index.spec.tsx b/web/app/components/share/text-generation/no-data/index.spec.tsx
new file mode 100644
index 0000000000..20a8485f4c
--- /dev/null
+++ b/web/app/components/share/text-generation/no-data/index.spec.tsx
@@ -0,0 +1,21 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import NoData from './index'
+
+jest.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key: string) => key,
+ }),
+}))
+
+describe('NoData', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+ it('should render empty state icon and text when mounted', () => {
+ const { container } = render()
+
+ expect(container.querySelector('svg')).toBeInTheDocument()
+ expect(screen.getByText('share.generation.noData')).toBeInTheDocument()
+ })
+})