@griddo/ax 11.13.3-rc.0 → 11.13.3

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 (59) hide show
  1. package/config/jest/setup.js +10 -0
  2. package/package.json +2 -2
  3. package/src/GlobalStore.tsx +1 -1
  4. package/src/__tests__/components/Fields/AsyncCheckGroup/AsyncCheckGroup.test.tsx +276 -66
  5. package/src/__tests__/components/FloatingMenu/FloatingMenu.test.tsx +300 -99
  6. package/src/__tests__/modules/Settings/Social/Social.test.tsx +12 -4
  7. package/src/api/checkgroups.tsx +4 -3
  8. package/src/api/selects.tsx +12 -5
  9. package/src/components/ActionMenu/index.tsx +1 -3
  10. package/src/components/Browser/index.tsx +12 -3
  11. package/src/components/Browser/style.tsx +7 -0
  12. package/src/components/ConfigPanel/Form/index.tsx +47 -53
  13. package/src/components/Fields/AnalyticsField/PageAnalytics/atoms.tsx +9 -13
  14. package/src/components/Fields/AnalyticsField/PageAnalytics/index.tsx +37 -29
  15. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/atoms.tsx +9 -13
  16. package/src/components/Fields/AnalyticsField/StructuredDataAnalytics/index.tsx +17 -11
  17. package/src/components/Fields/AnalyticsField/index.tsx +1 -2
  18. package/src/components/Fields/AnalyticsField/utils.tsx +4 -4
  19. package/src/components/Fields/AsyncCheckGroup/index.tsx +97 -79
  20. package/src/components/Fields/AsyncSelect/index.tsx +33 -22
  21. package/src/components/Fields/DateField/DatePickerInput/index.tsx +2 -2
  22. package/src/components/Fields/DateField/index.tsx +3 -3
  23. package/src/components/Fields/IntegrationsField/SideModal/index.tsx +2 -2
  24. package/src/components/Fields/IntegrationsField/index.tsx +14 -10
  25. package/src/components/Fields/MultiCheckSelect/index.tsx +6 -6
  26. package/src/components/Fields/MultiCheckSelectGroup/index.tsx +39 -37
  27. package/src/components/Fields/MultiCheckSelectGroup/style.tsx +1 -1
  28. package/src/components/Fields/ReferenceField/ManualPanel/index.tsx +0 -2
  29. package/src/components/Fields/RichText/index.tsx +15 -7
  30. package/src/components/Fields/TextArea/index.tsx +9 -6
  31. package/src/components/FloatingMenu/index.tsx +32 -31
  32. package/src/components/FloatingMenu/style.tsx +23 -5
  33. package/src/components/Loader/components/SmallCircle.js +3 -3
  34. package/src/components/MainWrapper/AppBar/style.tsx +1 -0
  35. package/src/components/SideModal/index.tsx +1 -1
  36. package/src/components/TableFilters/CategoryFilter/index.tsx +14 -15
  37. package/src/containers/App/actions.tsx +7 -1
  38. package/src/containers/App/constants.tsx +2 -0
  39. package/src/containers/App/interfaces.tsx +5 -0
  40. package/src/containers/App/reducer.tsx +11 -2
  41. package/src/containers/Forms/actions.tsx +5 -7
  42. package/src/containers/Integrations/actions.tsx +1 -3
  43. package/src/containers/Navigation/Menu/actions.tsx +2 -2
  44. package/src/containers/PageEditor/actions.tsx +3 -2
  45. package/src/containers/Settings/DataPacks/actions.tsx +35 -29
  46. package/src/containers/Sites/actions.tsx +40 -33
  47. package/src/containers/StructuredData/actions.tsx +3 -9
  48. package/src/modules/ActivityLog/LogFilters/DateFilter/index.tsx +5 -4
  49. package/src/modules/Content/NewContentModal/PageImporter/index.tsx +1 -2
  50. package/src/modules/Content/index.tsx +8 -3
  51. package/src/modules/Content/style.tsx +7 -0
  52. package/src/modules/Navigation/Defaults/DefaultsEditor/index.tsx +58 -45
  53. package/src/modules/Navigation/Defaults/index.tsx +103 -104
  54. package/src/modules/PageEditor/index.tsx +9 -1
  55. package/src/modules/PublicPreview/index.tsx +2 -1
  56. package/src/modules/Settings/ContentTypes/DataPacks/Config/Form/index.tsx +60 -44
  57. package/src/modules/Settings/ContentTypes/DataPacks/Config/index.tsx +32 -37
  58. package/src/modules/Sites/index.tsx +3 -3
  59. package/src/modules/Users/UserList/index.tsx +1 -1
@@ -1,5 +1,15 @@
1
1
  require("babel-plugin-require-context-hook/register")();
2
2
 
3
+ // styled-components warns when a component is first imported during a React render.
4
+ // In tests, modules are loaded lazily (on first use), so they get evaluated inside
5
+ // a render cycle — a false positive. The components themselves are correctly defined
6
+ // at module level; this suppressor avoids ~1800 spurious warnings per test run.
7
+ const originalWarn = console.warn.bind(console);
8
+ console.warn = (...args) => {
9
+ if (typeof args[0] === "string" && args[0].includes("has been created dynamically")) return;
10
+ originalWarn(...args);
11
+ };
12
+
3
13
  module.exports = () => {
4
14
  global.IS_REACT_ACT_ENVIRONMENT = true;
5
15
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@griddo/ax",
3
3
  "description": "Griddo Author Experience",
4
- "version": "11.13.3-rc.0",
4
+ "version": "11.13.3",
5
5
  "authors": [
6
6
  "Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
7
7
  "Diego M. Béjar <diego.bejar@secuoyas.com>",
@@ -217,5 +217,5 @@
217
217
  "publishConfig": {
218
218
  "access": "public"
219
219
  },
220
- "gitHead": "7ae5d46aa8bd0782fff780f0fbae9c2e7cb6ae57"
220
+ "gitHead": "6dd412b5408e057baa6c1c89f4815c85c7e16a45"
221
221
  }
@@ -34,7 +34,7 @@ export class GlobalStore {
34
34
  private authPersistConfig = {
35
35
  key: "app",
36
36
  storage,
37
- blacklist: ["error", "isLoggingIn", "isRehydrated", "isSaving", "user"],
37
+ blacklist: ["error", "isLoggingIn", "isRehydrated", "isSaving", "loadingCount", "user"],
38
38
  };
39
39
 
40
40
  public configureStore(history: History) {
@@ -1,18 +1,54 @@
1
- import React from "react";
2
- import axios from "axios";
3
- import AsyncCheckGroup, { IAsyncCheckGroup } from "@ax/components/Fields/AsyncCheckGroup";
4
- import { ThemeProvider } from "styled-components";
1
+ import type React from "react";
2
+
3
+ import type { IAsyncCheckGroup } from "@ax/components/Fields/AsyncCheckGroup";
4
+ import AsyncCheckGroup from "@ax/components/Fields/AsyncCheckGroup";
5
5
  import { parseTheme } from "@ax/helpers";
6
6
  import globalTheme from "@ax/themes/theme.json";
7
- import { render, screen, cleanup, act } from "@testing-library/react";
8
- import { ISite } from "@ax/types";
7
+ import type { ILanguage, ISite } from "@ax/types";
8
+
9
+ import { act, cleanup, fireEvent, render, screen } from "@testing-library/react";
10
+ import axios from "axios";
9
11
  import { mock } from "jest-mock-extended";
12
+ import { ThemeProvider } from "styled-components";
10
13
 
11
14
  afterEach(cleanup);
12
15
 
13
16
  jest.mock("axios");
14
17
  const mockedAxios = axios as jest.MockedFunction<typeof axios>;
15
18
 
19
+ const buildOkResponse = (data: any[]) => ({
20
+ data,
21
+ status: 200,
22
+ statusText: "Ok",
23
+ headers: {},
24
+ config: {},
25
+ });
26
+
27
+ const sampleOptions = [
28
+ { value: 3622, name: "Home", title: "Home" },
29
+ { value: 3731, name: "English child", title: "English child" },
30
+ { value: 3730, name: "English Parent", title: "English Parent" },
31
+ { value: 3787, name: "English test", title: "English test" },
32
+ { value: 3745, name: "New Page", title: "New Page" },
33
+ { value: 3784, name: "Prueba", title: "Prueba" },
34
+ ];
35
+
36
+ const renderWithTheme = (props: IAsyncCheckGroup) =>
37
+ render(
38
+ <ThemeProvider theme={parseTheme(globalTheme)}>
39
+ <AsyncCheckGroup {...props} />
40
+ </ThemeProvider>,
41
+ );
42
+
43
+ const rerenderWithTheme = (rerender: (ui: React.ReactElement) => void, props: IAsyncCheckGroup) =>
44
+ rerender(
45
+ <ThemeProvider theme={parseTheme(globalTheme)}>
46
+ <AsyncCheckGroup {...props} />
47
+ </ThemeProvider>,
48
+ );
49
+
50
+ const flushPromises = () => new Promise((resolve) => setTimeout(resolve, 0));
51
+
16
52
  describe("AsyncCheckGroup component rendering", () => {
17
53
  afterEach(() => {
18
54
  jest.resetAllMocks();
@@ -24,64 +60,16 @@ describe("AsyncCheckGroup component rendering", () => {
24
60
 
25
61
  const defaultDataProps: IAsyncCheckGroup = {
26
62
  site: defaultSite,
27
- value: [
28
- {
29
- value: 3622,
30
- name: "Home",
31
- title: "Home",
32
- },
33
- ],
63
+ value: [{ value: 3622, name: "Home", title: "Home" }],
34
64
  onChange: jest.fn(),
35
65
  source: "NEWS",
66
+ contentType: "data",
36
67
  };
37
68
 
38
- const data = {
39
- data: [
40
- {
41
- value: 3622,
42
- name: "Home",
43
- title: "Home",
44
- },
45
- {
46
- value: 3731,
47
- name: "English child",
48
- title: "English child",
49
- },
50
- {
51
- value: 3730,
52
- name: "English Parent",
53
- title: "English Parent",
54
- },
55
- {
56
- value: 3787,
57
- name: "English test",
58
- title: "English test",
59
- },
60
- {
61
- value: 3745,
62
- name: "New Page",
63
- title: "New Page",
64
- },
65
- {
66
- value: 3784,
67
- name: "Prueba",
68
- title: "Prueba",
69
- },
70
- ],
71
- status: 200,
72
- statusText: "Ok",
73
- headers: {},
74
- config: {},
75
- };
76
-
77
- mockedAxios.mockResolvedValue(data);
69
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
78
70
 
79
71
  await act(async () => {
80
- render(
81
- <ThemeProvider theme={parseTheme(globalTheme)}>
82
- <AsyncCheckGroup {...defaultDataProps} />
83
- </ThemeProvider>,
84
- );
72
+ renderWithTheme(defaultDataProps);
85
73
  });
86
74
 
87
75
  expect(screen.getAllByTestId("check-field-label")).toHaveLength(6);
@@ -92,25 +80,247 @@ describe("AsyncCheckGroup component rendering", () => {
92
80
  value: [],
93
81
  onChange: jest.fn(),
94
82
  source: "NEWS",
83
+ contentType: "data",
95
84
  };
96
85
 
97
- const data = {
86
+ mockedAxios.mockResolvedValue({
98
87
  data: [],
99
88
  status: 500,
100
89
  statusText: "Internal Server Error",
101
90
  headers: {},
102
91
  config: {},
92
+ });
93
+
94
+ await act(async () => {
95
+ renderWithTheme(defaultProps);
96
+ });
97
+
98
+ expect(screen.queryByTestId("check-field-label")).not.toBeTruthy();
99
+ });
100
+ });
101
+
102
+ describe("AsyncCheckGroup - infinite render loop prevention", () => {
103
+ afterEach(() => {
104
+ jest.resetAllMocks();
105
+ });
106
+
107
+ it("should call the API only once across rerenders with unstable onChange and value references", async () => {
108
+ const site = mock<ISite>();
109
+ site.id = 1;
110
+
111
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
112
+
113
+ const baseProps = {
114
+ site,
115
+ source: "NEWS",
116
+ contentType: "data" as const,
117
+ };
118
+
119
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
120
+ await act(async () => {
121
+ ({ rerender } = renderWithTheme({ ...baseProps, value: [], onChange: jest.fn() }));
122
+ });
123
+
124
+ for (let i = 0; i < 5; i++) {
125
+ await act(async () => {
126
+ rerenderWithTheme(rerender, { ...baseProps, value: [], onChange: jest.fn() });
127
+ await flushPromises();
128
+ });
129
+ }
130
+
131
+ expect(mockedAxios).toHaveBeenCalledTimes(1);
132
+ });
133
+ });
134
+
135
+ describe("AsyncCheckGroup - refetch behavior on API-relevant prop changes", () => {
136
+ afterEach(() => {
137
+ jest.resetAllMocks();
138
+ });
139
+
140
+ it("should refetch when source changes", async () => {
141
+ const site = mock<ISite>();
142
+ site.id = 1;
143
+
144
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
145
+
146
+ const baseProps = {
147
+ site,
148
+ value: [],
149
+ onChange: jest.fn(),
150
+ contentType: "data" as const,
103
151
  };
104
152
 
105
- mockedAxios.mockResolvedValue(data);
153
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
154
+ await act(async () => {
155
+ ({ rerender } = renderWithTheme({ ...baseProps, source: "NEWS" }));
156
+ });
157
+
158
+ const callsBefore = mockedAxios.mock.calls.length;
106
159
 
107
160
  await act(async () => {
108
- render(
109
- <ThemeProvider theme={parseTheme(globalTheme)}>
110
- <AsyncCheckGroup {...defaultProps} />
111
- </ThemeProvider>,
112
- );
161
+ rerenderWithTheme(rerender, { ...baseProps, source: "EVENTS" });
162
+ await flushPromises();
113
163
  });
114
- expect(screen.queryByTestId("check-field-label")).not.toBeTruthy();
164
+
165
+ expect(mockedAxios.mock.calls.length).toBeGreaterThan(callsBefore);
166
+ });
167
+
168
+ it("should refetch when site changes", async () => {
169
+ const siteA = mock<ISite>();
170
+ siteA.id = 1;
171
+ const siteB = mock<ISite>();
172
+ siteB.id = 2;
173
+
174
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
175
+
176
+ const baseProps = {
177
+ value: [],
178
+ onChange: jest.fn(),
179
+ source: "NEWS",
180
+ contentType: "data" as const,
181
+ };
182
+
183
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
184
+ await act(async () => {
185
+ ({ rerender } = renderWithTheme({ ...baseProps, site: siteA }));
186
+ });
187
+
188
+ const callsBefore = mockedAxios.mock.calls.length;
189
+
190
+ await act(async () => {
191
+ rerenderWithTheme(rerender, { ...baseProps, site: siteB });
192
+ await flushPromises();
193
+ });
194
+
195
+ expect(mockedAxios.mock.calls.length).toBeGreaterThan(callsBefore);
196
+ });
197
+
198
+ it("should refetch when contentLanguages changes", async () => {
199
+ const site = mock<ISite>();
200
+ site.id = 1;
201
+
202
+ const languages: ILanguage[] = [{ id: 1, locale: "en_GB" } as ILanguage, { id: 2, locale: "es_ES" } as ILanguage];
203
+
204
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
205
+
206
+ const baseProps = {
207
+ site,
208
+ value: [],
209
+ onChange: jest.fn(),
210
+ source: "NEWS",
211
+ contentType: "data" as const,
212
+ languages,
213
+ };
214
+
215
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
216
+ await act(async () => {
217
+ ({ rerender } = renderWithTheme({ ...baseProps, contentLanguages: ["en_GB"] }));
218
+ });
219
+
220
+ const callsBefore = mockedAxios.mock.calls.length;
221
+
222
+ await act(async () => {
223
+ rerenderWithTheme(rerender, { ...baseProps, contentLanguages: ["es_ES"] });
224
+ await flushPromises();
225
+ });
226
+
227
+ expect(mockedAxios.mock.calls.length).toBeGreaterThan(callsBefore);
228
+ });
229
+
230
+ it("should refetch when allLanguages changes", async () => {
231
+ const site = mock<ISite>();
232
+ site.id = 1;
233
+
234
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
235
+
236
+ const baseProps = {
237
+ site,
238
+ value: [],
239
+ onChange: jest.fn(),
240
+ source: "NEWS",
241
+ contentType: "data" as const,
242
+ };
243
+
244
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
245
+ await act(async () => {
246
+ ({ rerender } = renderWithTheme({ ...baseProps, allLanguages: false }));
247
+ });
248
+
249
+ const callsBefore = mockedAxios.mock.calls.length;
250
+
251
+ await act(async () => {
252
+ rerenderWithTheme(rerender, { ...baseProps, allLanguages: true });
253
+ await flushPromises();
254
+ });
255
+
256
+ expect(mockedAxios.mock.calls.length).toBeGreaterThan(callsBefore);
257
+ });
258
+ });
259
+
260
+ describe("AsyncCheckGroup - user interaction", () => {
261
+ afterEach(() => {
262
+ jest.resetAllMocks();
263
+ });
264
+
265
+ it("should call onChange when a checkbox is toggled", async () => {
266
+ const site = mock<ISite>();
267
+ site.id = 1;
268
+
269
+ const onChange = jest.fn();
270
+ const props: IAsyncCheckGroup = {
271
+ site,
272
+ value: [],
273
+ onChange,
274
+ source: "NEWS",
275
+ contentType: "data",
276
+ };
277
+
278
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
279
+
280
+ await act(async () => {
281
+ renderWithTheme(props);
282
+ });
283
+
284
+ const checkboxes = screen.getAllByTestId("check-field-label");
285
+ await act(async () => {
286
+ fireEvent.click(checkboxes[0]);
287
+ });
288
+
289
+ expect(onChange).toHaveBeenCalled();
290
+ });
291
+
292
+ it("should always invoke the latest onChange even if the parent swaps it between renders", async () => {
293
+ const site = mock<ISite>();
294
+ site.id = 1;
295
+
296
+ const firstOnChange = jest.fn();
297
+ const secondOnChange = jest.fn();
298
+
299
+ const baseProps = {
300
+ site,
301
+ value: [],
302
+ source: "NEWS",
303
+ contentType: "data" as const,
304
+ };
305
+
306
+ mockedAxios.mockResolvedValue(buildOkResponse(sampleOptions));
307
+
308
+ let rerender: (ui: React.ReactElement) => void = () => undefined;
309
+ await act(async () => {
310
+ ({ rerender } = renderWithTheme({ ...baseProps, onChange: firstOnChange }));
311
+ });
312
+
313
+ await act(async () => {
314
+ rerenderWithTheme(rerender, { ...baseProps, onChange: secondOnChange });
315
+ await flushPromises();
316
+ });
317
+
318
+ const checkboxes = screen.getAllByTestId("check-field-label");
319
+ await act(async () => {
320
+ fireEvent.click(checkboxes[0]);
321
+ });
322
+
323
+ expect(firstOnChange).not.toHaveBeenCalled();
324
+ expect(secondOnChange).toHaveBeenCalled();
115
325
  });
116
326
  });