Skip to content

Commit 2303457

Browse files
committed
test(web): cover skill detail loading
1 parent d7862a8 commit 2303457

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { render, screen, waitFor } from '@testing-library/react'
2+
import { vi } from 'vitest'
3+
4+
import { SkillDetailPage } from '../components/SkillDetailPage'
5+
6+
const navigateMock = vi.fn()
7+
8+
vi.mock('@tanstack/react-router', () => ({
9+
useNavigate: () => navigateMock,
10+
}))
11+
12+
const useQueryMock = vi.fn()
13+
const getReadmeMock = vi.fn()
14+
15+
vi.mock('convex/react', () => ({
16+
useConvexAuth: () => ({ isAuthenticated: false }),
17+
useQuery: (...args: unknown[]) => useQueryMock(...args),
18+
useMutation: () => vi.fn(),
19+
useAction: () => getReadmeMock,
20+
}))
21+
22+
describe('SkillDetailPage', () => {
23+
beforeEach(() => {
24+
useQueryMock.mockReset()
25+
getReadmeMock.mockReset()
26+
navigateMock.mockReset()
27+
getReadmeMock.mockResolvedValue({ text: '' })
28+
useQueryMock.mockImplementation((_fn: unknown, args: unknown) => {
29+
if (args === 'skip') return undefined
30+
return undefined
31+
})
32+
})
33+
34+
it('shows a loading indicator while loading', () => {
35+
useQueryMock
36+
.mockImplementationOnce(() => null) // me
37+
.mockImplementationOnce(() => undefined) // getBySlug
38+
39+
render(<SkillDetailPage slug="weather" />)
40+
expect(screen.getByText(/Loading skill/i)).toBeTruthy()
41+
expect(screen.queryByText(/Skill not found/i)).toBeNull()
42+
})
43+
44+
it('shows not found when skill query resolves to null', async () => {
45+
useQueryMock
46+
.mockImplementationOnce(() => null) // me
47+
.mockImplementationOnce(() => null) // getBySlug
48+
49+
render(<SkillDetailPage slug="missing-skill" />)
50+
expect(await screen.findByText(/Skill not found/i)).toBeTruthy()
51+
})
52+
53+
it('redirects legacy routes to canonical owner/slug', async () => {
54+
useQueryMock
55+
.mockImplementationOnce(() => null) // me
56+
.mockImplementationOnce(() => ({
57+
skill: {
58+
_id: 'skills:1',
59+
slug: 'weather',
60+
displayName: 'Weather',
61+
summary: 'Get current weather.',
62+
ownerUserId: 'users:1',
63+
tags: {},
64+
stats: { stars: 0, downloads: 0 },
65+
},
66+
owner: { handle: 'steipete', name: 'Peter' },
67+
latestVersion: { _id: 'skillVersions:1', version: '1.0.0', parsed: {} },
68+
}))
69+
70+
render(<SkillDetailPage slug="weather" redirectToCanonical />)
71+
expect(screen.getByText(/Loading skill/i)).toBeTruthy()
72+
73+
await waitFor(() => {
74+
expect(navigateMock).toHaveBeenCalled()
75+
})
76+
expect(navigateMock).toHaveBeenCalledWith({
77+
to: '/$owner/$slug',
78+
params: { owner: 'steipete', slug: 'weather' },
79+
replace: true,
80+
})
81+
})
82+
})

src/routeTree.gen.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import { Route as SettingsRouteImport } from './routes/settings'
1515
import { Route as SearchRouteImport } from './routes/search'
1616
import { Route as AdminRouteImport } from './routes/admin'
1717
import { Route as IndexRouteImport } from './routes/index'
18+
import { Route as SkillsIndexRouteImport } from './routes/skills/index'
1819
import { Route as UHandleRouteImport } from './routes/u/$handle'
1920
import { Route as SkillsSlugRouteImport } from './routes/skills/$slug'
2021
import { Route as CliAuthRouteImport } from './routes/cli/auth'
22+
import { Route as OwnerSlugRouteImport } from './routes/$owner/$slug'
2123

2224
const UploadRoute = UploadRouteImport.update({
2325
id: '/upload',
@@ -49,6 +51,11 @@ const IndexRoute = IndexRouteImport.update({
4951
path: '/',
5052
getParentRoute: () => rootRouteImport,
5153
} as any)
54+
const SkillsIndexRoute = SkillsIndexRouteImport.update({
55+
id: '/skills/',
56+
path: '/skills/',
57+
getParentRoute: () => rootRouteImport,
58+
} as any)
5259
const UHandleRoute = UHandleRouteImport.update({
5360
id: '/u/$handle',
5461
path: '/u/$handle',
@@ -64,6 +71,11 @@ const CliAuthRoute = CliAuthRouteImport.update({
6471
path: '/cli/auth',
6572
getParentRoute: () => rootRouteImport,
6673
} as any)
74+
const OwnerSlugRoute = OwnerSlugRouteImport.update({
75+
id: '/$owner/$slug',
76+
path: '/$owner/$slug',
77+
getParentRoute: () => rootRouteImport,
78+
} as any)
6779

6880
export interface FileRoutesByFullPath {
6981
'/': typeof IndexRoute
@@ -72,9 +84,11 @@ export interface FileRoutesByFullPath {
7284
'/settings': typeof SettingsRoute
7385
'/stars': typeof StarsRoute
7486
'/upload': typeof UploadRoute
87+
'/$owner/$slug': typeof OwnerSlugRoute
7588
'/cli/auth': typeof CliAuthRoute
7689
'/skills/$slug': typeof SkillsSlugRoute
7790
'/u/$handle': typeof UHandleRoute
91+
'/skills': typeof SkillsIndexRoute
7892
}
7993
export interface FileRoutesByTo {
8094
'/': typeof IndexRoute
@@ -83,9 +97,11 @@ export interface FileRoutesByTo {
8397
'/settings': typeof SettingsRoute
8498
'/stars': typeof StarsRoute
8599
'/upload': typeof UploadRoute
100+
'/$owner/$slug': typeof OwnerSlugRoute
86101
'/cli/auth': typeof CliAuthRoute
87102
'/skills/$slug': typeof SkillsSlugRoute
88103
'/u/$handle': typeof UHandleRoute
104+
'/skills': typeof SkillsIndexRoute
89105
}
90106
export interface FileRoutesById {
91107
__root__: typeof rootRouteImport
@@ -95,9 +111,11 @@ export interface FileRoutesById {
95111
'/settings': typeof SettingsRoute
96112
'/stars': typeof StarsRoute
97113
'/upload': typeof UploadRoute
114+
'/$owner/$slug': typeof OwnerSlugRoute
98115
'/cli/auth': typeof CliAuthRoute
99116
'/skills/$slug': typeof SkillsSlugRoute
100117
'/u/$handle': typeof UHandleRoute
118+
'/skills/': typeof SkillsIndexRoute
101119
}
102120
export interface FileRouteTypes {
103121
fileRoutesByFullPath: FileRoutesByFullPath
@@ -108,9 +126,11 @@ export interface FileRouteTypes {
108126
| '/settings'
109127
| '/stars'
110128
| '/upload'
129+
| '/$owner/$slug'
111130
| '/cli/auth'
112131
| '/skills/$slug'
113132
| '/u/$handle'
133+
| '/skills'
114134
fileRoutesByTo: FileRoutesByTo
115135
to:
116136
| '/'
@@ -119,9 +139,11 @@ export interface FileRouteTypes {
119139
| '/settings'
120140
| '/stars'
121141
| '/upload'
142+
| '/$owner/$slug'
122143
| '/cli/auth'
123144
| '/skills/$slug'
124145
| '/u/$handle'
146+
| '/skills'
125147
id:
126148
| '__root__'
127149
| '/'
@@ -130,9 +152,11 @@ export interface FileRouteTypes {
130152
| '/settings'
131153
| '/stars'
132154
| '/upload'
155+
| '/$owner/$slug'
133156
| '/cli/auth'
134157
| '/skills/$slug'
135158
| '/u/$handle'
159+
| '/skills/'
136160
fileRoutesById: FileRoutesById
137161
}
138162
export interface RootRouteChildren {
@@ -142,9 +166,11 @@ export interface RootRouteChildren {
142166
SettingsRoute: typeof SettingsRoute
143167
StarsRoute: typeof StarsRoute
144168
UploadRoute: typeof UploadRoute
169+
OwnerSlugRoute: typeof OwnerSlugRoute
145170
CliAuthRoute: typeof CliAuthRoute
146171
SkillsSlugRoute: typeof SkillsSlugRoute
147172
UHandleRoute: typeof UHandleRoute
173+
SkillsIndexRoute: typeof SkillsIndexRoute
148174
}
149175

150176
declare module '@tanstack/react-router' {
@@ -191,6 +217,13 @@ declare module '@tanstack/react-router' {
191217
preLoaderRoute: typeof IndexRouteImport
192218
parentRoute: typeof rootRouteImport
193219
}
220+
'/skills/': {
221+
id: '/skills/'
222+
path: '/skills'
223+
fullPath: '/skills'
224+
preLoaderRoute: typeof SkillsIndexRouteImport
225+
parentRoute: typeof rootRouteImport
226+
}
194227
'/u/$handle': {
195228
id: '/u/$handle'
196229
path: '/u/$handle'
@@ -212,6 +245,13 @@ declare module '@tanstack/react-router' {
212245
preLoaderRoute: typeof CliAuthRouteImport
213246
parentRoute: typeof rootRouteImport
214247
}
248+
'/$owner/$slug': {
249+
id: '/$owner/$slug'
250+
path: '/$owner/$slug'
251+
fullPath: '/$owner/$slug'
252+
preLoaderRoute: typeof OwnerSlugRouteImport
253+
parentRoute: typeof rootRouteImport
254+
}
215255
}
216256
}
217257

@@ -222,9 +262,11 @@ const rootRouteChildren: RootRouteChildren = {
222262
SettingsRoute: SettingsRoute,
223263
StarsRoute: StarsRoute,
224264
UploadRoute: UploadRoute,
265+
OwnerSlugRoute: OwnerSlugRoute,
225266
CliAuthRoute: CliAuthRoute,
226267
SkillsSlugRoute: SkillsSlugRoute,
227268
UHandleRoute: UHandleRoute,
269+
SkillsIndexRoute: SkillsIndexRoute,
228270
}
229271
export const routeTree = rootRouteImport
230272
._addFileChildren(rootRouteChildren)

0 commit comments

Comments
 (0)