Sprint: ll (#207)
* feat(PBI-ll): voeg lib/product-switch-path.ts toe met resolveProductSwitchTarget Pure helper die doel-URL bij product-wissel bepaalt; unit-tests dekken alle pad-gevallen. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(product-switch-path): dek alle pad-categorieën en null-terugval af * feat(nav-bar): gebruik resolveProductSwitchTarget bij product-wissel Vervang router.refresh() door gerichte navigatie via resolveProductSwitchTarget, zodat product/sprint/solo-pagina's direct naar het nieuwe product navigeren. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * test(nav-bar): voeg navigatie-assertions toe voor product-wissel Voeg 4 tests toe die verifiëren dat NavBar na product-wissel naar de juiste URL navigeert: /products/B, /products/B/sprint, /products/B/solo, en router.refresh() op niet-product-pagina's. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3ad352c10f
commit
8287509c7c
4 changed files with 114 additions and 1 deletions
|
|
@ -111,6 +111,47 @@ describe('NavBar — product switch', () => {
|
|||
await Promise.resolve()
|
||||
expect(actionMock).toHaveBeenCalledWith('B')
|
||||
})
|
||||
|
||||
it('non-demo: on /products/A navigates to /products/B', async () => {
|
||||
pathnameMock.mockReturnValue('/products/A')
|
||||
renderNavBar({ isDemo: false, activeProductId: 'A' })
|
||||
fireEvent.click(screen.getByText('Beta'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(pushMock).toHaveBeenCalledWith('/products/B')
|
||||
expect(toastSuccess).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('non-demo: on /products/A/sprint/SPR1 navigates to /products/B/sprint', async () => {
|
||||
pathnameMock.mockReturnValue('/products/A/sprint/SPR1')
|
||||
renderNavBar({ isDemo: false, activeProductId: 'A' })
|
||||
fireEvent.click(screen.getByText('Beta'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(pushMock).toHaveBeenCalledWith('/products/B/sprint')
|
||||
expect(toastSuccess).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('non-demo: on /products/A/solo navigates to /products/B/solo', async () => {
|
||||
pathnameMock.mockReturnValue('/products/A/solo')
|
||||
renderNavBar({ isDemo: false, activeProductId: 'A' })
|
||||
fireEvent.click(screen.getByText('Beta'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(pushMock).toHaveBeenCalledWith('/products/B/solo')
|
||||
expect(toastSuccess).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('non-demo: on /dashboard calls router.refresh and not router.push', async () => {
|
||||
pathnameMock.mockReturnValue('/dashboard')
|
||||
renderNavBar({ isDemo: false, activeProductId: 'A' })
|
||||
fireEvent.click(screen.getByText('Beta'))
|
||||
await Promise.resolve()
|
||||
await Promise.resolve()
|
||||
expect(refreshMock).toHaveBeenCalled()
|
||||
expect(pushMock).not.toHaveBeenCalled()
|
||||
expect(toastSuccess).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('NavBar — URL-derived active product (demo only)', () => {
|
||||
|
|
|
|||
56
__tests__/lib/product-switch-path.test.ts
Normal file
56
__tests__/lib/product-switch-path.test.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import { resolveProductSwitchTarget } from '@/lib/product-switch-path'
|
||||
|
||||
describe('resolveProductSwitchTarget', () => {
|
||||
it('returns null for non-product pages', () => {
|
||||
expect(resolveProductSwitchTarget('/dashboard', 'new-id')).toBeNull()
|
||||
expect(resolveProductSwitchTarget('/insights', 'new-id')).toBeNull()
|
||||
expect(resolveProductSwitchTarget('/ideas', 'new-id')).toBeNull()
|
||||
expect(resolveProductSwitchTarget('/jobs', 'new-id')).toBeNull()
|
||||
expect(resolveProductSwitchTarget('/', 'new-id')).toBeNull()
|
||||
})
|
||||
|
||||
it('maps /products/<old> to /products/<new>', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id', 'new-id')).toBe('/products/new-id')
|
||||
})
|
||||
|
||||
it('maps /products/<old>/ to /products/<new>', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/', 'new-id')).toBe('/products/new-id')
|
||||
})
|
||||
|
||||
it('maps /products/<old>/sprint to /products/<new>/sprint', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/sprint', 'new-id')).toBe(
|
||||
'/products/new-id/sprint',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps /products/<old>/sprint/<sprintId> to /products/<new>/sprint', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/sprint/abc123', 'new-id')).toBe(
|
||||
'/products/new-id/sprint',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps /products/<old>/sprint/.../planning to /products/<new>/sprint', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/sprint/abc123/planning', 'new-id')).toBe(
|
||||
'/products/new-id/sprint',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps /products/<old>/solo to /products/<new>/solo', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/solo', 'new-id')).toBe(
|
||||
'/products/new-id/solo',
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to /products/<new> for /products/<old>/settings', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/settings', 'new-id')).toBe(
|
||||
'/products/new-id',
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to /products/<new> for unknown sub-segments', () => {
|
||||
expect(resolveProductSwitchTarget('/products/old-id/unknown/deep', 'new-id')).toBe(
|
||||
'/products/new-id',
|
||||
)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue