Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 34 additions & 2 deletions calm-hub-ui/src/hub/hooks/useResourceNavigationKey.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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);
});
});
});
20 changes: 14 additions & 6 deletions calm-hub-ui/src/hub/hooks/useResourceNavigationKey.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
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
* to the same path still changes the key, and so does a query-only change after the load failed,
* 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;
}
}
Loading