@truedat/core 8.11.0 → 8.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "8.11.0",
3
+ "version": "8.11.2",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -57,7 +57,7 @@
57
57
  "@testing-library/jest-dom": "^6.6.3",
58
58
  "@testing-library/react": "^16.3.0",
59
59
  "@testing-library/user-event": "^14.6.1",
60
- "@truedat/test": "8.11.0",
60
+ "@truedat/test": "8.11.2",
61
61
  "identity-obj-proxy": "^3.0.0",
62
62
  "jest": "^29.7.0",
63
63
  "redux-saga-test-plan": "^4.0.6"
@@ -98,5 +98,5 @@
98
98
  "swr": "^2.3.3",
99
99
  "turndown": "^7.2.2"
100
100
  },
101
- "gitHead": "89b12f5fa7ff2d0fb790d88830938ccff9cb3d91"
101
+ "gitHead": "5c0ce3c555de4c9e89021b4d0ab785ed27a87305"
102
102
  }
@@ -8,7 +8,6 @@ import { linkTo } from "@truedat/core/routes";
8
8
  import { makeGetSubscopes } from "@truedat/core/selectors/subscopedTemplates";
9
9
  import { getConceptSubscope } from "@truedat/core/selectors/getConceptSubscope";
10
10
  import { getSidemenuGlossarySubscopes } from "@truedat/core/selectors/getSidemenuGlossarySubscopes";
11
- import TemplatesLoader from "@truedat/core/components/TemplatesLoader";
12
11
  import { useAuthorized } from "../hooks";
13
12
  import {
14
13
  CONCEPTS,
@@ -270,7 +269,6 @@ export const GlossaryMenu = ({ bgSubscopes, rootSubscopes }) => {
270
269
 
271
270
  return (
272
271
  <>
273
- <TemplatesLoader />
274
272
  <Submenu
275
273
  icon={defaultIcon}
276
274
  name="glossary"
@@ -14,6 +14,7 @@ import SidebarToggle from "./SidebarToggle";
14
14
  import TaxonomyMenu from "./TaxonomyMenu";
15
15
  import GrantMenu from "./GrantMenu";
16
16
  import MembersMenu from "./MembersMenu";
17
+ import TemplatesLoader from "./TemplatesLoader";
17
18
 
18
19
  export const SideMenu = ({
19
20
  animation = "push",
@@ -35,6 +36,7 @@ export const SideMenu = ({
35
36
  animation={animation}
36
37
  >
37
38
  <SearchMenu />
39
+ <TemplatesLoader />
38
40
  <GlossaryMenu />
39
41
  <CatalogMenu />
40
42
  <GrantMenu />
@@ -1,4 +1,5 @@
1
1
  import { render } from "@truedat/test/render";
2
+ import { fetchTemplates } from "@truedat/core/routines";
2
3
  import GlossaryMenu from "../GlossaryMenu";
3
4
 
4
5
  jest.mock("../../hooks", () => ({
@@ -77,4 +78,20 @@ describe("<GlossaryMenu />", () => {
77
78
  expect(queryByRole("option", { name: "subscope1" })).toBeInTheDocument();
78
79
  expect(queryByRole("option", { name: "subscope2" })).toBeInTheDocument();
79
80
  });
81
+
82
+ it("does not dispatch fetchTemplates on mount (templates preload moved to SideMenu)", () => {
83
+ const dispatched = [];
84
+ render(<GlossaryMenu />, {
85
+ ...renderOpts,
86
+ dispatch: jest.fn((action) => {
87
+ dispatched.push(action);
88
+ return action;
89
+ }),
90
+ });
91
+
92
+ const fetchTemplatesCalls = dispatched.filter(
93
+ (action) => action && action.type === fetchTemplates.TRIGGER,
94
+ );
95
+ expect(fetchTemplatesCalls).toHaveLength(0);
96
+ });
80
97
  });
@@ -1,5 +1,6 @@
1
1
  import { render, waitForLoad } from "@truedat/test/render";
2
2
  import { CATALOG_VIEW_CONFIGS_QUERY } from "@truedat/core/api/queries";
3
+ import { fetchTemplates } from "@truedat/core/routines";
3
4
  import SideMenu from "../SideMenu";
4
5
 
5
6
  jest.mock("@truedat/core/hooks", () => ({
@@ -46,4 +47,63 @@ describe("<SideMenu />", () => {
46
47
  await waitForLoad(rendered);
47
48
  expect(rendered.container).toMatchSnapshot();
48
49
  });
50
+
51
+ describe("global templates preload", () => {
52
+ let dispatched;
53
+ let useAuthorizedMock;
54
+
55
+ beforeEach(() => {
56
+ dispatched = [];
57
+ useAuthorizedMock = require("@truedat/core/hooks").useAuthorized;
58
+ useAuthorizedMock.mockReturnValue(true);
59
+ });
60
+
61
+ const renderWithDispatch = () =>
62
+ render(
63
+ <SideMenu>
64
+ <p>Hello</p>
65
+ </SideMenu>,
66
+ {
67
+ ...renderOpts,
68
+ dispatch: jest.fn((action) => {
69
+ dispatched.push(action);
70
+ return action;
71
+ }),
72
+ },
73
+ );
74
+
75
+ it("dispatches fetchTemplates with no scope on mount", async () => {
76
+ const rendered = renderWithDispatch();
77
+ await waitForLoad(rendered);
78
+ expect(dispatched).toContainEqual(
79
+ expect.objectContaining({
80
+ type: fetchTemplates.TRIGGER,
81
+ payload: { scope: undefined },
82
+ }),
83
+ );
84
+ });
85
+
86
+ it("dispatches fetchTemplates even when user lacks business_glossary_view", async () => {
87
+ useAuthorizedMock.mockImplementation((allowedRoles) => {
88
+ const roles = Array.isArray(allowedRoles)
89
+ ? allowedRoles
90
+ : [allowedRoles];
91
+ // Deny business_glossary_view/management but allow everything else.
92
+ const blocked = new Set([
93
+ "business_glossary_view",
94
+ "business_glossary_management",
95
+ ]);
96
+ return roles.some((r) => r && !blocked.has(r));
97
+ });
98
+
99
+ const rendered = renderWithDispatch();
100
+ await waitForLoad(rendered);
101
+ expect(dispatched).toContainEqual(
102
+ expect.objectContaining({
103
+ type: fetchTemplates.TRIGGER,
104
+ payload: { scope: undefined },
105
+ }),
106
+ );
107
+ });
108
+ });
49
109
  });
@@ -64,6 +64,9 @@ export const SearchContextProvider = (props) => {
64
64
  const [count, setCount] = useState(0);
65
65
 
66
66
  const [onSearchChange, setOnSearchChange] = useState();
67
+ const [refreshCounter, refreshToken] = useState(0);
68
+
69
+ const refreshSearch = useCallback(() => refreshToken((c) => c + 1), []);
67
70
 
68
71
  const debouncedTriggerSearch = useCallback(
69
72
  _.debounce(debounceQueryTime)((filterParam) => {
@@ -270,6 +273,7 @@ export const SearchContextProvider = (props) => {
270
273
  page,
271
274
  size,
272
275
  enrichSearchPayload,
276
+ refreshCounter,
273
277
  ]);
274
278
 
275
279
  const mapDomainFilterKey = (key) => {
@@ -410,6 +414,7 @@ export const SearchContextProvider = (props) => {
410
414
  searchFiltersPropsMapping,
411
415
 
412
416
  setOnSearchChange,
417
+ refreshSearch,
413
418
  };
414
419
 
415
420
  return <SearchContext value={context}>{children}</SearchContext>;
@@ -508,4 +508,52 @@ describe("<SearchContextProvider />", () => {
508
508
 
509
509
  jest.useRealTimers();
510
510
  });
511
+
512
+ it("re-executes the search when refreshSearch is called", async () => {
513
+ jest.useFakeTimers();
514
+ const user = userEvent.setup({
515
+ delay: null,
516
+ advanceTimers: jest.advanceTimersByTime,
517
+ });
518
+ const triggerSearchSpy = jest.fn().mockReturnValue({
519
+ then: (callback) =>
520
+ callback({
521
+ data,
522
+ headers: {},
523
+ }),
524
+ });
525
+ const useSearchLocal = () => ({ trigger: triggerSearchSpy });
526
+
527
+ const RefreshConsumer = () => {
528
+ const { refreshSearch } = useSearchContext();
529
+ return <button onClick={() => refreshSearch()}>refresh</button>;
530
+ };
531
+
532
+ const rendered = render(
533
+ <SearchContextProvider {...searchProps} useSearch={useSearchLocal}>
534
+ <RefreshConsumer />
535
+ </SearchContextProvider>,
536
+ renderOpts,
537
+ );
538
+
539
+ jest.runAllTimers();
540
+
541
+ expect(triggerSearchSpy).toHaveBeenCalledTimes(1);
542
+ const firstCallArgs = triggerSearchSpy.mock.calls[0][0];
543
+
544
+ await user.click(rendered.getByText(/refresh/i));
545
+
546
+ jest.runAllTimers();
547
+
548
+ expect(triggerSearchSpy).toHaveBeenCalledTimes(2);
549
+ expect(triggerSearchSpy).toHaveBeenLastCalledWith(
550
+ expect.objectContaining({
551
+ must: firstCallArgs.must,
552
+ page: firstCallArgs.page,
553
+ size: firstCallArgs.size,
554
+ }),
555
+ );
556
+
557
+ jest.useRealTimers();
558
+ });
511
559
  });