diff --git a/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.test.tsx b/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.test.tsx index 8c712e3c1d..e40b1b4f13 100644 --- a/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.test.tsx +++ b/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.test.tsx @@ -1,5 +1,5 @@ -import { act, render } from '@testing-library/react'; -import { MemoryRouter, useLocation, useNavigate, type NavigateFunction } from 'react-router-dom'; +import { act, render, renderHook } from '@testing-library/react'; +import { MemoryRouter, useLocation, useNavigate, type Location, type NavigateFunction } from 'react-router-dom'; import { describe, expect, it } from 'vitest'; import { useResourceNavigationKey } from './useResourceNavigationKey.js'; @@ -58,4 +58,36 @@ describe('useResourceNavigationKey', () => { expect(last()).not.toBe(first); }); + + describe('for a location without history state', () => { + const at = (pathname: string, search = ''): Location => ({ + key: 'default', + pathname, + search, + hash: '', + state: null, + }); + const renderAt = (location: Location) => + renderHook(({ location }) => useResourceNavigationKey(location, false), { + initialProps: { location }, + }); + + it('changes the key when the path changes', () => { + const { result, rerender } = renderAt(at('/finos.calm/interfaces/2/detail')); + const first = result.current; + + rerender({ location: at('/namespace/finos.traderx') }); + + expect(result.current).not.toBe(first); + }); + + it('keeps the key when only the query changes', () => { + const { result, rerender } = renderAt(at('/finos/patterns/api-gateway/1.0.0')); + const first = result.current; + + rerender({ location: at('/finos/patterns/api-gateway/1.0.0', '?tab=json') }); + + expect(result.current).toBe(first); + }); + }); }); diff --git a/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.ts b/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.ts index 649b312320..b5f782d9de 100644 --- a/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.ts +++ b/calm-hub-ui/src/hub/hooks/useResourceNavigationKey.ts @@ -1,6 +1,13 @@ import { useState } from 'react'; import type { Location } from 'react-router-dom'; +// A location that the router did not create, such as a first page load or a hash that was +// edited by hand, has no history state, so React Router gives it the key `default`. For those +// locations the pathname identifies the navigation. +function navigationId(location: Location): string { + return location.key === 'default' ? `${location.key}:${location.pathname}` : location.key; +} + /** * Returns a key that changes on each navigation, except a change to the query only, such as a * diagram tab. While the key stays the same, the page keeps the resource it loaded. A navigation @@ -8,21 +15,22 @@ import type { Location } from 'react-router-dom'; * so that the route loads the resource again. */ export function useResourceNavigationKey(location: Location, loadFailed: boolean): string { + const id = navigationId(location); const [last, setLast] = useState({ - key: location.key, + id, pathname: location.pathname, search: location.search, - resourceKey: location.key, + resourceKey: id, }); - if (last.key === location.key) return last.resourceKey; + if (last.id === id) return last.resourceKey; const keepsResource = !loadFailed && location.pathname === last.pathname && location.search !== last.search; const next = { - key: location.key, + id, pathname: location.pathname, search: location.search, - resourceKey: keepsResource ? last.resourceKey : location.key, + resourceKey: keepsResource ? last.resourceKey : id, }; setLast(next); return next.resourceKey; -} +} \ No newline at end of file