@truedat/core 8.12.1 → 8.12.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.12.1",
3
+ "version": "8.12.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.12.1",
60
+ "@truedat/test": "8.12.2",
61
61
  "identity-obj-proxy": "^3.0.0",
62
62
  "jest": "^29.7.0",
63
63
  "redux-saga-test-plan": "^4.0.6"
@@ -99,5 +99,5 @@
99
99
  "swr": "^2.3.3",
100
100
  "turndown": "^7.2.2"
101
101
  },
102
- "gitHead": "db02aab92701c55a73d5a5b469a7114f9c3a596e"
102
+ "gitHead": "b0000f1b2727cad43ce3c3eb2fa29681488dd2ec"
103
103
  }
@@ -179,7 +179,7 @@ export const FilterMultilevelDropdown = ({
179
179
  open={_.contains(option.id)(open)}
180
180
  canOpen={_.negate(_.isEmpty)(option.children)}
181
181
  selected={_.contains(option.id)(selected)}
182
- {...option}
182
+ {..._.omit("key")(option)}
183
183
  />
184
184
  ))(filteredOptions)}
185
185
  </Dropdown.Menu>
@@ -66,7 +66,6 @@ export const SelectedFilters = ({
66
66
  const options = isSelectedFilter ? selectedFilterValues : null;
67
67
 
68
68
  const props = {
69
- key: filter,
70
69
  activeValues,
71
70
  closeFilter,
72
71
  filter,
@@ -80,15 +79,16 @@ export const SelectedFilters = ({
80
79
  switch (filterType) {
81
80
  case "domain":
82
81
  return useDomainSearchFilter ? (
83
- <DomainSearchFilter {...props} />
82
+ <DomainSearchFilter key={filter} {...props} />
84
83
  ) : (
85
- <FilterMultilevelDropdown {...props} />
84
+ <FilterMultilevelDropdown key={filter} {...props} />
86
85
  );
87
86
  case "hierarchy":
88
- return <HierarchyFilterDropdown {...props} />;
87
+ return <HierarchyFilterDropdown key={filter} {...props} />;
89
88
  case "search":
90
89
  return (
91
90
  <SearchFilterDropdown
91
+ key={filter}
92
92
  {...props}
93
93
  {...searchFiltersPropsMapping[filter]}
94
94
  open={isSelectedFilter}
@@ -96,7 +96,7 @@ export const SelectedFilters = ({
96
96
  />
97
97
  );
98
98
  default:
99
- return <FilterDropdown {...props} />;
99
+ return <FilterDropdown key={filter} {...props} />;
100
100
  }
101
101
  })}
102
102
  <a className="resetFilters" onClick={() => resetFilters()}>
@@ -4,8 +4,6 @@ import { render, waitForLoad } from "@truedat/test/render";
4
4
  import { apiJsonPost } from "@truedat/core/services/api";
5
5
  import { AddMemberForm } from "../AddMemberForm";
6
6
 
7
- const user = userEvent.setup({ delay: null });
8
-
9
7
  jest.mock("@truedat/core/services/api", () => {
10
8
  const originalModule = jest.requireActual("@truedat/core/services/api");
11
9
 
@@ -48,24 +46,23 @@ describe("<AddMemberForm />", () => {
48
46
  });
49
47
 
50
48
  it("calls onSuccess with acl data on submit correctly", async () => {
49
+ const user = userEvent.setup({ delay: null });
51
50
  const rendered = render(<AddMemberForm {...props} />);
52
51
  await waitForLoad(rendered);
53
52
 
54
53
  // Submit button should initially be disabled
55
- await waitFor(async () =>
56
- expect(
57
- await rendered.findByRole("button", { name: /add_member/i }),
58
- ).toBeDisabled(),
59
- );
54
+ expect(
55
+ rendered.getByRole("button", { name: /add_member/i }),
56
+ ).toBeDisabled();
60
57
 
61
58
  // Description
62
59
  await user.click(await rendered.findByText(/john/i));
63
60
  await user.click(await rendered.findByRole("option", { name: "role2" }));
64
61
 
65
62
  // Submit button should now be enabled
66
- await waitFor(async () =>
63
+ await waitFor(() =>
67
64
  expect(
68
- await rendered.findByRole("button", { name: /add_member/i }),
65
+ rendered.getByRole("button", { name: /add_member/i }),
69
66
  ).not.toBeDisabled(),
70
67
  );
71
68
 
@@ -87,6 +84,7 @@ describe("<AddMemberForm />", () => {
87
84
  });
88
85
 
89
86
  it("displays group alias in dropdown when available", async () => {
87
+ const user = userEvent.setup({ delay: null });
90
88
  const rendered = render(<AddMemberForm {...props} />);
91
89
  await waitForLoad(rendered);
92
90
 
@@ -100,6 +98,7 @@ describe("<AddMemberForm />", () => {
100
98
  });
101
99
 
102
100
  it("displays group name when alias is not available", async () => {
101
+ const user = userEvent.setup({ delay: null });
103
102
  const rendered = render(<AddMemberForm {...props} />);
104
103
  await waitForLoad(rendered);
105
104
 
@@ -38,7 +38,9 @@ describe("<ColoredEdge /> metadata badge", () => {
38
38
  const onClick = jest.fn();
39
39
  const metadata = { source: "sales", relation: "derives" };
40
40
  const rendered = render(
41
- <ColoredEdge {...baseProps} data={{ metadata, onClick }} />,
41
+ <svg>
42
+ <ColoredEdge {...baseProps} data={{ metadata, onClick }} />
43
+ </svg>,
42
44
  );
43
45
  const badge = rendered.container.querySelector(
44
46
  ".td-graph-edge-metadata-badge",
@@ -52,7 +54,9 @@ describe("<ColoredEdge /> metadata badge", () => {
52
54
  it("does nothing when the edge has metadata but no click handler", () => {
53
55
  const onClick = jest.fn();
54
56
  const rendered = render(
55
- <ColoredEdge {...baseProps} data={{ metadata: { source: "sales" } }} />,
57
+ <svg>
58
+ <ColoredEdge {...baseProps} data={{ metadata: { source: "sales" } }} />
59
+ </svg>,
56
60
  );
57
61
  const badge = rendered.container.querySelector(
58
62
  ".td-graph-edge-metadata-badge",
@@ -80,10 +84,12 @@ describe("<ColoredEdge /> arrow markers", () => {
80
84
 
81
85
  it("draws the filled arrow at the target for impact edges", () => {
82
86
  const rendered = render(
83
- <ColoredEdge
84
- {...arrowProps}
85
- data={{ edgePath: "orthogonal", markerPosition: "target" }}
86
- />,
87
+ <svg>
88
+ <ColoredEdge
89
+ {...arrowProps}
90
+ data={{ edgePath: "orthogonal", markerPosition: "target" }}
91
+ />
92
+ </svg>,
87
93
  );
88
94
  const arrow = rendered.container.querySelector(".td-graph-edge-arrow");
89
95
 
@@ -93,10 +99,12 @@ describe("<ColoredEdge /> arrow markers", () => {
93
99
 
94
100
  it("draws the filled arrow at the source for traceability edges", () => {
95
101
  const rendered = render(
96
- <ColoredEdge
97
- {...arrowProps}
98
- data={{ edgePath: "orthogonal", markerPosition: "source" }}
99
- />,
102
+ <svg>
103
+ <ColoredEdge
104
+ {...arrowProps}
105
+ data={{ edgePath: "orthogonal", markerPosition: "source" }}
106
+ />
107
+ </svg>,
100
108
  );
101
109
  const arrow = rendered.container.querySelector(".td-graph-edge-arrow");
102
110
 
@@ -7,6 +7,17 @@ describe("<EditableCell />", () => {
7
7
  const onChange = jest.fn();
8
8
  const value = "value";
9
9
  const placeholder = "placeholder";
10
+ const renderCell = (props) =>
11
+ render(
12
+ <table>
13
+ <tbody>
14
+ <tr>
15
+ <EditableCell {...props} />
16
+ </tr>
17
+ </tbody>
18
+ </table>,
19
+ { messages },
20
+ );
10
21
 
11
22
  it("matches the latest snapshot", async () => {
12
23
  const props = {
@@ -14,7 +25,7 @@ describe("<EditableCell />", () => {
14
25
  value,
15
26
  placeholder,
16
27
  };
17
- const rendered = render(<EditableCell {...props} />, { messages });
28
+ const rendered = renderCell(props);
18
29
  await waitForLoad(rendered);
19
30
  expect(rendered.container).toMatchSnapshot();
20
31
  });
@@ -25,7 +36,7 @@ describe("<EditableCell />", () => {
25
36
  value,
26
37
  placeholder,
27
38
  };
28
- const rendered = render(<EditableCell {...props} />, { messages });
39
+ const rendered = renderCell(props);
29
40
  await waitForLoad(rendered);
30
41
 
31
42
  const user = userEvent.setup({ delay: null });
@@ -39,7 +50,7 @@ describe("<EditableCell />", () => {
39
50
  value,
40
51
  placeholder,
41
52
  };
42
- const rendered = render(<EditableCell {...props} />, { messages });
53
+ const rendered = renderCell(props);
43
54
  await waitForLoad(rendered);
44
55
 
45
56
  const user = userEvent.setup({ delay: null });
@@ -2,29 +2,41 @@
2
2
 
3
3
  exports[`<EditableCell /> clicking the cell will enter edition mode and match snapshot 1`] = `
4
4
  <div>
5
- <td
6
- class="five wide no-padding"
7
- >
8
- <div
9
- class="ui fluid input"
10
- >
11
- <input
12
- maxlength="255"
13
- placeholder="placeholder"
14
- type="text"
15
- value="value"
16
- />
17
- </div>
18
- </td>
5
+ <table>
6
+ <tbody>
7
+ <tr>
8
+ <td
9
+ class="five wide no-padding"
10
+ >
11
+ <div
12
+ class="ui fluid input"
13
+ >
14
+ <input
15
+ maxlength="255"
16
+ placeholder="placeholder"
17
+ type="text"
18
+ value="value"
19
+ />
20
+ </div>
21
+ </td>
22
+ </tr>
23
+ </tbody>
24
+ </table>
19
25
  </div>
20
26
  `;
21
27
 
22
28
  exports[`<EditableCell /> matches the latest snapshot 1`] = `
23
29
  <div>
24
- <td
25
- class="five wide cursor-pointer"
26
- >
27
- value
28
- </td>
30
+ <table>
31
+ <tbody>
32
+ <tr>
33
+ <td
34
+ class="five wide cursor-pointer"
35
+ >
36
+ value
37
+ </td>
38
+ </tr>
39
+ </tbody>
40
+ </table>
29
41
  </div>
30
42
  `;
@@ -185,7 +185,7 @@ export default function FilterMultilevelDropdown() {
185
185
  open={_.contains(option.id)(open)}
186
186
  canOpen={_.negate(_.isEmpty)(option.children)}
187
187
  selected={_.contains(option.id)(selected)}
188
- {...option}
188
+ {..._.omit("key")(option)}
189
189
  />
190
190
  ))(filteredOptions)}
191
191
  </Dropdown.Menu>
@@ -1,4 +1,4 @@
1
- import { waitFor } from "@testing-library/react";
1
+ import { act, waitFor } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
3
  import { render } from "@truedat/test/render";
4
4
  import {
@@ -60,6 +60,12 @@ const renderOpts = {
60
60
  },
61
61
  };
62
62
 
63
+ const runAllTimers = async () => {
64
+ await act(async () => {
65
+ jest.runAllTimers();
66
+ });
67
+ };
68
+
63
69
  describe("<SearchContextProvider />", () => {
64
70
  const ChildComponent = () => <div>Child Component</div>;
65
71
  const ContextConsumer = ({ onSearchChangeSpy }) => {
@@ -180,7 +186,7 @@ describe("<SearchContextProvider />", () => {
180
186
  renderOpts,
181
187
  );
182
188
 
183
- jest.runAllTimers();
189
+ await runAllTimers();
184
190
 
185
191
  expect(triggerFiltersSpy).toHaveBeenCalledWith(
186
192
  expect.objectContaining({
@@ -215,7 +221,7 @@ describe("<SearchContextProvider />", () => {
215
221
  renderOpts,
216
222
  );
217
223
 
218
- jest.runAllTimers();
224
+ await runAllTimers();
219
225
 
220
226
  expect(triggerSearchSpy).toHaveBeenCalledWith(
221
227
  expect.objectContaining({
@@ -253,7 +259,7 @@ describe("<SearchContextProvider />", () => {
253
259
  renderOpts,
254
260
  );
255
261
 
256
- jest.runAllTimers();
262
+ await runAllTimers();
257
263
 
258
264
  expect(triggerSearchSpy).toHaveBeenCalledWith(
259
265
  expect.objectContaining({
@@ -302,7 +308,7 @@ describe("<SearchContextProvider />", () => {
302
308
  renderOpts,
303
309
  );
304
310
 
305
- jest.runAllTimers();
311
+ await runAllTimers();
306
312
 
307
313
  await waitFor(() =>
308
314
  expect(
@@ -339,7 +345,7 @@ describe("<SearchContextProvider />", () => {
339
345
  renderOpts,
340
346
  );
341
347
 
342
- jest.runAllTimers();
348
+ await runAllTimers();
343
349
 
344
350
  await waitFor(() =>
345
351
  expect(
@@ -363,7 +369,7 @@ describe("<SearchContextProvider />", () => {
363
369
  renderOpts,
364
370
  );
365
371
 
366
- jest.runAllTimers();
372
+ await runAllTimers();
367
373
 
368
374
  await user.click(rendered.getByText(/set-callback/i));
369
375
 
@@ -437,7 +443,7 @@ describe("<SearchContextProvider />", () => {
437
443
  renderOpts,
438
444
  );
439
445
 
440
- jest.runAllTimers();
446
+ await runAllTimers();
441
447
 
442
448
  await user.click(rendered.getByText(/set-callback/i));
443
449
  await user.click(rendered.getByText(/sort-empty/i));
@@ -536,14 +542,14 @@ describe("<SearchContextProvider />", () => {
536
542
  renderOpts,
537
543
  );
538
544
 
539
- jest.runAllTimers();
545
+ await runAllTimers();
540
546
 
541
547
  expect(triggerSearchSpy).toHaveBeenCalledTimes(1);
542
548
  const firstCallArgs = triggerSearchSpy.mock.calls[0][0];
543
549
 
544
550
  await user.click(rendered.getByText(/refresh/i));
545
551
 
546
- jest.runAllTimers();
552
+ await runAllTimers();
547
553
 
548
554
  expect(triggerSearchSpy).toHaveBeenCalledTimes(2);
549
555
  expect(triggerSearchSpy).toHaveBeenLastCalledWith(
@@ -3,8 +3,14 @@ import { render } from "@truedat/test/render";
3
3
  import { waitFor } from "@testing-library/react";
4
4
  import userEvent from "@testing-library/user-event";
5
5
  import SearchContextWrapper from "@truedat/core/components/common/SearchContextWrapper";
6
+ import { useUserFilters } from "@truedat/core/hooks/useUserFilters";
6
7
  import SearchWidget from "../SearchWidget";
7
8
 
9
+ jest.mock("@truedat/core/hooks/useUserFilters", () => ({
10
+ ...jest.requireActual("@truedat/core/hooks/useUserFilters"),
11
+ useUserFilters: jest.fn(() => ({ userFilters: [], loading: false })),
12
+ }));
13
+
8
14
  const messages = {
9
15
  en: {
10
16
  "search.placeholder": "Search...",
@@ -40,6 +46,7 @@ describe("<SearchWidget/>", () => {
40
46
  </SearchContextWrapper>,
41
47
  renderOpts,
42
48
  );
49
+ expect(useUserFilters).toHaveBeenCalledWith("foo", undefined);
43
50
  expect(container).toMatchSnapshot();
44
51
  });
45
52
 
@@ -18,4 +18,24 @@ describe("selectors: makeActiveFiltersSelector", () => {
18
18
  makeActiveFiltersSelector("conceptActiveFilters")(state, props),
19
19
  ).toEqual({ foo: "bar", status: ["pending"] });
20
20
  });
21
+
22
+ it("keeps the same result for equal filters and updates it when filters change", () => {
23
+ const selector = makeActiveFiltersSelector("conceptActiveFilters");
24
+ const first = selector(
25
+ { conceptActiveFilters: { foo: "bar" } },
26
+ { defaultFilters: { status: ["pending"] } },
27
+ );
28
+ const equal = selector(
29
+ { conceptActiveFilters: { foo: "bar" } },
30
+ { defaultFilters: { status: ["pending"] } },
31
+ );
32
+ const changed = selector(
33
+ { conceptActiveFilters: { foo: "baz" } },
34
+ { defaultFilters: { status: ["pending"] } },
35
+ );
36
+
37
+ expect(equal).toBe(first);
38
+ expect(changed).not.toBe(first);
39
+ expect(changed).toEqual({ foo: "baz", status: ["pending"] });
40
+ });
21
41
  });
@@ -1,4 +1,7 @@
1
- import { makeSearchQuerySelector } from "../makeSearchQuerySelector";
1
+ import {
2
+ makeSearchQuerySelector,
3
+ queryPropsSelector,
4
+ } from "../makeSearchQuerySelector";
2
5
 
3
6
  const searchQuerySelector = makeSearchQuerySelector(
4
7
  "conceptQuery",
@@ -7,6 +10,40 @@ const searchQuerySelector = makeSearchQuerySelector(
7
10
  );
8
11
 
9
12
  describe("selectors: makeSearchQuerySelector", () => {
13
+ it("keeps equal query props referentially stable", () => {
14
+ const first = queryPropsSelector({}, { pageSize: 10, ignored: "first" });
15
+ const equal = queryPropsSelector({}, { pageSize: 10, ignored: "second" });
16
+ const changed = queryPropsSelector({}, { pageSize: 20 });
17
+
18
+ expect(equal).toBe(first);
19
+ expect(changed).not.toBe(first);
20
+ expect(changed).toEqual({ pageSize: 20 });
21
+ });
22
+
23
+ it("keeps equal search payloads referentially stable", () => {
24
+ const selector = makeSearchQuerySelector(
25
+ "conceptQuery",
26
+ "conceptActiveFilters",
27
+ "dateFilter",
28
+ );
29
+ const first = selector({
30
+ conceptQuery: { query: "foo" },
31
+ conceptActiveFilters: { status: ["draft"] },
32
+ });
33
+ const equal = selector({
34
+ conceptQuery: { query: "foo" },
35
+ conceptActiveFilters: { status: ["draft"] },
36
+ });
37
+ const changed = selector({
38
+ conceptQuery: { query: "bar" },
39
+ conceptActiveFilters: { status: ["draft"] },
40
+ });
41
+
42
+ expect(equal).toBe(first);
43
+ expect(changed).not.toBe(first);
44
+ expect(changed).toMatchObject({ query: "bar" });
45
+ });
46
+
10
47
  describe("with an empty query string", () => {
11
48
  const conceptQuery = { query: " " };
12
49
  const conceptActiveFilters = { foo: ["foo1"], bar: [] };
@@ -1,17 +1,14 @@
1
1
  import _ from "lodash/fp";
2
- import { createSelector, createSelectorCreator, lruMemoize } from "reselect";
2
+ import { createSelector, lruMemoize } from "reselect";
3
3
 
4
4
  const defaultFilters = false;
5
5
 
6
6
  export const makeActiveFiltersSelector = (activeFiltersProp) => {
7
- const activeFiltersSelector = createSelector(
7
+ return createSelector(
8
8
  _.prop(activeFiltersProp),
9
9
  (_state, props) => _.propOr(defaultFilters, "defaultFilters")(props),
10
10
  (activeFilters, defaultFilters) =>
11
11
  defaultFilters ? { ...defaultFilters, ...activeFilters } : activeFilters,
12
- );
13
- return createSelectorCreator(lruMemoize, _.isEqual)(
14
- activeFiltersSelector,
15
- _.identity,
12
+ { memoize: lruMemoize, memoizeOptions: { resultEqualityCheck: _.isEqual } },
16
13
  );
17
14
  };
@@ -1,20 +1,12 @@
1
- import {
2
- identity,
3
- isEmpty,
4
- isEqual,
5
- mapKeys,
6
- omitBy,
7
- pick,
8
- prop,
9
- trim,
10
- } from "lodash/fp";
1
+ import { isEmpty, isEqual, mapKeys, omitBy, pick, prop, trim } from "lodash/fp";
11
2
  import { createSelector, createSelectorCreator, lruMemoize } from "reselect";
12
3
  import { getSearchPayload } from "../services/filters";
13
4
  import { makeDateFiltersSelector } from "./makeDateFiltersSelector";
14
5
 
15
- export const queryPropsSelector = createSelectorCreator(lruMemoize, isEqual)(
16
- (_state, props) => pick(["defaultFilters", "linkable", "pageSize"])(props),
17
- identity,
6
+ export const queryPropsSelector = createSelector(
7
+ (_state, props) => props,
8
+ (props) => pick(["defaultFilters", "linkable", "pageSize"])(props),
9
+ { memoize: lruMemoize, memoizeOptions: { resultEqualityCheck: isEqual } },
18
10
  );
19
11
 
20
12
  export const makeSearchFiltersSelector = (activeFiltersProp, dateFilterProp) =>
@@ -38,7 +30,7 @@ export const makeSearchQuerySelector = (
38
30
  activeFiltersProp,
39
31
  dateFilterProp,
40
32
  );
41
- const searchQuerySelector = createSelector(
33
+ return createSelector(
42
34
  prop(queryProp),
43
35
  searchFiltersSelector,
44
36
  queryPropsSelector,
@@ -61,9 +53,6 @@ export const makeSearchQuerySelector = (
61
53
  : { must: filters, page: page - 1, sort: sort };
62
54
  return getSearchPayload(q, ownProps);
63
55
  },
64
- );
65
- return createSelectorCreator(lruMemoize, isEqual)(
66
- searchQuerySelector,
67
- identity,
56
+ { memoize: lruMemoize, memoizeOptions: { resultEqualityCheck: isEqual } },
68
57
  );
69
58
  };