fix: remove explore context and migrate query to orpc contract (#32320)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
yyh
2026-02-14 16:18:26 +08:00
committed by GitHub
parent db17119a96
commit 1f74a251f7
29 changed files with 787 additions and 827 deletions

View File

@ -1,12 +1,12 @@
import type { Mock } from 'vitest'
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import type { CurrentTryAppParams } from '@/context/explore-context'
import type { App } from '@/models/explore'
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
import ExploreContext from '@/context/explore-context'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { fetchAppDetail } from '@/service/explore'
import { useMembers } from '@/service/use-common'
import { AppModeEnum } from '@/types/app'
import AppList from '../index'
@ -29,6 +29,14 @@ vi.mock('@/service/explore', () => ({
fetchAppList: vi.fn(),
}))
vi.mock('@/context/app-context', () => ({
useAppContext: vi.fn(),
}))
vi.mock('@/service/use-common', () => ({
useMembers: vi.fn(),
}))
vi.mock('@/hooks/use-import-dsl', () => ({
useImportDSL: () => ({
handleImportDSL: mockHandleImportDSL,
@ -111,24 +119,22 @@ const createApp = (overrides: Partial<App> = {}): App => ({
is_agent: overrides.is_agent ?? false,
})
const renderWithContext = (hasEditPermission = false, onSuccess?: () => void, searchParams?: Record<string, string>) => {
const mockMemberRole = (hasEditPermission: boolean) => {
;(useAppContext as Mock).mockReturnValue({
userProfile: { id: 'user-1' },
})
;(useMembers as Mock).mockReturnValue({
data: {
accounts: [{ id: 'user-1', role: hasEditPermission ? 'admin' : 'normal' }],
},
})
}
const renderAppList = (hasEditPermission = false, onSuccess?: () => void, searchParams?: Record<string, string>) => {
mockMemberRole(hasEditPermission)
return render(
<NuqsTestingAdapter searchParams={searchParams}>
<ExploreContext.Provider
value={{
controlUpdateInstalledApps: 0,
setControlUpdateInstalledApps: vi.fn(),
hasEditPermission,
installedApps: [],
setInstalledApps: vi.fn(),
isFetchingInstalledApps: false,
setIsFetchingInstalledApps: vi.fn(),
isShowTryAppPanel: false,
setShowTryAppPanel: vi.fn(),
}}
>
<AppList onSuccess={onSuccess} />
</ExploreContext.Provider>
<AppList onSuccess={onSuccess} />
</NuqsTestingAdapter>,
)
}
@ -151,7 +157,7 @@ describe('AppList', () => {
mockExploreData = undefined
mockIsLoading = true
renderWithContext()
renderAppList()
expect(screen.getByRole('status')).toBeInTheDocument()
})
@ -162,7 +168,7 @@ describe('AppList', () => {
allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })],
}
renderWithContext()
renderAppList()
expect(screen.getByText('Alpha')).toBeInTheDocument()
expect(screen.getByText('Beta')).toBeInTheDocument()
@ -176,7 +182,7 @@ describe('AppList', () => {
allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Beta' }, category: 'Translate' })],
}
renderWithContext(false, undefined, { category: 'Writing' })
renderAppList(false, undefined, { category: 'Writing' })
expect(screen.getByText('Alpha')).toBeInTheDocument()
expect(screen.queryByText('Beta')).not.toBeInTheDocument()
@ -189,7 +195,7 @@ describe('AppList', () => {
categories: ['Writing'],
allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })],
}
renderWithContext()
renderAppList()
const input = screen.getByPlaceholderText('common.operation.search')
fireEvent.change(input, { target: { value: 'gam' } })
@ -217,7 +223,7 @@ describe('AppList', () => {
options.onSuccess?.()
})
renderWithContext(true, onSuccess)
renderAppList(true, onSuccess)
fireEvent.click(screen.getByText('explore.appCard.addToWorkspace'))
fireEvent.click(await screen.findByTestId('confirm-create'))
@ -241,7 +247,7 @@ describe('AppList', () => {
categories: ['Writing'],
allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })],
}
renderWithContext()
renderAppList()
const input = screen.getByPlaceholderText('common.operation.search')
fireEvent.change(input, { target: { value: 'gam' } })
@ -263,7 +269,7 @@ describe('AppList', () => {
mockIsError = true
mockExploreData = undefined
const { container } = renderWithContext()
const { container } = renderAppList()
expect(container.innerHTML).toBe('')
})
@ -271,7 +277,7 @@ describe('AppList', () => {
it('should render nothing when data is undefined', () => {
mockExploreData = undefined
const { container } = renderWithContext()
const { container } = renderAppList()
expect(container.innerHTML).toBe('')
})
@ -281,7 +287,7 @@ describe('AppList', () => {
categories: ['Writing'],
allList: [createApp(), createApp({ app_id: 'app-2', app: { ...createApp().app, name: 'Gamma' } })],
}
renderWithContext()
renderAppList()
const input = screen.getByPlaceholderText('common.operation.search')
fireEvent.change(input, { target: { value: 'gam' } })
@ -304,7 +310,7 @@ describe('AppList', () => {
};
(fetchAppDetail as unknown as Mock).mockResolvedValue({ export_data: 'yaml' })
renderWithContext(true)
renderAppList(true)
fireEvent.click(screen.getByText('explore.appCard.addToWorkspace'))
expect(await screen.findByTestId('create-app-modal')).toBeInTheDocument()
@ -325,7 +331,7 @@ describe('AppList', () => {
options.onSuccess?.()
})
renderWithContext(true)
renderAppList(true)
fireEvent.click(screen.getByText('explore.appCard.addToWorkspace'))
fireEvent.click(await screen.findByTestId('confirm-create'))
@ -345,7 +351,7 @@ describe('AppList', () => {
options.onPending?.()
})
renderWithContext(true)
renderAppList(true)
fireEvent.click(screen.getByText('explore.appCard.addToWorkspace'))
fireEvent.click(await screen.findByTestId('confirm-create'))
@ -362,70 +368,16 @@ describe('AppList', () => {
describe('TryApp Panel', () => {
it('should open create modal from try app panel', async () => {
vi.useRealTimers()
const mockSetShowTryAppPanel = vi.fn()
const app = createApp()
mockExploreData = {
categories: ['Writing'],
allList: [app],
}
render(
<NuqsTestingAdapter>
<ExploreContext.Provider
value={{
controlUpdateInstalledApps: 0,
setControlUpdateInstalledApps: vi.fn(),
hasEditPermission: true,
installedApps: [],
setInstalledApps: vi.fn(),
isFetchingInstalledApps: false,
setIsFetchingInstalledApps: vi.fn(),
isShowTryAppPanel: true,
setShowTryAppPanel: mockSetShowTryAppPanel,
currentApp: { appId: 'app-1', app },
}}
>
<AppList />
</ExploreContext.Provider>
</NuqsTestingAdapter>,
)
const createBtn = screen.getByTestId('try-app-create')
fireEvent.click(createBtn)
await waitFor(() => {
expect(screen.getByTestId('create-app-modal')).toBeInTheDocument()
})
})
it('should open create modal with null currApp when appParams has no app', async () => {
vi.useRealTimers()
mockExploreData = {
categories: ['Writing'],
allList: [createApp()],
}
render(
<NuqsTestingAdapter>
<ExploreContext.Provider
value={{
controlUpdateInstalledApps: 0,
setControlUpdateInstalledApps: vi.fn(),
hasEditPermission: true,
installedApps: [],
setInstalledApps: vi.fn(),
isFetchingInstalledApps: false,
setIsFetchingInstalledApps: vi.fn(),
isShowTryAppPanel: true,
setShowTryAppPanel: vi.fn(),
currentApp: { appId: 'app-1' } as CurrentTryAppParams,
}}
>
<AppList />
</ExploreContext.Provider>
</NuqsTestingAdapter>,
)
renderAppList(true)
fireEvent.click(screen.getByText('explore.appCard.try'))
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()
fireEvent.click(screen.getByTestId('try-app-create'))
@ -434,33 +386,19 @@ describe('AppList', () => {
})
})
it('should render try app panel with empty appId when currentApp is undefined', () => {
it('should close try app panel when close is clicked', () => {
mockExploreData = {
categories: ['Writing'],
allList: [createApp()],
}
render(
<NuqsTestingAdapter>
<ExploreContext.Provider
value={{
controlUpdateInstalledApps: 0,
setControlUpdateInstalledApps: vi.fn(),
hasEditPermission: true,
installedApps: [],
setInstalledApps: vi.fn(),
isFetchingInstalledApps: false,
setIsFetchingInstalledApps: vi.fn(),
isShowTryAppPanel: true,
setShowTryAppPanel: vi.fn(),
}}
>
<AppList />
</ExploreContext.Provider>
</NuqsTestingAdapter>,
)
renderAppList(true)
fireEvent.click(screen.getByText('explore.appCard.try'))
expect(screen.getByTestId('try-app-panel')).toBeInTheDocument()
fireEvent.click(screen.getByTestId('try-app-close'))
expect(screen.queryByTestId('try-app-panel')).not.toBeInTheDocument()
})
})
@ -477,7 +415,7 @@ describe('AppList', () => {
allList: [createApp()],
}
renderWithContext()
renderAppList()
expect(screen.getByTestId('explore-banner')).toBeInTheDocument()
})

View File

@ -2,12 +2,12 @@
import type { CreateAppModalProps } from '@/app/components/explore/create-app-modal'
import type { App } from '@/models/explore'
import type { TryAppSelection } from '@/types/try-app'
import { useDebounceFn } from 'ahooks'
import { useQueryState } from 'nuqs'
import * as React from 'react'
import { useCallback, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useContext, useContextSelector } from 'use-context-selector'
import DSLConfirmModal from '@/app/components/app/create-from-dsl-modal/dsl-confirm-modal'
import Button from '@/app/components/base/button'
import Input from '@/app/components/base/input'
@ -16,13 +16,14 @@ import AppCard from '@/app/components/explore/app-card'
import Banner from '@/app/components/explore/banner/banner'
import Category from '@/app/components/explore/category'
import CreateAppModal from '@/app/components/explore/create-app-modal'
import ExploreContext from '@/context/explore-context'
import { useAppContext } from '@/context/app-context'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { useImportDSL } from '@/hooks/use-import-dsl'
import {
DSLImportMode,
} from '@/models/app'
import { fetchAppDetail } from '@/service/explore'
import { useMembers } from '@/service/use-common'
import { useExploreAppList } from '@/service/use-explore'
import { cn } from '@/utils/classnames'
import TryApp from '../try-app'
@ -36,9 +37,12 @@ const Apps = ({
onSuccess,
}: AppsProps) => {
const { t } = useTranslation()
const { userProfile } = useAppContext()
const { systemFeatures } = useGlobalPublicStore()
const { hasEditPermission } = useContext(ExploreContext)
const { data: membersData } = useMembers()
const allCategoriesEn = t('apps.allCategories', { ns: 'explore', lng: 'en' })
const userAccount = membersData?.accounts?.find(account => account.id === userProfile.id)
const hasEditPermission = !!userAccount && userAccount.role !== 'normal'
const [keywords, setKeywords] = useState('')
const [searchKeywords, setSearchKeywords] = useState('')
@ -85,8 +89,8 @@ const Apps = ({
)
}, [searchKeywords, filteredList])
const [currApp, setCurrApp] = React.useState<App | null>(null)
const [isShowCreateModal, setIsShowCreateModal] = React.useState(false)
const [currApp, setCurrApp] = useState<App | null>(null)
const [isShowCreateModal, setIsShowCreateModal] = useState(false)
const {
handleImportDSL,
@ -96,16 +100,18 @@ const Apps = ({
} = useImportDSL()
const [showDSLConfirmModal, setShowDSLConfirmModal] = useState(false)
const isShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.isShowTryAppPanel)
const setShowTryAppPanel = useContextSelector(ExploreContext, ctx => ctx.setShowTryAppPanel)
const [currentTryApp, setCurrentTryApp] = useState<TryAppSelection | undefined>(undefined)
const isShowTryAppPanel = !!currentTryApp
const hideTryAppPanel = useCallback(() => {
setShowTryAppPanel(false)
}, [setShowTryAppPanel])
const appParams = useContextSelector(ExploreContext, ctx => ctx.currentApp)
setCurrentTryApp(undefined)
}, [])
const handleTryApp = useCallback((params: TryAppSelection) => {
setCurrentTryApp(params)
}, [])
const handleShowFromTryApp = useCallback(() => {
setCurrApp(appParams?.app || null)
setCurrApp(currentTryApp?.app || null)
setIsShowCreateModal(true)
}, [appParams?.app])
}, [currentTryApp?.app])
const onCreate: CreateAppModalProps['onConfirm'] = async ({
name,
@ -175,7 +181,7 @@ const Apps = ({
)}
>
<div className="flex items-center">
<div className="system-xl-semibold grow truncate text-text-primary">{!hasFilterCondition ? t('apps.title', { ns: 'explore' }) : t('apps.resultNum', { num: searchFilteredList.length, ns: 'explore' })}</div>
<div className="grow truncate text-text-primary system-xl-semibold">{!hasFilterCondition ? t('apps.title', { ns: 'explore' }) : t('apps.resultNum', { num: searchFilteredList.length, ns: 'explore' })}</div>
{hasFilterCondition && (
<>
<div className="mx-3 h-4 w-px bg-divider-regular"></div>
@ -216,13 +222,13 @@ const Apps = ({
{searchFilteredList.map(app => (
<AppCard
key={app.app_id}
isExplore
app={app}
canCreate={hasEditPermission}
onCreate={() => {
setCurrApp(app)
setIsShowCreateModal(true)
}}
onTry={handleTryApp}
/>
))}
</nav>
@ -255,9 +261,9 @@ const Apps = ({
{isShowTryAppPanel && (
<TryApp
appId={appParams?.appId || ''}
app={appParams?.app}
category={appParams?.app?.category}
appId={currentTryApp?.appId || ''}
app={currentTryApp?.app}
category={currentTryApp?.app?.category}
onClose={hideTryAppPanel}
onCreate={handleShowFromTryApp}
/>