mirror of
https://github.com/iflytek/skillhub.git
synced 2026-09-06 08:15:57 +00:00
Merge pull request #805 from iflytek/codex/fix/issue-800-detail-return
fix(web): preserve dashboard return path from skill details
This commit is contained in:
commit
1d63d101fa
5 changed files with 156 additions and 17 deletions
|
|
@ -393,6 +393,9 @@ const dashboardStarsRoute = createRoute({
|
|||
getParentRoute: () => rootRoute,
|
||||
path: 'dashboard/stars',
|
||||
beforeLoad: requireAuth,
|
||||
validateSearch: (search: Record<string, unknown>): { page?: number } => ({
|
||||
page: typeof search.page === 'number' && search.page > 0 ? search.page : undefined,
|
||||
}),
|
||||
component: MyStarsPage,
|
||||
})
|
||||
|
||||
|
|
@ -400,6 +403,9 @@ const dashboardSubscriptionsRoute = createRoute({
|
|||
getParentRoute: () => rootRoute,
|
||||
path: 'dashboard/subscriptions',
|
||||
beforeLoad: requireAuth,
|
||||
validateSearch: (search: Record<string, unknown>): { page?: number } => ({
|
||||
page: typeof search.page === 'number' && search.page > 0 ? search.page : undefined,
|
||||
}),
|
||||
component: MySubscriptionsPage,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,30 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import { createElement } from 'react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const navigate = vi.fn()
|
||||
const { useMyStarsPage } = vi.hoisted(() => ({
|
||||
useMyStarsPage: vi.fn(() => ({
|
||||
data: {
|
||||
items: [{ id: 1, namespace: 'team-a', slug: 'demo skill' }],
|
||||
total: 1,
|
||||
page: 0,
|
||||
size: 12,
|
||||
},
|
||||
isLoading: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@tanstack/react-router', () => ({
|
||||
useNavigate: () => vi.fn(),
|
||||
useNavigate: () => navigate,
|
||||
useSearch: () => ({ page: 2 }),
|
||||
useLocation: () => ({
|
||||
pathname: '/dashboard/stars',
|
||||
searchStr: '?page=2',
|
||||
hash: '#saved',
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('react-i18next', async () => {
|
||||
|
|
@ -15,7 +38,7 @@ vi.mock('react-i18next', async () => {
|
|||
})
|
||||
|
||||
vi.mock('@/features/skill/skill-card', () => ({
|
||||
SkillCard: () => null,
|
||||
SkillCard: ({ onClick }: { onClick?: () => void }) => createElement('button', { onClick }, 'skill-card'),
|
||||
}))
|
||||
|
||||
vi.mock('@/shared/components/pagination', () => ({
|
||||
|
|
@ -23,10 +46,7 @@ vi.mock('@/shared/components/pagination', () => ({
|
|||
}))
|
||||
|
||||
vi.mock('@/shared/hooks/use-user-queries', () => ({
|
||||
useMyStarsPage: () => ({
|
||||
data: { items: [], total: 0, page: 0, size: 12 },
|
||||
isLoading: false,
|
||||
}),
|
||||
useMyStarsPage,
|
||||
}))
|
||||
|
||||
vi.mock('@/shared/ui/card', () => ({
|
||||
|
|
@ -40,7 +60,26 @@ vi.mock('@/shared/components/dashboard-page-header', () => ({
|
|||
import { MyStarsPage } from './stars'
|
||||
|
||||
describe('MyStarsPage', () => {
|
||||
beforeEach(() => navigate.mockClear())
|
||||
|
||||
it('exports a named component function', () => {
|
||||
expect(typeof MyStarsPage).toBe('function')
|
||||
})
|
||||
|
||||
it('preserves the favorites page when opening a skill', () => {
|
||||
render(createElement(MyStarsPage))
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'skill-card' }))
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith({
|
||||
to: '/space/team-a/demo%20skill',
|
||||
search: { returnTo: '/dashboard/stars?page=2#saved' },
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the URL page as the query source', () => {
|
||||
render(createElement(MyStarsPage))
|
||||
|
||||
expect(useMyStarsPage).toHaveBeenCalledWith({ page: 2, size: 12 })
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
import { useState } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { useLocation, useNavigate, useSearch } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SkillCard } from '@/features/skill/skill-card'
|
||||
import { Pagination } from '@/shared/components/pagination'
|
||||
import { useMyStarsPage } from '@/shared/hooks/use-user-queries'
|
||||
import { Card } from '@/shared/ui/card'
|
||||
import { DashboardPageHeader } from '@/shared/components/dashboard-page-header'
|
||||
import { buildReturnTo } from '@/shared/lib/auth-route'
|
||||
|
||||
const PAGE_SIZE = 12
|
||||
|
||||
export function MyStarsPage() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [page, setPage] = useState(0)
|
||||
const location = useLocation()
|
||||
const search = useSearch({ from: '/dashboard/stars' })
|
||||
const page = search.page ?? 0
|
||||
const { data, isLoading } = useMyStarsPage({ page, size: PAGE_SIZE })
|
||||
const skills = data?.items ?? []
|
||||
const totalPages = data ? Math.max(Math.ceil(data.total / data.size), 1) : 1
|
||||
|
|
@ -40,12 +42,22 @@ export function MyStarsPage() {
|
|||
<SkillCard
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
onClick={() => navigate({ to: `/space/${skill.namespace}/${encodeURIComponent(skill.slug)}` })}
|
||||
onClick={() => navigate({
|
||||
to: `/space/${skill.namespace}/${encodeURIComponent(skill.slug)}`,
|
||||
search: { returnTo: buildReturnTo(location) },
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{data && data.total > PAGE_SIZE ? (
|
||||
<Pagination page={page} totalPages={totalPages} onPageChange={setPage} />
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
onPageChange={(nextPage) => navigate({
|
||||
to: '/dashboard/stars',
|
||||
search: { page: nextPage > 0 ? nextPage : undefined },
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
70
web/src/pages/dashboard/subscriptions.test.ts
Normal file
70
web/src/pages/dashboard/subscriptions.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// @vitest-environment jsdom
|
||||
|
||||
import { createElement } from 'react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const navigate = vi.fn()
|
||||
const { useMySubscriptionsPage } = vi.hoisted(() => ({
|
||||
useMySubscriptionsPage: vi.fn(() => ({
|
||||
data: {
|
||||
items: [{ id: 1, namespace: 'team-a', slug: 'demo-skill' }],
|
||||
total: 1,
|
||||
page: 0,
|
||||
size: 12,
|
||||
},
|
||||
isLoading: false,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('@tanstack/react-router', () => ({
|
||||
useNavigate: () => navigate,
|
||||
useSearch: () => ({ page: 1 }),
|
||||
useLocation: () => ({
|
||||
pathname: '/dashboard/subscriptions',
|
||||
searchStr: '?page=1',
|
||||
hash: '',
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('react-i18next', async () => {
|
||||
const actual = await vi.importActual<typeof import('react-i18next')>('react-i18next')
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('@/features/skill/skill-card', () => ({
|
||||
SkillCard: ({ onClick }: { onClick?: () => void }) => createElement('button', { onClick }, 'skill-card'),
|
||||
}))
|
||||
|
||||
vi.mock('@/shared/components/pagination', () => ({ Pagination: () => null }))
|
||||
vi.mock('@/shared/hooks/use-user-queries', () => ({
|
||||
useMySubscriptionsPage,
|
||||
}))
|
||||
vi.mock('@/shared/ui/card', () => ({ Card: ({ children }: { children: unknown }) => children }))
|
||||
vi.mock('@/shared/components/dashboard-page-header', () => ({ DashboardPageHeader: () => null }))
|
||||
|
||||
import { MySubscriptionsPage } from './subscriptions'
|
||||
|
||||
describe('MySubscriptionsPage', () => {
|
||||
beforeEach(() => navigate.mockClear())
|
||||
|
||||
it('preserves the subscriptions page when opening a skill', () => {
|
||||
render(createElement(MySubscriptionsPage))
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'skill-card' }))
|
||||
|
||||
expect(navigate).toHaveBeenCalledWith({
|
||||
to: '/space/team-a/demo-skill',
|
||||
search: { returnTo: '/dashboard/subscriptions?page=1' },
|
||||
})
|
||||
})
|
||||
|
||||
it('uses the URL page as the query source', () => {
|
||||
render(createElement(MySubscriptionsPage))
|
||||
|
||||
expect(useMySubscriptionsPage).toHaveBeenCalledWith({ page: 1, size: 12 })
|
||||
})
|
||||
})
|
||||
|
|
@ -1,18 +1,20 @@
|
|||
import { useState } from 'react'
|
||||
import { useNavigate } from '@tanstack/react-router'
|
||||
import { useLocation, useNavigate, useSearch } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SkillCard } from '@/features/skill/skill-card'
|
||||
import { Pagination } from '@/shared/components/pagination'
|
||||
import { useMySubscriptionsPage } from '@/shared/hooks/use-user-queries'
|
||||
import { Card } from '@/shared/ui/card'
|
||||
import { DashboardPageHeader } from '@/shared/components/dashboard-page-header'
|
||||
import { buildReturnTo } from '@/shared/lib/auth-route'
|
||||
|
||||
const PAGE_SIZE = 12
|
||||
|
||||
export function MySubscriptionsPage() {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [page, setPage] = useState(0)
|
||||
const location = useLocation()
|
||||
const search = useSearch({ from: '/dashboard/subscriptions' })
|
||||
const page = search.page ?? 0
|
||||
const { data, isLoading } = useMySubscriptionsPage({ page, size: PAGE_SIZE })
|
||||
const skills = data?.items ?? []
|
||||
const totalPages = data ? Math.max(Math.ceil(data.total / data.size), 1) : 1
|
||||
|
|
@ -40,12 +42,22 @@ export function MySubscriptionsPage() {
|
|||
<SkillCard
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
onClick={() => navigate({ to: `/space/${skill.namespace}/${encodeURIComponent(skill.slug)}` })}
|
||||
onClick={() => navigate({
|
||||
to: `/space/${skill.namespace}/${encodeURIComponent(skill.slug)}`,
|
||||
search: { returnTo: buildReturnTo(location) },
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{data && data.total > PAGE_SIZE ? (
|
||||
<Pagination page={page} totalPages={totalPages} onPageChange={setPage} />
|
||||
<Pagination
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
onPageChange={(nextPage) => navigate({
|
||||
to: '/dashboard/subscriptions',
|
||||
search: { page: nextPage > 0 ? nextPage : undefined },
|
||||
})}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue