@griddo/ax 12.10.2 → 12.10.4
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 +2 -2
- package/src/__tests__/hooks/shortcuts/engine.test.ts +9 -9
- package/src/__tests__/hooks/shortcuts/hooks.test.tsx +19 -19
- package/src/__tests__/modules/Content/NewContentModal/PageImporter.test.tsx +110 -0
- package/src/hooks/index.tsx +30 -0
- package/src/modules/Content/NewContentModal/PageImporter/index.tsx +8 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "12.10.
|
|
4
|
+
"version": "12.10.4",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Diego M. Béjar <diego.bejar@secuoyas.com>",
|
|
@@ -201,5 +201,5 @@
|
|
|
201
201
|
"publishConfig": {
|
|
202
202
|
"access": "public"
|
|
203
203
|
},
|
|
204
|
-
"gitHead": "
|
|
204
|
+
"gitHead": "c0d3c4387df84871e0e2697846e5a18b67e7bf52"
|
|
205
205
|
}
|
|
@@ -18,7 +18,7 @@ function register(
|
|
|
18
18
|
overrides: Partial<ShortcutConfig> & Pick<ShortcutConfig, "id" | "keys">,
|
|
19
19
|
target?: EventTarget,
|
|
20
20
|
): ShortcutConfig {
|
|
21
|
-
const config: ShortcutConfig = { handler:
|
|
21
|
+
const config: ShortcutConfig = { handler: vi.fn(), ...overrides };
|
|
22
22
|
const cleanup = shortcutEngine.register(config, target);
|
|
23
23
|
cleanups.push(cleanup);
|
|
24
24
|
return config;
|
|
@@ -266,13 +266,13 @@ describe("ShortcutEngine", () => {
|
|
|
266
266
|
});
|
|
267
267
|
|
|
268
268
|
it("resets on timeout", () => {
|
|
269
|
-
|
|
269
|
+
vi.useFakeTimers();
|
|
270
270
|
const config = register({ id: "goto", keys: parseShortcut("g l") });
|
|
271
271
|
keydown({ key: "g" });
|
|
272
|
-
|
|
272
|
+
vi.advanceTimersByTime(1100);
|
|
273
273
|
keydown({ key: "l" });
|
|
274
274
|
expect(config.handler).not.toHaveBeenCalled();
|
|
275
|
-
|
|
275
|
+
vi.useRealTimers();
|
|
276
276
|
});
|
|
277
277
|
|
|
278
278
|
it("calls preventDefault on first key of chord", () => {
|
|
@@ -282,8 +282,8 @@ describe("ShortcutEngine", () => {
|
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it("does not fire single-key shortcuts while chord is advancing", () => {
|
|
285
|
-
const chordHandler =
|
|
286
|
-
const singleHandler =
|
|
285
|
+
const chordHandler = vi.fn();
|
|
286
|
+
const singleHandler = vi.fn();
|
|
287
287
|
register({ id: "chord", keys: parseShortcut("g l") });
|
|
288
288
|
register({ id: "single-g", keys: parseShortcut("g"), handler: singleHandler });
|
|
289
289
|
|
|
@@ -320,7 +320,7 @@ describe("ShortcutEngine", () => {
|
|
|
320
320
|
|
|
321
321
|
describe("cleanup", () => {
|
|
322
322
|
it("unregisters shortcut when cleanup is called", () => {
|
|
323
|
-
const handler =
|
|
323
|
+
const handler = vi.fn();
|
|
324
324
|
const cleanup = shortcutEngine.register({ id: "tmp", keys: parseShortcut("mod+s"), handler }, window);
|
|
325
325
|
cleanup();
|
|
326
326
|
|
|
@@ -331,9 +331,9 @@ describe("ShortcutEngine", () => {
|
|
|
331
331
|
it("removes listener when last shortcut on a target is unregistered", () => {
|
|
332
332
|
const div = document.createElement("div");
|
|
333
333
|
document.body.appendChild(div);
|
|
334
|
-
const spy =
|
|
334
|
+
const spy = vi.spyOn(div, "removeEventListener");
|
|
335
335
|
|
|
336
|
-
const cleanup = shortcutEngine.register({ id: "tmp", keys: parseShortcut("mod+x"), handler:
|
|
336
|
+
const cleanup = shortcutEngine.register({ id: "tmp", keys: parseShortcut("mod+x"), handler: vi.fn() }, div);
|
|
337
337
|
cleanup();
|
|
338
338
|
|
|
339
339
|
expect(spy).toHaveBeenCalledWith("keydown", expect.any(Function));
|
|
@@ -26,7 +26,7 @@ function keydown(init: KeyboardEventInit): KeyboardEvent {
|
|
|
26
26
|
|
|
27
27
|
describe("shortcut()", () => {
|
|
28
28
|
it("creates a ShortcutConfig with parsed keys", () => {
|
|
29
|
-
const handler =
|
|
29
|
+
const handler = vi.fn();
|
|
30
30
|
const config = shortcut("save", "mod+s", handler);
|
|
31
31
|
expect(config.id).toBe("save");
|
|
32
32
|
expect(config.keys).toEqual([{ modifiers: ["mod"], key: "s" }]);
|
|
@@ -34,7 +34,7 @@ describe("shortcut()", () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("applies overrides", () => {
|
|
37
|
-
const config = shortcut("save", "mod+s",
|
|
37
|
+
const config = shortcut("save", "mod+s", vi.fn(), {
|
|
38
38
|
allowInInput: true,
|
|
39
39
|
priority: 5,
|
|
40
40
|
description: "Save document",
|
|
@@ -51,14 +51,14 @@ describe("shortcut()", () => {
|
|
|
51
51
|
|
|
52
52
|
describe("useShortcuts", () => {
|
|
53
53
|
it("registers and fires shortcut on matching key", () => {
|
|
54
|
-
const handler =
|
|
54
|
+
const handler = vi.fn();
|
|
55
55
|
renderHook(() => useShortcuts([shortcut("s1", "mod+s", handler)]));
|
|
56
56
|
keydown({ key: "s", ctrlKey: true });
|
|
57
57
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it("cleans up shortcuts on unmount", () => {
|
|
61
|
-
const handler =
|
|
61
|
+
const handler = vi.fn();
|
|
62
62
|
const { unmount } = renderHook(() => useShortcuts([shortcut("s1", "mod+s", handler)]));
|
|
63
63
|
unmount();
|
|
64
64
|
keydown({ key: "s", ctrlKey: true });
|
|
@@ -66,7 +66,7 @@ describe("useShortcuts", () => {
|
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it("does not fire when enabled is false", () => {
|
|
69
|
-
const handler =
|
|
69
|
+
const handler = vi.fn();
|
|
70
70
|
renderHook(() => useShortcuts([shortcut("s1", "mod+s", handler)], { enabled: false }));
|
|
71
71
|
keydown({ key: "s", ctrlKey: true });
|
|
72
72
|
expect(handler).not.toHaveBeenCalled();
|
|
@@ -94,7 +94,7 @@ describe("useShortcuts", () => {
|
|
|
94
94
|
const div = document.createElement("div");
|
|
95
95
|
document.body.appendChild(div);
|
|
96
96
|
const ref = { current: div } as React.RefObject<HTMLDivElement>;
|
|
97
|
-
const handler =
|
|
97
|
+
const handler = vi.fn();
|
|
98
98
|
|
|
99
99
|
renderHook(() => useShortcuts([shortcut("s1", "mod+b", handler)], { ref }));
|
|
100
100
|
|
|
@@ -116,21 +116,21 @@ describe("useShortcuts", () => {
|
|
|
116
116
|
|
|
117
117
|
describe("useSaveShortcut", () => {
|
|
118
118
|
it("fires handler on mod+s", () => {
|
|
119
|
-
const handler =
|
|
119
|
+
const handler = vi.fn();
|
|
120
120
|
renderHook(() => useSaveShortcut(handler));
|
|
121
121
|
keydown({ key: "s", ctrlKey: true });
|
|
122
122
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
it("does not fire when disabled", () => {
|
|
126
|
-
const handler =
|
|
126
|
+
const handler = vi.fn();
|
|
127
127
|
renderHook(() => useSaveShortcut(handler, false));
|
|
128
128
|
keydown({ key: "s", ctrlKey: true });
|
|
129
129
|
expect(handler).not.toHaveBeenCalled();
|
|
130
130
|
});
|
|
131
131
|
|
|
132
132
|
it("works inside input elements", () => {
|
|
133
|
-
const handler =
|
|
133
|
+
const handler = vi.fn();
|
|
134
134
|
renderHook(() => useSaveShortcut(handler));
|
|
135
135
|
const input = document.createElement("input");
|
|
136
136
|
document.body.appendChild(input);
|
|
@@ -142,14 +142,14 @@ describe("useSaveShortcut", () => {
|
|
|
142
142
|
|
|
143
143
|
describe("useEscapeShortcut", () => {
|
|
144
144
|
it("fires handler on Escape", () => {
|
|
145
|
-
const handler =
|
|
145
|
+
const handler = vi.fn();
|
|
146
146
|
renderHook(() => useEscapeShortcut(handler));
|
|
147
147
|
keydown({ key: "Escape" });
|
|
148
148
|
expect(handler).toHaveBeenCalledTimes(1);
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
it("does not fire when disabled", () => {
|
|
152
|
-
const handler =
|
|
152
|
+
const handler = vi.fn();
|
|
153
153
|
renderHook(() => useEscapeShortcut(handler, false));
|
|
154
154
|
keydown({ key: "Escape" });
|
|
155
155
|
expect(handler).not.toHaveBeenCalled();
|
|
@@ -158,8 +158,8 @@ describe("useEscapeShortcut", () => {
|
|
|
158
158
|
|
|
159
159
|
describe("useUndoRedoShortcuts", () => {
|
|
160
160
|
it("fires onUndo on mod+z", () => {
|
|
161
|
-
const onUndo =
|
|
162
|
-
const onRedo =
|
|
161
|
+
const onUndo = vi.fn();
|
|
162
|
+
const onRedo = vi.fn();
|
|
163
163
|
renderHook(() => useUndoRedoShortcuts(onUndo, onRedo));
|
|
164
164
|
keydown({ key: "z", ctrlKey: true });
|
|
165
165
|
expect(onUndo).toHaveBeenCalledTimes(1);
|
|
@@ -167,8 +167,8 @@ describe("useUndoRedoShortcuts", () => {
|
|
|
167
167
|
});
|
|
168
168
|
|
|
169
169
|
it("fires onRedo on mod+shift+z", () => {
|
|
170
|
-
const onUndo =
|
|
171
|
-
const onRedo =
|
|
170
|
+
const onUndo = vi.fn();
|
|
171
|
+
const onRedo = vi.fn();
|
|
172
172
|
renderHook(() => useUndoRedoShortcuts(onUndo, onRedo));
|
|
173
173
|
keydown({ key: "z", ctrlKey: true, shiftKey: true });
|
|
174
174
|
expect(onRedo).toHaveBeenCalledTimes(1);
|
|
@@ -184,7 +184,7 @@ describe("useGlobalShortcuts", () => {
|
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
it("yields to higher-priority shortcut", () => {
|
|
187
|
-
const handler =
|
|
187
|
+
const handler = vi.fn();
|
|
188
188
|
renderHook(() => useGlobalShortcuts());
|
|
189
189
|
renderHook(() => useSaveShortcut(handler));
|
|
190
190
|
|
|
@@ -201,7 +201,7 @@ describe("useGlobalShortcuts", () => {
|
|
|
201
201
|
describe("useIframeShortcutBridge", () => {
|
|
202
202
|
it("re-dispatches same-origin shortcut messages as keydown", () => {
|
|
203
203
|
renderHook(() => useIframeShortcutBridge());
|
|
204
|
-
const spy =
|
|
204
|
+
const spy = vi.fn();
|
|
205
205
|
window.addEventListener("keydown", spy);
|
|
206
206
|
|
|
207
207
|
window.dispatchEvent(
|
|
@@ -224,7 +224,7 @@ describe("useIframeShortcutBridge", () => {
|
|
|
224
224
|
|
|
225
225
|
it("ignores cross-origin messages", () => {
|
|
226
226
|
renderHook(() => useIframeShortcutBridge());
|
|
227
|
-
const spy =
|
|
227
|
+
const spy = vi.fn();
|
|
228
228
|
window.addEventListener("keydown", spy);
|
|
229
229
|
|
|
230
230
|
window.dispatchEvent(
|
|
@@ -243,7 +243,7 @@ describe("useIframeShortcutBridge", () => {
|
|
|
243
243
|
|
|
244
244
|
it("ignores messages with unrecognized type", () => {
|
|
245
245
|
renderHook(() => useIframeShortcutBridge());
|
|
246
|
-
const spy =
|
|
246
|
+
const spy = vi.fn();
|
|
247
247
|
window.addEventListener("keydown", spy);
|
|
248
248
|
|
|
249
249
|
window.dispatchEvent(
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { parseTheme } from "@ax/helpers";
|
|
2
|
+
import PageImporter from "@ax/modules/Content/NewContentModal/PageImporter";
|
|
3
|
+
import globalTheme from "@ax/themes/theme.json";
|
|
4
|
+
|
|
5
|
+
import configureStore from "redux-mock-store";
|
|
6
|
+
import thunk from "redux-thunk";
|
|
7
|
+
import { ThemeProvider } from "styled-components";
|
|
8
|
+
|
|
9
|
+
import { act, cleanup, fireEvent, render, screen } from "./../../../../../config/tests/test-utils";
|
|
10
|
+
|
|
11
|
+
const getGlobalPagesLight = vi.fn();
|
|
12
|
+
|
|
13
|
+
vi.mock("@ax/api", async (importOriginal) => {
|
|
14
|
+
const actual = (await importOriginal()) as any;
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...actual,
|
|
18
|
+
sites: {
|
|
19
|
+
...actual.sites,
|
|
20
|
+
getGlobalPagesLight: (...args: any[]) => getGlobalPagesLight(...args),
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(cleanup);
|
|
26
|
+
|
|
27
|
+
const buildPage = (id: number) => ({
|
|
28
|
+
id,
|
|
29
|
+
title: `insti news ${id}`,
|
|
30
|
+
slug: `/insti-news-${id}`,
|
|
31
|
+
modified: "2026-09-22T10:00:00.000Z",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const setPagesToImport = vi.fn();
|
|
35
|
+
|
|
36
|
+
const renderImporter = async () => {
|
|
37
|
+
const store = configureStore([thunk])({
|
|
38
|
+
app: { lang: { id: 4, locale: "en_GB" } },
|
|
39
|
+
sites: { currentSiteInfo: { id: 1 } },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await act(async () => {
|
|
43
|
+
render(
|
|
44
|
+
<ThemeProvider theme={parseTheme(globalTheme)}>
|
|
45
|
+
<PageImporter
|
|
46
|
+
structuredDataID="INS_NEWS"
|
|
47
|
+
title="Get Institutional News from Global"
|
|
48
|
+
setPagesToImport={setPagesToImport}
|
|
49
|
+
toggleImporter={vi.fn()}
|
|
50
|
+
importPage={vi.fn()}
|
|
51
|
+
/>
|
|
52
|
+
</ThemeProvider>,
|
|
53
|
+
{ store },
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const search = async (query: string) => {
|
|
59
|
+
const input = screen.getByTestId("search-input");
|
|
60
|
+
await act(async () => {
|
|
61
|
+
fireEvent.change(input, { target: { value: query } });
|
|
62
|
+
});
|
|
63
|
+
await act(async () => {
|
|
64
|
+
fireEvent.keyDown(input, { key: "Enter" });
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const selectPage = async (pageId: number) => {
|
|
69
|
+
const checkbox = screen
|
|
70
|
+
.getAllByTestId("check-field-input")
|
|
71
|
+
.find((input) => input.getAttribute("value") === String(pageId)) as HTMLElement;
|
|
72
|
+
|
|
73
|
+
await act(async () => {
|
|
74
|
+
fireEvent.click(checkbox);
|
|
75
|
+
});
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const addPagesButton = () => screen.getByTestId("button-default");
|
|
79
|
+
|
|
80
|
+
describe("[UC-AX-explore-manage-pages] PageImporter — importar páginas globales", () => {
|
|
81
|
+
beforeEach(() => {
|
|
82
|
+
// El listado inicial trae una primera tanda; la búsqueda devuelve una página
|
|
83
|
+
// que no venía en ella, que es justo lo que hace el backend al filtrar por query.
|
|
84
|
+
getGlobalPagesLight.mockImplementation(async ({ query }: { query: string }) => ({
|
|
85
|
+
status: 200,
|
|
86
|
+
data: { items: query ? [buildPage(3)] : [buildPage(1), buildPage(2)] },
|
|
87
|
+
}));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("habilita 'Add pages' con una página de la primera tanda", async () => {
|
|
91
|
+
await renderImporter();
|
|
92
|
+
|
|
93
|
+
await selectPage(1);
|
|
94
|
+
|
|
95
|
+
expect(addPagesButton()).not.toBeDisabled();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("habilita 'Add pages' con una página que solo aparece al buscar", async () => {
|
|
99
|
+
await renderImporter();
|
|
100
|
+
|
|
101
|
+
await search("insti news 3");
|
|
102
|
+
await selectPage(3);
|
|
103
|
+
|
|
104
|
+
expect(addPagesButton()).not.toBeDisabled();
|
|
105
|
+
// Y la columna de la derecha la lista, en vez de seguir pidiendo una selección.
|
|
106
|
+
expect(screen.queryByText(/You have to select at least one page/)).not.toBeInTheDocument();
|
|
107
|
+
// El conjunto que se importa es el seleccionado, no el de la primera tanda.
|
|
108
|
+
expect(setPagesToImport).toHaveBeenLastCalledWith([3]);
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/hooks/index.tsx
CHANGED
|
@@ -16,6 +16,22 @@ import { useURLSearchParam } from "./location";
|
|
|
16
16
|
import { useContextMenu, useHandleClickOutside, useModal, useModals, useToast } from "./modals";
|
|
17
17
|
import { useNetworkStatus } from "./network";
|
|
18
18
|
import { useResizable } from "./resize";
|
|
19
|
+
import {
|
|
20
|
+
type KeyCombo,
|
|
21
|
+
type ModifierKey,
|
|
22
|
+
parseShortcut,
|
|
23
|
+
type ShortcutConfig,
|
|
24
|
+
shortcut,
|
|
25
|
+
shortcutEngine,
|
|
26
|
+
type UseShortcutsOptions,
|
|
27
|
+
useEscapeShortcut,
|
|
28
|
+
useForwardShortcutKeys,
|
|
29
|
+
useGlobalShortcuts,
|
|
30
|
+
useIframeShortcutBridge,
|
|
31
|
+
useSaveShortcut,
|
|
32
|
+
useShortcuts,
|
|
33
|
+
useUndoRedoShortcuts,
|
|
34
|
+
} from "./shortcuts";
|
|
19
35
|
import {
|
|
20
36
|
useGlobalPermission,
|
|
21
37
|
usePermission,
|
|
@@ -26,6 +42,9 @@ import {
|
|
|
26
42
|
import { useFirefoxScrollLock, useWindowSize } from "./window";
|
|
27
43
|
|
|
28
44
|
export {
|
|
45
|
+
parseShortcut,
|
|
46
|
+
shortcut,
|
|
47
|
+
shortcutEngine,
|
|
29
48
|
useAdaptiveText,
|
|
30
49
|
useBroadcastChannel,
|
|
31
50
|
useBroadcastClipboard,
|
|
@@ -40,9 +59,13 @@ export {
|
|
|
40
59
|
useEditingLock,
|
|
41
60
|
useEmptyState,
|
|
42
61
|
useEqualStructured,
|
|
62
|
+
useEscapeShortcut,
|
|
43
63
|
useFirefoxScrollLock,
|
|
64
|
+
useForwardShortcutKeys,
|
|
44
65
|
useGlobalPermission,
|
|
66
|
+
useGlobalShortcuts,
|
|
45
67
|
useHandleClickOutside,
|
|
68
|
+
useIframeShortcutBridge,
|
|
46
69
|
useIsDirty,
|
|
47
70
|
useModal,
|
|
48
71
|
useModals,
|
|
@@ -55,9 +78,16 @@ export {
|
|
|
55
78
|
usePermissionsForSite,
|
|
56
79
|
usePrevious,
|
|
57
80
|
useResizable,
|
|
81
|
+
useSaveShortcut,
|
|
82
|
+
useShortcuts,
|
|
58
83
|
useShouldBeSaved,
|
|
59
84
|
useToast,
|
|
85
|
+
useUndoRedoShortcuts,
|
|
60
86
|
useURLSearchParam,
|
|
61
87
|
useWindowSize,
|
|
62
88
|
type IBulkSelectedItems,
|
|
89
|
+
type KeyCombo,
|
|
90
|
+
type ModifierKey,
|
|
91
|
+
type ShortcutConfig,
|
|
92
|
+
type UseShortcutsOptions,
|
|
63
93
|
};
|
|
@@ -48,7 +48,14 @@ const PageImporter = (props: IPageImporterProps) => {
|
|
|
48
48
|
data: { items },
|
|
49
49
|
} = response;
|
|
50
50
|
setPages(items);
|
|
51
|
-
|
|
51
|
+
// `allPages` acumula todo lo que se ha listado, no solo la primera tanda: la
|
|
52
|
+
// columna de seleccionadas y el botón "Add pages" tienen que poder resolver
|
|
53
|
+
// una página que apareció por primera vez en una búsqueda posterior.
|
|
54
|
+
setAllPages((prevPages) => {
|
|
55
|
+
const knownPages = prevPages ?? [];
|
|
56
|
+
const newPages = items.filter((item: IPage) => !knownPages.some((page) => page.id === item.id));
|
|
57
|
+
return newPages.length ? [...knownPages, ...newPages] : knownPages;
|
|
58
|
+
});
|
|
52
59
|
}
|
|
53
60
|
setIsLoading(false);
|
|
54
61
|
} catch (error) {
|