From 75b3eb65e78b98b8817f54a4468dbf77ccee51cf Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Thu, 17 Apr 2025 22:20:59 -0600 Subject: [PATCH] initial tests --- .../components/ui/segmented/AppSegmented.tsx | 4 +- .../AppLayoutProvider.test.tsx | 129 ++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 web/src/context/BusterAppLayout/AppLayoutProvider.test.tsx diff --git a/web/src/components/ui/segmented/AppSegmented.tsx b/web/src/components/ui/segmented/AppSegmented.tsx index 5c0af2b05..461797369 100644 --- a/web/src/components/ui/segmented/AppSegmented.tsx +++ b/web/src/components/ui/segmented/AppSegmented.tsx @@ -17,6 +17,7 @@ import { import { Tooltip } from '../tooltip/Tooltip'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; +import { useAppLayoutContextSelector } from '@/context/BusterAppLayout'; export interface SegmentedItem { value: T; @@ -228,6 +229,7 @@ function SegmentedTriggerComponent(props: SegmentedTr const { item, selectedValue, size, block, tabRefs, handleTabClick } = props; const { tooltip, label, icon, disabled, value, link } = item; const router = useRouter(); + const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); const LinkDiv = link ? Link : 'div'; @@ -237,7 +239,7 @@ function SegmentedTriggerComponent(props: SegmentedTr handleTabClick(value); // Wait for a short duration to allow the animation to complete await new Promise((resolve) => setTimeout(resolve, 1)); - router.push(link); + onChangePage(link); } else { handleTabClick(value); } diff --git a/web/src/context/BusterAppLayout/AppLayoutProvider.test.tsx b/web/src/context/BusterAppLayout/AppLayoutProvider.test.tsx new file mode 100644 index 000000000..aa467944c --- /dev/null +++ b/web/src/context/BusterAppLayout/AppLayoutProvider.test.tsx @@ -0,0 +1,129 @@ +import { renderHook } from '@testing-library/react'; +import { useAppLayout } from './AppLayoutProvider'; +import { useRouter, usePathname, useParams } from 'next/navigation'; +import { BusterRoutesWithArgsRoute } from '@/routes/busterRoutes'; +import { BusterAppRoutes } from '@/routes/busterRoutes/busterAppRoutes'; + +// Mock next/navigation +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), + useParams: jest.fn() +})); + +describe('useAppLayout - onChangePage', () => { + // Mock window.location + const mockPush = jest.fn(); + let originalLocation: Location; + + beforeEach(() => { + // Setup router mock + (useRouter as jest.Mock).mockReturnValue({ push: mockPush }); + (usePathname as jest.Mock).mockReturnValue('/'); + (useParams as jest.Mock).mockReturnValue({}); + + // Setup window.location mock + originalLocation = window.location; + delete (window as any).location; + window.location = { + ...originalLocation, + href: 'http://localhost:3000', + origin: 'http://localhost:3000', + pathname: '/', + search: '' + } as Location; + + // Mock window.history + window.history.pushState = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + window.location = originalLocation; + }); + + it('should not navigate when target URL is identical to current URL', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard'; + window.location.pathname = '/dashboard'; + + const { result } = renderHook(() => useAppLayout()); + + await result.current.onChangePage('/dashboard'); + + expect(mockPush).not.toHaveBeenCalled(); + expect(window.history.pushState).not.toHaveBeenCalled(); + }); + + it('should handle shallow routing when only query params change', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard'; + window.location.pathname = '/dashboard'; + + const { result } = renderHook(() => useAppLayout()); + + await result.current.onChangePage('/dashboard?filter=active', { shallow: true }); + + expect(mockPush).not.toHaveBeenCalled(); + expect(window.history.pushState).toHaveBeenCalledWith( + {}, + '', + expect.stringContaining('/dashboard?filter=active') + ); + }); + + it('should navigate to new route when pathname changes', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard'; + window.location.pathname = '/dashboard'; + + const { result } = renderHook(() => useAppLayout()); + + await result.current.onChangePage('/settings'); + + expect(mockPush).toHaveBeenCalledWith('/settings'); + }); + + it('should update query parameters on same pathname', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard?filter=all'; + window.location.pathname = '/dashboard'; + window.location.search = '?filter=all'; + + const { result } = renderHook(() => useAppLayout()); + + await result.current.onChangePage('/dashboard?filter=active'); + + expect(mockPush).toHaveBeenCalledWith('/dashboard?filter=active'); + }); + + it('should remove query parameters when navigating to clean URL', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard?filter=active'; + window.location.pathname = '/dashboard'; + window.location.search = '?filter=active'; + + const { result } = renderHook(() => useAppLayout()); + + await result.current.onChangePage('/dashboard'); + + expect(window.history.pushState).toHaveBeenCalledWith({}, '', '/dashboard'); + }); + + it('should handle route with dynamic parameters', async () => { + // Set initial URL + window.location.href = 'http://localhost:3000/dashboard'; + window.location.pathname = '/dashboard'; + + const { result } = renderHook(() => useAppLayout()); + + const route: BusterRoutesWithArgsRoute = { + route: BusterAppRoutes.APP_DASHBOARD_ID, + dashboardId: '123' + }; + + await result.current.onChangePage(route); + + expect(mockPush).toHaveBeenCalled(); + }); +});