-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: move the common RHHC Navigation configuration getters to hooks
LIIKUNTA-617. Move the common RHHC Navigation Component getter-configurations to new hook files, to make the code cleaner and to make it more testable.
- Loading branch information
1 parent
b4b750d
commit 104fdf5
Showing
4 changed files
with
203 additions
and
69 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
110 changes: 110 additions & 0 deletions
110
packages/components/src/components/navigation/__tests__/useGetIsItemActive.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { renderHook, waitFor } from '@testing-library/react'; | ||
import mockRouter from 'next-router-mock'; | ||
import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider'; | ||
|
||
import type { MenuItem } from 'react-helsinki-headless-cms'; | ||
import CmsHelperProvider from '../../../cmsHelperProvider/CmsHelperProvider'; | ||
import { APP_LANGUAGES } from '../../../constants'; | ||
import { HeadlessCMSHelper } from '../../../utils/headless-cms/HeadlessCMSHelper'; | ||
import { CmsRoutedAppHelper } from '../../../utils/headless-cms/HeadlessCmsRoutedAppHelper'; | ||
import useGetItemIsActive from '../useGetIsItemActive'; | ||
|
||
const i18nRoutes = { | ||
'/search': [ | ||
{ source: '/haku', locale: 'fi' }, | ||
{ source: '/sok', locale: 'sv' }, | ||
], | ||
'/events/:eventId': [ | ||
{ source: '/tapahtumat/:eventId', locale: 'fi' }, | ||
{ source: '/kurser/:eventId', locale: 'sv' }, | ||
], | ||
'/articles': [ | ||
{ source: '/artikkelit', locale: 'fi' }, | ||
{ source: '/artiklar', locale: 'sv' }, | ||
], | ||
'/articles/:slug*': [ | ||
{ source: '/artikkelit/:slug*', locale: 'fi' }, | ||
{ source: '/artiklar/:slug*', locale: 'sv' }, | ||
], | ||
'/pages/:slug*': [ | ||
{ source: '/sivut/:slug*', locale: 'fi' }, | ||
{ source: '/sidor/:slug*', locale: 'sv' }, | ||
], | ||
}; | ||
|
||
const testCmsHelper = new HeadlessCMSHelper({ | ||
cmsArticlesContextPath: '/articles', | ||
cmsPagesContextPath: '/pages', | ||
dateFormat: 'dd.MM.yyyy', | ||
ArticleDetails: jest.fn(), | ||
}); | ||
|
||
const testRoutedAppHelper = new CmsRoutedAppHelper({ | ||
i18nRoutes, | ||
locales: APP_LANGUAGES, | ||
URLRewriteMapping: {}, | ||
}); | ||
|
||
type MenuItemLocation = string; | ||
type WindowLocationHref = string; | ||
type MenuItemLocationMatcherType = [MenuItemLocation, WindowLocationHref][]; | ||
|
||
const frontPage: MenuItemLocationMatcherType = [ | ||
['/', '/'], | ||
['/fi', '/'], | ||
['/fi', '/fi'], | ||
['/sv', '/sv'], | ||
['/en', '/en'], | ||
]; | ||
|
||
const searchPage: MenuItemLocationMatcherType = [ | ||
['/fi/search', '/fi/search'], | ||
['/fi/search', '/fi/haku'], | ||
['/fi/search', '/haku'], | ||
['/fi/search', '/haku'], | ||
['/sv/search', '/sv/search'], | ||
['/sv/search', '/sv/sok'], | ||
['/en/search', '/en/search'], | ||
['/search', '/search'], | ||
['/search', '/haku'], | ||
['/fi/search', '/search'], | ||
['/fi/search', '/haku'], | ||
['/fi/search', '/search?searchType=Venue'], | ||
]; | ||
|
||
const articlesPage: MenuItemLocationMatcherType = [ | ||
['/fi/articles', '/fi/artikkelit'], | ||
['/fi/articles', '/fi/articles'], | ||
['/sv/articles', '/sv/articles'], | ||
['/sv/articles', '/sv/artiklar'], | ||
['/en/articles', '/en/articles'], | ||
]; | ||
|
||
// FIXME: Skipped while no way to mock the nextjs redirects and i18nroutes for router was found. | ||
// eslint-disable-next-line jest/no-disabled-tests | ||
describe.skip('useGetIsItemActive hook for Navigation-component', () => { | ||
describe('getIsItemActive function', () => { | ||
it.each([...frontPage, ...searchPage, ...articlesPage])( | ||
'returns true for menu item with URL "%s" when current location is "%s"', | ||
async (itemUrl, locationUrl) => { | ||
mockRouter.setCurrentUrl(locationUrl); | ||
await waitFor(() => { | ||
expect(mockRouter.asPath).toStrictEqual(locationUrl); | ||
}); | ||
const { result } = renderHook(() => useGetItemIsActive(), { | ||
wrapper: ({ children }: any) => ( | ||
<MemoryRouterProvider> | ||
<CmsHelperProvider | ||
cmsHelper={testCmsHelper} | ||
routerHelper={testRoutedAppHelper} | ||
> | ||
{children} | ||
</CmsHelperProvider> | ||
</MemoryRouterProvider> | ||
), | ||
}); | ||
expect(result.current({ path: itemUrl } as MenuItem)).toBeTruthy(); | ||
} | ||
); | ||
}); | ||
}); |
35 changes: 35 additions & 0 deletions
35
packages/components/src/components/navigation/useGetIsItemActive.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { resolveHref } from 'next/dist/shared/lib/router/utils/resolve-href'; // NOTE: in Next 14 this is located in: 'next/dist/client/resolve-href' -- https://github.com/vercel/next.js/discussions/22025 | ||
import { useRouter } from 'next/router'; | ||
import type { NavigationProps } from 'react-helsinki-headless-cms'; | ||
import { useCmsRoutedAppHelper } from '../../cmsHelperProvider'; | ||
import { useLocale } from '../../hooks'; | ||
|
||
export default function useGetItemIsActive(): NonNullable< | ||
NavigationProps['getIsItemActive'] | ||
> { | ||
const router = useRouter(); | ||
const { pathname, asPath, query } = router; | ||
const routerHelper = useCmsRoutedAppHelper(); | ||
const locale = useLocale(); | ||
|
||
return ({ path }) => { | ||
const pathWithoutTrailingSlash = (path ?? '').replace(/\/$/, ''); | ||
const i18nRouterPathname = routerHelper.getI18nPath(pathname, locale); | ||
const i18nRouterAsPath = routerHelper.getI18nPath(asPath, locale); | ||
const [, resolvedUrl] = resolveHref(router, { pathname, query }, true); | ||
const resolvedPathname = resolvedUrl?.split('?')[0]; | ||
return Boolean( | ||
// The router.pathname needs to be checked when dealing with "statically routed page". | ||
pathWithoutTrailingSlash === i18nRouterPathname || | ||
pathWithoutTrailingSlash === `/${locale}${i18nRouterPathname}` || | ||
// The pathname may or may not contain the i18n version of the menu item path | ||
pathWithoutTrailingSlash === resolvedPathname || | ||
pathWithoutTrailingSlash === `/${locale}${resolvedPathname}` || | ||
// Since the menu can contain subitems in a dropdown, the parent items needs to be checked too. | ||
// NOTE: We are now assuming that all the parent items are also real parent pages. | ||
(path && | ||
(i18nRouterAsPath.startsWith(path) || | ||
resolvedPathname?.startsWith(path))) | ||
); | ||
}; | ||
} |
50 changes: 50 additions & 0 deletions
50
packages/components/src/components/navigation/useGetPathnameForLanguage.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { useRouter } from 'next/router'; | ||
import { useCallback } from 'react'; | ||
import type { | ||
ArticleType, | ||
NavigationProps, | ||
PageType, | ||
} from 'react-helsinki-headless-cms'; | ||
import { useCmsHelper, useCmsRoutedAppHelper } from '../../cmsHelperProvider'; | ||
import type { AppLanguage } from '../../types'; | ||
|
||
export default function useGetPathnameForLanguage( | ||
page?: PageType | ArticleType | ||
): NavigationProps['getPathnameForLanguage'] { | ||
const { pathname: currentPage, query } = useRouter(); | ||
|
||
const cmsHelper = useCmsHelper(); | ||
const routerHelper = useCmsRoutedAppHelper(); | ||
|
||
// router.query has no query parameters, even if the current URL does when serving | ||
// server-side generated pages. Simply using window.location.search always when | ||
// available broke e.g. /courses/[eventId] URL part so that the [eventId] part didn't | ||
// get replaced with the actual event ID. Merging both query sources worked better. | ||
const getCurrentParsedUrlQuery = useCallback( | ||
() => ({ | ||
...query, | ||
...(window | ||
? Object.fromEntries(new URLSearchParams(window.location.search)) | ||
: {}), | ||
}), | ||
[query] | ||
); | ||
|
||
return ({ slug }) => { | ||
const translatedPage = (page?.translations as PageType[])?.find( | ||
(translation) => translation?.language?.slug === slug | ||
); | ||
return routerHelper.getLocalizedCmsItemUrl( | ||
currentPage, | ||
translatedPage | ||
? { | ||
slug: | ||
cmsHelper.getSlugFromUri( | ||
cmsHelper.removeContextPathFromUri(translatedPage.uri) | ||
) ?? '', | ||
} | ||
: getCurrentParsedUrlQuery(), | ||
slug as AppLanguage | ||
); | ||
}; | ||
} |