@truedat/qx 7.13.6 → 7.13.8

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.
Files changed (74) hide show
  1. package/package.json +3 -3
  2. package/src/components/QxRoutes.js +8 -6
  3. package/src/components/common/ResourceSelector.js +25 -6
  4. package/src/components/common/TypeSelector.js +14 -9
  5. package/src/components/common/__tests__/__snapshots__/ResourceSelector.spec.js.snap +269 -241
  6. package/src/components/common/__tests__/__snapshots__/TypeSelector.spec.js.snap +198 -190
  7. package/src/components/common/expressions/Clauses.js +19 -11
  8. package/src/components/common/expressions/Condition.js +32 -31
  9. package/src/components/common/expressions/FieldSelector.js +30 -16
  10. package/src/components/common/expressions/FunctionSelector.js +38 -23
  11. package/src/components/common/expressions/ShapeSelector.js +6 -5
  12. package/src/components/common/expressions/__tests__/ShapeSelector.spec.js +1 -1
  13. package/src/components/common/expressions/__tests__/__snapshots__/Clauses.spec.js.snap +36 -12
  14. package/src/components/common/expressions/__tests__/__snapshots__/Condition.spec.js.snap +87 -75
  15. package/src/components/common/expressions/__tests__/__snapshots__/ConstantSelector.spec.js.snap +99 -97
  16. package/src/components/common/expressions/__tests__/__snapshots__/Expression.spec.js.snap +236 -216
  17. package/src/components/common/expressions/__tests__/__snapshots__/FunctionArgs.spec.js.snap +97 -89
  18. package/src/components/common/expressions/__tests__/__snapshots__/FunctionSelector.spec.js.snap +373 -345
  19. package/src/components/common/expressions/constantInputs/AnySelector.js +2 -1
  20. package/src/components/common/expressions/constantInputs/BooleanSelector.js +20 -15
  21. package/src/components/common/expressions/constantInputs/DefaultSelector.js +0 -1
  22. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/AnySelector.spec.js.snap +189 -182
  23. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/BooleanSelector.spec.js.snap +74 -66
  24. package/src/components/common/expressions/constantInputs/__tests__/__snapshots__/DefaultSelector.spec.js.snap +2 -4
  25. package/src/components/common/resourceSelectors/DataStructureSelector.js +5 -4
  26. package/src/components/common/resourceSelectors/DataViewSelector.js +4 -3
  27. package/src/components/common/resourceSelectors/ReferenceDatasetSelector.js +4 -3
  28. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataStructureSelector.spec.js.snap +65 -61
  29. package/src/components/common/resourceSelectors/__tests__/__snapshots__/DataViewSelector.spec.js.snap +38 -34
  30. package/src/components/common/resourceSelectors/__tests__/__snapshots__/ReferenceDatasetSelector.spec.js.snap +38 -34
  31. package/src/components/dataViews/BreadCrumb.js +20 -0
  32. package/src/components/dataViews/DataViewEditor.js +169 -178
  33. package/src/components/dataViews/DataViews.js +113 -135
  34. package/src/components/dataViews/__tests__/AdvancedDataViewEditor.spec.js +260 -0
  35. package/src/components/dataViews/__tests__/DataViewEditor.spec.js +173 -239
  36. package/src/components/dataViews/__tests__/DataViewSelect.spec.js +1 -1
  37. package/src/components/dataViews/__tests__/DataViews.spec.js +124 -51
  38. package/src/components/dataViews/__tests__/Queryable.spec.js +1 -1
  39. package/src/components/dataViews/__tests__/Queryables.spec.js +1 -1
  40. package/src/components/dataViews/__tests__/SimpleDataViewEditor.spec.js +164 -0
  41. package/src/components/dataViews/__tests__/__snapshots__/{DataViewEditor.spec.js.snap → AdvancedDataViewEditor.spec.js.snap} +230 -200
  42. package/src/components/dataViews/__tests__/__snapshots__/DataViews.spec.js.snap +141 -29
  43. package/src/components/dataViews/__tests__/__snapshots__/Queryable.spec.js.snap +184 -141
  44. package/src/components/dataViews/__tests__/__snapshots__/Queryables.spec.js.snap +126 -91
  45. package/src/components/dataViews/actions/CancelButton.js +33 -0
  46. package/src/components/dataViews/actions/DeleteButton.js +33 -0
  47. package/src/components/dataViews/advancedForm/AdvancedDataViewEditor.js +159 -0
  48. package/src/components/dataViews/{DataViewSelect.js → advancedForm/DataViewSelect.js} +2 -2
  49. package/src/components/dataViews/{Queryable.js → advancedForm/Queryable.js} +2 -2
  50. package/src/components/dataViews/queryableFunctions.js +7 -0
  51. package/src/components/dataViews/queryableProperties/GroupBy.js +23 -27
  52. package/src/components/dataViews/queryableProperties/Join.js +0 -3
  53. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/From.spec.js.snap +30 -26
  54. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/GroupBy.spec.js.snap +130 -102
  55. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Join.spec.js.snap +42 -31
  56. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Select.spec.js.snap +81 -69
  57. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/SelectField.spec.js.snap +62 -54
  58. package/src/components/dataViews/queryableProperties/__tests__/__snapshots__/Where.spec.js.snap +12 -4
  59. package/src/components/dataViews/simpleForm/AggregationForm.js +179 -0
  60. package/src/components/dataViews/simpleForm/DatasetForm.js +199 -0
  61. package/src/components/dataViews/simpleForm/FormQueryable.js +114 -0
  62. package/src/components/dataViews/simpleForm/InformationForm.js +107 -0
  63. package/src/components/dataViews/simpleForm/SelectionForm.js +50 -0
  64. package/src/components/dataViews/simpleForm/SimpleDataViewEditor.js +265 -0
  65. package/src/components/functions/__tests__/__snapshots__/FunctionEditor.spec.js.snap +663 -631
  66. package/src/components/functions/__tests__/__snapshots__/FunctionParams.spec.js.snap +113 -109
  67. package/src/components/qualityControls/__tests__/__snapshots__/ControlProperties.spec.js.snap +92 -76
  68. package/src/components/qualityControls/__tests__/__snapshots__/EditQualityControl.spec.js.snap +108 -80
  69. package/src/components/qualityControls/__tests__/__snapshots__/NewDraftQualityControl.spec.js.snap +108 -80
  70. package/src/components/qualityControls/__tests__/__snapshots__/QualityControlEditor.spec.js.snap +108 -80
  71. package/src/components/qualityControls/controlProperties/__tests__/__snapshots__/Count.spec.js.snap +40 -36
  72. package/src/components/qualityControls/controlProperties/__tests__/__snapshots__/Ratio.spec.js.snap +92 -76
  73. package/src/hooks/useDataViews.js +7 -0
  74. /package/src/components/dataViews/{Queryables.js → advancedForm/Queryables.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  import { render } from "@truedat/test/render";
2
2
  import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
3
- import DataViewSelect from "../DataViewSelect";
3
+ import DataViewSelect from "../advancedForm/DataViewSelect";
4
4
 
5
5
  const renderOpts = {
6
6
  messages: {
@@ -1,69 +1,142 @@
1
+ import React from "react";
1
2
  import { render, waitForLoad } from "@truedat/test/render";
2
- import { REFERENCE_DATASETS_HEADERS_QUERY } from "@truedat/dd/api/queries";
3
+ import userEvent from "@testing-library/user-event";
3
4
  import DataViews from "../DataViews";
5
+ import { useDataViews, useDataViewDelete } from "@truedat/qx/hooks/useDataViews";
4
6
 
5
- jest.mock("@truedat/qx/hooks/useFunctions", () => {
6
- const originalModule = jest.requireActual("@truedat/qx/hooks/useFunctions");
7
+ const mockFormatMessage = jest.fn(({ id }) => id);
7
8
 
8
- return {
9
- __esModule: true,
10
- ...originalModule,
11
- useFunctions: jest.fn(() => ({})),
12
- };
13
- });
9
+ jest.mock("react-intl", () => ({
10
+ ...jest.requireActual("react-intl"),
11
+ useIntl: () => ({
12
+ formatMessage: mockFormatMessage,
13
+ }),
14
+ }));
15
+
16
+ jest.mock("@truedat/core/routes", () => ({
17
+ DATA_VIEW: "/dataviews/:id",
18
+ DATA_VIEWS_NEW: "/dataviews/new",
19
+ }));
20
+
21
+ jest.mock("@truedat/qx/hooks/useDataViews", () => ({
22
+ useDataViews: jest.fn(),
23
+ useDataViewDelete: jest.fn(),
24
+ }));
25
+
26
+
27
+ describe("<DataViews />", () => {
28
+ beforeEach(() => {
29
+ useDataViewDelete.mockReturnValue({
30
+ trigger: jest.fn().mockResolvedValue({}),
31
+ isMutating: false,
32
+ });
33
+ });
34
+
35
+ afterEach(() => {
36
+ jest.clearAllMocks();
37
+ });
38
+
39
+ it("renders empty state when no data views", async () => {
40
+ useDataViews.mockReturnValue({
41
+ data: { data: [] },
42
+ loading: false,
43
+ });
14
44
 
15
- jest.mock("@truedat/qx/hooks/useDataViews", () => {
16
- const originalModule = jest.requireActual("@truedat/qx/hooks/useDataViews");
45
+ const rendered = render(<DataViews />);
46
+ await waitForLoad(rendered);
47
+
48
+ expect(rendered.getByRole("heading", { name: /dataViews.header/i })).toBeInTheDocument();
49
+ expect(rendered.getByText(/dataViews.empty_list/i)).toBeInTheDocument();
50
+
51
+ expect(rendered.getByRole("button", { name: /dataViews.action.new/i })).toBeInTheDocument();
52
+ expect(rendered.container).toMatchSnapshot();
53
+ });
17
54
 
18
- return {
19
- __esModule: true,
20
- ...originalModule,
21
- useDataViews: jest.fn(() => ({
55
+ it("renders cards and the mode label, and the Open link points to the correct route", async () => {
56
+ useDataViews.mockReturnValue({
22
57
  data: {
23
58
  data: [
24
- {
25
- id: 4,
26
- description: "Description",
27
- name: "StructureView",
28
- },
59
+ { id: 2, name: "Inventory", description: "Stock levels", mode: "advanced" },
60
+ { id: 1, name: "Users Spain", description: "Active users in Spain", mode: "guided" },
29
61
  ],
30
62
  },
31
63
  loading: false,
32
- })),
33
- useDataViewCreate: jest.fn(() => ({
34
- trigger: jest.fn(() => new Promise(() => {})),
35
- isMutating: false,
36
- })),
37
- useDataViewDelete: jest.fn(() => ({
38
- trigger: jest.fn(() => new Promise(() => {})),
39
- isMutating: false,
40
- })),
41
- useDataViewUpdate: jest.fn(() => ({
42
- trigger: jest.fn(() => new Promise(() => {})),
43
- isMutating: false,
44
- })),
45
- };
46
- });
64
+ });
47
65
 
48
- const dataSet = {
49
- id: "7",
50
- name: "dataset1",
51
- headers: ["campo1", "campo2"],
52
- };
66
+ const rendered = render(<DataViews />);
67
+ await waitForLoad(rendered);
53
68
 
54
- const referenceDatasetsMock = {
55
- request: { query: REFERENCE_DATASETS_HEADERS_QUERY },
56
- result: { data: { referenceDatasets: [dataSet], loading: false } },
57
- };
69
+ // Ordered by id asc: first card should be id=1 "Users Spain"
70
+ expect(rendered.getByText("Users Spain")).toBeInTheDocument();
71
+ expect(rendered.getByText("Inventory")).toBeInTheDocument();
58
72
 
59
- const renderOpts = {
60
- mocks: [referenceDatasetsMock],
61
- };
73
+
74
+ const guidedIcon = rendered.container.querySelector(".dataViews\\.mode\\.icon\\.guided.icon");
75
+ const advancedIcon = rendered.container.querySelector(".dataViews\\.mode\\.icon\\.advanced.icon");
76
+ expect(guidedIcon).toBeInTheDocument();
77
+ expect(advancedIcon).toBeInTheDocument();
62
78
 
63
- describe("<DataViews />", () => {
64
- it("matches the latest snapshot", async () => {
65
- const rendered = render(<DataViews />, renderOpts);
66
- await waitForLoad(rendered);
79
+ const dataViewLinks = rendered.container.querySelectorAll('a[href^="/dataviews/"]');
80
+ expect(dataViewLinks.length).toBe(3);
81
+
82
+ const hrefs = Array.from(dataViewLinks).map(a => a.getAttribute("href"));
83
+ expect(hrefs).toContain("/dataviews/1");
84
+ expect(hrefs).toContain("/dataviews/2");
67
85
  expect(rendered.container).toMatchSnapshot();
68
86
  });
87
+
88
+ it("filters by search input across name and description", async () => {
89
+ const user = userEvent.setup({ delay: null });
90
+ useDataViews.mockReturnValue({
91
+ data: {
92
+ data: [
93
+ { id: 1, name: "Revenue Report", description: "Monthly KPIs", mode: "advanced" },
94
+ { id: 2, name: "Users", description: "Active users in Spain", mode: "guided" },
95
+ { id: 3, name: "Inventory", description: "Stock and SKUs", mode: "advanced" },
96
+ ],
97
+ },
98
+ loading: false,
99
+ });
100
+
101
+ const rendered = render(<DataViews />);
102
+ await waitForLoad(rendered);
103
+
104
+ // All cards visible initially
105
+ expect(rendered.getByText("Revenue Report")).toBeInTheDocument();
106
+ expect(rendered.getByText("Users")).toBeInTheDocument();
107
+ expect(rendered.getByText("Inventory")).toBeInTheDocument();
108
+
109
+ const input = rendered.getByPlaceholderText(/search/i);
110
+ await user.type(input, "spain");
111
+
112
+ // Only "Users" card should remain
113
+ expect(rendered.queryByText("Revenue Report")).not.toBeInTheDocument();
114
+ expect(rendered.getByText("Users")).toBeInTheDocument();
115
+ expect(rendered.queryByText("Inventory")).not.toBeInTheDocument();
116
+ });
117
+
118
+ it("shows untitled message when name is missing", async () => {
119
+ useDataViews.mockReturnValue({
120
+ data: { data: [{ id: 1, name: "", description: "desc", mode: "guided" }] },
121
+ loading: false,
122
+ });
123
+
124
+ const rendered = render(<DataViews />);
125
+ await waitForLoad(rendered);
126
+
127
+ expect(rendered.getByText("dataViews.untitled")).toBeInTheDocument();
128
+ });
129
+
130
+ it("has a button to create new data view linking to the create route", () => {
131
+ useDataViews.mockReturnValue({
132
+ data: { data: [] },
133
+ loading: false,
134
+ });
135
+
136
+ const rendered = render(<DataViews />);
137
+
138
+ const newBtn = rendered.getByRole("button", { name: /dataViews.action.new/i });
139
+ expect(newBtn).toBeInTheDocument();
140
+ expect(newBtn.getAttribute("href")).toBe("/dataviews/new");
141
+ });
69
142
  });
@@ -1,6 +1,6 @@
1
1
  import { render } from "@truedat/test/render";
2
2
  import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
3
- import Queryable from "../Queryable";
3
+ import Queryable from "../advancedForm/Queryable";
4
4
 
5
5
  const renderOpts = {
6
6
  messages: {
@@ -2,7 +2,7 @@ import userEvent from "@testing-library/user-event";
2
2
  import { waitFor } from "@testing-library/react";
3
3
  import { render, waitForLoad } from "@truedat/test/render";
4
4
  import TestFormWrapper from "@truedat/qx/components/common/TestFormWrapper";
5
- import Queryables from "../Queryables";
5
+ import Queryables from "../advancedForm/Queryables";
6
6
 
7
7
  describe("<Queryables />", () => {
8
8
  it("matches the latest snapshot with from queryable", async () => {
@@ -0,0 +1,164 @@
1
+ import React from "react";
2
+ import { render, waitForLoad } from "@truedat/test/render";
3
+ import { screen } from "@testing-library/react";
4
+ import SimpleDataViewEditor from "../simpleForm/SimpleDataViewEditor";
5
+
6
+ // Keep intl messages simple (just echo the id)
7
+ const mockFormatMessage = jest.fn(({ id }) => id);
8
+ jest.mock("react-intl", () => ({
9
+ ...jest.requireActual("react-intl"),
10
+ useIntl: () => ({ formatMessage: mockFormatMessage }),
11
+ }));
12
+
13
+ // We DO NOT mock child forms; they will render as-is.
14
+ // We also keep QxContext and action buttons unmocked.
15
+
16
+ // Helpers
17
+ const baseSelected = {
18
+ id: undefined,
19
+ name: "",
20
+ description: "",
21
+ mode: "guided",
22
+ queryables: [{ alias: "a", type: "from", properties: { resource: 1 } }],
23
+ select: { type: "select" },
24
+ };
25
+ const baseContext = {
26
+ dataViews: [],
27
+ referenceDatasets: [],
28
+ functions: [],
29
+ currentDataViewId: undefined,
30
+ };
31
+
32
+ describe("<SimpleDataViewEditor /> (own rendering only, no child mocks)", () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+
37
+ it("returns null when selectedDataView is not provided", async () => {
38
+ const rendered = render(
39
+ <SimpleDataViewEditor
40
+ selectedDataView={undefined}
41
+ context={baseContext}
42
+ onSubmit={jest.fn()}
43
+ onCancel={jest.fn()}
44
+ isSubmitting={false}
45
+ mode="guided"
46
+ />
47
+ );
48
+ await waitForLoad(rendered);
49
+ expect(rendered.container).toBeEmptyDOMElement();
50
+ });
51
+
52
+ it("renders step headers and the forward action on initial step", async () => {
53
+ const rendered = render(
54
+ <SimpleDataViewEditor
55
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
56
+ context={baseContext}
57
+ onSubmit={jest.fn()}
58
+ onCancel={jest.fn()}
59
+ isSubmitting={false}
60
+ mode="guided"
61
+ />
62
+ );
63
+ await waitForLoad(rendered);
64
+
65
+ // Step titles (they come from this component via formatMessage)
66
+ expect(
67
+ rendered.getByText("dataViews.form.step.information.title")
68
+ ).toBeInTheDocument();
69
+ expect(
70
+ rendered.getByText("dataViews.form.step.dataset.title")
71
+ ).toBeInTheDocument();
72
+ expect(
73
+ rendered.getByText("dataViews.form.step.aggregations.title")
74
+ ).toBeInTheDocument();
75
+ expect(
76
+ rendered.getByText("dataViews.form.step.select.title")
77
+ ).toBeInTheDocument();
78
+
79
+ // Initial actions: Cancel exists; Forward exists; Back should not (step 0)
80
+ expect(
81
+ screen.getByRole("button", { name: /dataViews\.action\.forward/i })
82
+ ).toBeInTheDocument();
83
+ expect(
84
+ screen.queryByRole("button", { name: /dataViews\.action\.back/i })
85
+ ).not.toBeInTheDocument();
86
+
87
+ // Save button only appears on last step → not present yet
88
+ expect(
89
+ screen.queryByRole("button", { name: /actions\.save/i })
90
+ ).not.toBeInTheDocument();
91
+ });
92
+
93
+
94
+ it("disables the forward action (and keeps back hidden) when isSubmitting is true on step 0", async () => {
95
+ const rendered = render(
96
+ <SimpleDataViewEditor
97
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
98
+ context={baseContext}
99
+ onSubmit={jest.fn()}
100
+ onCancel={jest.fn()}
101
+ isSubmitting={true}
102
+ mode="guided"
103
+ />
104
+ );
105
+ await waitForLoad(rendered);
106
+
107
+ const forward = screen.getByRole("button", {
108
+ name: /dataViews\.action\.forward/i,
109
+ });
110
+ expect(forward).toBeDisabled();
111
+
112
+ expect(
113
+ screen.queryByRole("button", { name: /dataViews\.action\.back/i })
114
+ ).not.toBeInTheDocument();
115
+ });
116
+
117
+ it("renders Cancel and Forward buttons consistently on initial render", async () => {
118
+ const rendered = render(
119
+ <SimpleDataViewEditor
120
+ selectedDataView={{ ...baseSelected, source_id: 123 }}
121
+ context={baseContext}
122
+ onSubmit={jest.fn()}
123
+ onCancel={jest.fn()}
124
+ isSubmitting={false}
125
+ mode="guided"
126
+ />
127
+ );
128
+ await waitForLoad(rendered);
129
+
130
+ expect(rendered.getByText(/cancel/i)).toBeInTheDocument();
131
+
132
+ const forward = rendered.getByRole("button", {
133
+ name: /dataViews\.action\.forward/i,
134
+ });
135
+ expect(forward).toBeInTheDocument();
136
+ });
137
+
138
+ it("renders step descriptions (from this component) regardless of child internals", async () => {
139
+ const rendered = render(
140
+ <SimpleDataViewEditor
141
+ selectedDataView={{ ...baseSelected, source_id: 10 }}
142
+ context={baseContext}
143
+ onSubmit={jest.fn()}
144
+ onCancel={jest.fn()}
145
+ isSubmitting={false}
146
+ mode="guided"
147
+ />
148
+ );
149
+ await waitForLoad(rendered);
150
+
151
+ expect(
152
+ rendered.getByText("dataViews.form.step.information.description")
153
+ ).toBeInTheDocument();
154
+ expect(
155
+ rendered.getByText("dataViews.form.step.dataset.description")
156
+ ).toBeInTheDocument();
157
+ expect(
158
+ rendered.getByText("dataViews.form.step.aggregations.description")
159
+ ).toBeInTheDocument();
160
+ expect(
161
+ rendered.getByText("dataViews.form.step.select.description")
162
+ ).toBeInTheDocument();
163
+ });
164
+ });