diff --git a/src/app/(main)/TopNav.test.tsx b/src/app/(main)/TopNav.test.tsx new file mode 100644 index 000000000..be6d0f479 --- /dev/null +++ b/src/app/(main)/TopNav.test.tsx @@ -0,0 +1,74 @@ +import { beforeEach, expect, test, vi } from 'vitest'; +import { render, screen } from '@/test/render'; +import { TopNav } from './TopNav'; + +const mockPush = vi.fn(); +const mockRenderUrl = vi.fn((path: string) => path); +const mockUseNavigation = vi.fn(); + +vi.mock('@/components/hooks', () => ({ + useNavigation: () => mockUseNavigation(), +})); + +vi.mock('@/components/input/TeamsButton', () => ({ + TeamsButton: () =>
TeamsButton
, +})); + +vi.mock('@/components/input/WebsiteSelect', () => ({ + WebsiteSelect: ({ onChange }: { onChange: (value: string | number | null) => void }) => ( + <> + + + + ), +})); + +vi.mock('@/components/input/LinkSelect', () => ({ + LinkSelect: () => null, +})); + +vi.mock('@/components/input/PixelSelect', () => ({ + PixelSelect: () => null, +})); + +vi.mock('@/components/input/BoardSelect', () => ({ + BoardSelect: () => null, +})); + +beforeEach(() => { + mockPush.mockReset(); + mockRenderUrl.mockClear(); + mockUseNavigation.mockReturnValue({ + websiteId: 'website-1', + linkId: undefined, + pixelId: undefined, + boardId: undefined, + teamId: undefined, + router: { + push: mockPush, + }, + renderUrl: mockRenderUrl, + }); +}); + +test('does not navigate when the website select emits null', async () => { + const { user } = render(); + + await user.click(screen.getByRole('button', { name: 'clear website' })); + + expect(mockRenderUrl).not.toHaveBeenCalled(); + expect(mockPush).not.toHaveBeenCalled(); +}); + +test('navigates when the website select emits a website id', async () => { + const { user } = render(); + + await user.click(screen.getByRole('button', { name: 'select website' })); + + expect(mockRenderUrl).toHaveBeenCalledWith('/websites/website-2', false); + expect(mockPush).toHaveBeenCalledWith('/websites/website-2'); +}); diff --git a/src/app/(main)/TopNav.tsx b/src/app/(main)/TopNav.tsx index a3f34d075..3d8092377 100644 --- a/src/app/(main)/TopNav.tsx +++ b/src/app/(main)/TopNav.tsx @@ -11,20 +11,28 @@ import { WebsiteSelect } from '@/components/input/WebsiteSelect'; export function TopNav() { const { websiteId, linkId, pixelId, boardId, teamId, router, renderUrl } = useNavigation(); + const navigateToEntity = (basePath: string, value: string | number | null) => { + if (value === null || value === undefined || value === '') { + return; + } + + router.push(renderUrl(`${basePath}/${value}`, false)); + }; + const handleWebsiteChange = (value: string | number | null) => { - router.push(renderUrl(`/websites/${value}`, false)); + navigateToEntity('/websites', value); }; const handleLinkChange = (value: string | number | null) => { - router.push(renderUrl(`/links/${value}`, false)); + navigateToEntity('/links', value); }; const handlePixelChange = (value: string | number | null) => { - router.push(renderUrl(`/pixels/${value}`, false)); + navigateToEntity('/pixels', value); }; const handleBoardChange = (value: string | number | null) => { - router.push(renderUrl(`/boards/${value}`, false)); + navigateToEntity('/boards', value); }; return ( diff --git a/src/queries/prisma/website.ts b/src/queries/prisma/website.ts index 3b647ba2d..5c2117827 100644 --- a/src/queries/prisma/website.ts +++ b/src/queries/prisma/website.ts @@ -4,6 +4,7 @@ import prisma from '@/lib/prisma'; import redis from '@/lib/redis'; import { sanitizeSortFilters } from '@/lib/sort'; import type { QueryFilters } from '@/lib/types'; +import { z } from 'zod'; const WEBSITE_SORT_FIELDS = ['name', 'domain', 'createdAt'] as const; @@ -12,6 +13,10 @@ export async function findWebsite(criteria: Prisma.WebsiteFindUniqueArgs) { } export async function getWebsite(websiteId: string) { + if (!z.uuid().safeParse(websiteId).success) { + return null; + } + const website = await findWebsite({ where: { id: websiteId,