@elementor/editor-v1-adapters 0.1.0

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.
@@ -0,0 +1,93 @@
1
+ import { act, renderHook } from '@testing-library/react-hooks';
2
+ import useIsRouteActive from '../use-is-route-active';
3
+ import { dispatchRouteClose, dispatchRouteOpen } from '../../__tests__/utils';
4
+ import { mockIsRouteActive } from './test-utils';
5
+
6
+ describe( '@elementor/editor-v1-adapters - useIsRouteActive', () => {
7
+ it( 'should return false when a route is inactive by default', () => {
8
+ // Arrange.
9
+ const route = 'panel/menu';
10
+
11
+ mockIsRouteActive( () => false );
12
+
13
+ // Act.
14
+ const { result } = renderHook( () => useIsRouteActive( route ) );
15
+
16
+ // Assert.
17
+ expect( result.current ).toBe( false );
18
+ } );
19
+
20
+ it( 'should return true when a route is active by default', () => {
21
+ // Arrange.
22
+ const route = 'panel/menu';
23
+
24
+ mockIsRouteActive( () => true );
25
+
26
+ // Act.
27
+ const { result } = renderHook( () => useIsRouteActive( route ) );
28
+
29
+ // Assert.
30
+ expect( result.current ).toBe( true );
31
+ } );
32
+
33
+ it( 'should return true when a route gets activated', () => {
34
+ // Arrange.
35
+ const route = 'panel/menu';
36
+
37
+ mockIsRouteActive( () => false );
38
+
39
+ // Act.
40
+ const { result } = renderHook( () => useIsRouteActive( route ) );
41
+
42
+ act( () => {
43
+ mockIsRouteActive( () => true );
44
+ dispatchRouteOpen( route );
45
+ } );
46
+
47
+ // Assert.
48
+ expect( result.current ).toBe( true );
49
+ } );
50
+
51
+ it( 'should return false when a route gets deactivated', () => {
52
+ // Arrange.
53
+ const route = 'panel/menu';
54
+
55
+ mockIsRouteActive( () => true );
56
+
57
+ // Act.
58
+ const { result } = renderHook( () => useIsRouteActive( route ) );
59
+
60
+ act( () => {
61
+ mockIsRouteActive( () => false );
62
+ dispatchRouteClose( route );
63
+ } );
64
+
65
+ // Assert.
66
+ expect( result.current ).toBe( false );
67
+ } );
68
+
69
+ it( 'should re-check whether the route is active when changing it', () => {
70
+ // Arrange.
71
+ mockIsRouteActive( ( r ) => {
72
+ return 'active/route' === r;
73
+ } );
74
+
75
+ // Act.
76
+ const { result, rerender } = renderHook( ( { route } ) => useIsRouteActive( route ), {
77
+ initialProps: {
78
+ route: 'active/route',
79
+ },
80
+ } );
81
+
82
+ // Assert.
83
+ expect( result.current ).toBe( true );
84
+
85
+ // Act.
86
+ rerender( {
87
+ route: 'inactive/route',
88
+ } );
89
+
90
+ // Assert.
91
+ expect( result.current ).toBe( false );
92
+ } );
93
+ } );
@@ -0,0 +1,124 @@
1
+ import { renderHook } from '@testing-library/react-hooks';
2
+ import useRouteStatus from '../use-route-status';
3
+ import { mockGetCurrentEditMode, mockIsRouteActive } from './test-utils';
4
+
5
+ describe( '@elementor/editor-v1-adapters - useRouteStatus', () => {
6
+ it.each( [
7
+ {
8
+ input: {
9
+ isRouteActive: true,
10
+ isKitRouteActive: false,
11
+ isPreviewMode: false,
12
+ options: {},
13
+ },
14
+ expected: true,
15
+ },
16
+ {
17
+ input: {
18
+ isRouteActive: true,
19
+ isKitRouteActive: false,
20
+ isPreviewMode: true,
21
+ options: {
22
+ blockOnPreviewMode: false,
23
+ },
24
+ },
25
+ expected: true,
26
+ },
27
+ {
28
+ input: {
29
+ isRouteActive: false,
30
+ isKitRouteActive: false,
31
+ isPreviewMode: false,
32
+ options: {},
33
+ },
34
+ expected: false,
35
+ },
36
+ {
37
+ input: {
38
+ isRouteActive: true,
39
+ isKitRouteActive: false,
40
+ isPreviewMode: true,
41
+ options: {},
42
+ },
43
+ expected: false,
44
+ },
45
+ ] )( 'should check if the route is active: isRouteActive = $input.isRouteActive, isPreviewMode = $input.isPreviewMode, options: $input.options', ( {
46
+ input: { isRouteActive, isKitRouteActive, isPreviewMode, options },
47
+ expected,
48
+ } ) => {
49
+ // Arrange
50
+ mockIsRouteActive( ( route ) => route === 'panel/global' ? isKitRouteActive : isRouteActive );
51
+ mockGetCurrentEditMode( () => isPreviewMode ? 'preview' : 'edit' );
52
+
53
+ // Act.
54
+ const { result } = renderHook( () => useRouteStatus( 'panel/page-settings', options ) );
55
+
56
+ // Assert.
57
+ expect( result.current.isActive ).toBe( expected );
58
+ } );
59
+
60
+ it.each( [
61
+ {
62
+ input: {
63
+ isRouteActive: false,
64
+ isKitRouteActive: false,
65
+ isPreviewMode: false,
66
+ options: {},
67
+ },
68
+ expected: false,
69
+ },
70
+ {
71
+ input: {
72
+ isRouteActive: false,
73
+ isKitRouteActive: false,
74
+ isPreviewMode: true,
75
+ options: {
76
+ blockOnPreviewMode: false,
77
+ },
78
+ },
79
+ expected: false,
80
+ },
81
+ {
82
+ input: {
83
+ isRouteActive: false,
84
+ isKitRouteActive: true,
85
+ isPreviewMode: false,
86
+ options: {
87
+ blockOnKitRoutes: false,
88
+ },
89
+ },
90
+ expected: false,
91
+ },
92
+ {
93
+ input: {
94
+ isRouteActive: false,
95
+ isKitRouteActive: true,
96
+ isPreviewMode: false,
97
+ options: {},
98
+ },
99
+ expected: true,
100
+ },
101
+ {
102
+ input: {
103
+ isRouteActive: false,
104
+ isKitRouteActive: false,
105
+ isPreviewMode: true,
106
+ options: {},
107
+ },
108
+ expected: true,
109
+ },
110
+ ] )( 'should check if the route is blocked: isKitRouteActive = $input.isKitRouteActive, isPreviewMode = $input.isPreviewMode, options: $input.options', ( {
111
+ input: { isRouteActive, isKitRouteActive, isPreviewMode, options },
112
+ expected,
113
+ } ) => {
114
+ // Arrange
115
+ mockIsRouteActive( ( route ) => route === 'panel/global' ? isKitRouteActive : isRouteActive );
116
+ mockGetCurrentEditMode( () => isPreviewMode ? 'preview' : 'edit' );
117
+
118
+ // Act.
119
+ const { result } = renderHook( () => useRouteStatus( 'panel/page-settings', options ) );
120
+
121
+ // Assert.
122
+ expect( result.current.isBlocked ).toBe( expected );
123
+ } );
124
+ } );
@@ -0,0 +1,4 @@
1
+ export { default as useIsPreviewMode } from './use-is-preview-mode';
2
+ export { default as useIsRouteActive } from './use-is-route-active';
3
+ export { default as useListenTo } from './use-listen-to';
4
+ export { default as useRouteStatus } from './use-route-status';
@@ -0,0 +1,10 @@
1
+ import useListenTo from './use-listen-to';
2
+ import { getCurrentEditMode } from '../readers';
3
+ import { editModeChangeEvent } from '../listeners';
4
+
5
+ export default function useIsPreviewMode() {
6
+ return useListenTo(
7
+ editModeChangeEvent(),
8
+ () => getCurrentEditMode() === 'preview'
9
+ );
10
+ }
@@ -0,0 +1,14 @@
1
+ import useListenTo from './use-listen-to';
2
+ import { isRouteActive } from '../readers';
3
+ import { routeCloseEvent, routeOpenEvent, RouteEventDescriptor } from '../listeners';
4
+
5
+ export default function useIsRouteActive( route: RouteEventDescriptor['name'] ) {
6
+ return useListenTo(
7
+ [
8
+ routeOpenEvent( route ),
9
+ routeCloseEvent( route ),
10
+ ],
11
+ () => isRouteActive( route ),
12
+ [ route ]
13
+ );
14
+ }
@@ -0,0 +1,21 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { EventDescriptor, listenTo } from '../listeners';
3
+
4
+ export default function useListenTo<T>(
5
+ event: EventDescriptor | EventDescriptor[],
6
+ getSnapshot: () => T,
7
+ deps: unknown[] = []
8
+ ) {
9
+ const [ snapshot, setSnapshot ] = useState( () => getSnapshot() );
10
+
11
+ useEffect( () => {
12
+ const updateState = () => setSnapshot( getSnapshot() );
13
+
14
+ // Ensure the state is re-calculated when the dependencies have been changed.
15
+ updateState();
16
+
17
+ return listenTo( event, updateState );
18
+ }, deps ); // eslint-disable-line react-hooks/exhaustive-deps
19
+
20
+ return snapshot;
21
+ }
@@ -0,0 +1,32 @@
1
+ import useIsPreviewMode from './use-is-preview-mode';
2
+ import useIsRouteActive from './use-is-route-active';
3
+ import { RouteEventDescriptor } from '../listeners';
4
+
5
+ type Options = {
6
+ blockOnKitRoutes?: boolean,
7
+ blockOnPreviewMode?: boolean,
8
+ }
9
+
10
+ export default function useRouteStatus(
11
+ route: RouteEventDescriptor['name'],
12
+ {
13
+ blockOnKitRoutes = true,
14
+ blockOnPreviewMode = true,
15
+ }: Options = {}
16
+ ) {
17
+ const isRouteActive = useIsRouteActive( route );
18
+ const isKitRouteActive = useIsRouteActive( 'panel/global' );
19
+ const isPreviewMode = useIsPreviewMode();
20
+
21
+ const isActive = isRouteActive && ! ( blockOnPreviewMode && isPreviewMode );
22
+
23
+ const isBlocked = (
24
+ ( blockOnPreviewMode && isPreviewMode ) ||
25
+ ( blockOnKitRoutes && isKitRouteActive )
26
+ );
27
+
28
+ return {
29
+ isActive,
30
+ isBlocked,
31
+ };
32
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export * from './dispatchers';
2
+ export * from './hooks';
3
+ export * from './listeners';
4
+ export * from './readers';
@@ -0,0 +1,343 @@
1
+ import {
2
+ commandEndEvent,
3
+ commandStartEvent,
4
+ dispatchReadyEvent,
5
+ listenTo,
6
+ routeOpenEvent,
7
+ routeCloseEvent,
8
+ windowEvent,
9
+ v1ReadyEvent,
10
+ setReady,
11
+ ExtendedWindow,
12
+ } from '../';
13
+
14
+ import {
15
+ dispatchCommandAfter,
16
+ dispatchCommandBefore,
17
+ dispatchRouteClose,
18
+ dispatchRouteOpen,
19
+ dispatchWindowEvent,
20
+ } from '../../__tests__/utils';
21
+
22
+ describe( '@elementor/editor-v1-adapters/listeners', () => {
23
+ beforeEach( () => {
24
+ jest.useFakeTimers();
25
+ } );
26
+
27
+ afterEach( () => {
28
+ jest.useRealTimers();
29
+ } );
30
+
31
+ it( 'should listen to command start', () => {
32
+ // Arrange.
33
+ const commandToListen = 'editor/documents/open',
34
+ anotherCommand = 'non/related/command',
35
+ callback = jest.fn();
36
+
37
+ // Act.
38
+ listenTo(
39
+ commandStartEvent( commandToListen ),
40
+ callback
41
+ );
42
+
43
+ // Dispatch the command to test.
44
+ dispatchCommandBefore( commandToListen );
45
+ dispatchCommandAfter( commandToListen );
46
+
47
+ // Dispatch another command to make sure we don't listen to it.
48
+ dispatchCommandBefore( anotherCommand );
49
+ dispatchCommandAfter( anotherCommand );
50
+
51
+ // Assert.
52
+ expect( callback ).toHaveBeenCalledTimes( 1 );
53
+ expect( callback ).toHaveBeenCalledWith( {
54
+ type: 'command',
55
+ command: 'editor/documents/open',
56
+ originalEvent: expect.any( CustomEvent ),
57
+ } );
58
+ } );
59
+
60
+ it( 'should listen to command end', () => {
61
+ // Arrange.
62
+ const commandToListen = 'editor/documents/open',
63
+ anotherCommand = 'non/related/command',
64
+ callback = jest.fn();
65
+
66
+ // Act.
67
+ listenTo(
68
+ commandEndEvent( commandToListen ),
69
+ callback
70
+ );
71
+
72
+ // Dispatch the command to test.
73
+ dispatchCommandBefore( commandToListen );
74
+ dispatchCommandAfter( commandToListen );
75
+
76
+ // Dispatch another command to make sure we don't listen to it.
77
+ dispatchCommandBefore( anotherCommand );
78
+ dispatchCommandAfter( anotherCommand );
79
+
80
+ // Assert.
81
+ expect( callback ).toHaveBeenCalledTimes( 1 );
82
+ expect( callback ).toHaveBeenCalledWith( {
83
+ type: 'command',
84
+ command: 'editor/documents/open',
85
+ originalEvent: expect.any( CustomEvent ),
86
+ } );
87
+ } );
88
+
89
+ it( 'should listen to route open', () => {
90
+ // Arrange.
91
+ const routeToListen = 'panel/menu',
92
+ anotherRoute = 'non/related/route',
93
+ callback = jest.fn();
94
+
95
+ // Act.
96
+ listenTo(
97
+ routeOpenEvent( routeToListen ),
98
+ callback
99
+ );
100
+
101
+ // Dispatch the route to test.
102
+ dispatchRouteOpen( routeToListen );
103
+
104
+ // Dispatch another route to make sure we don't listen to it.
105
+ dispatchRouteOpen( anotherRoute );
106
+
107
+ // Assert.
108
+ expect( callback ).toHaveBeenCalledTimes( 1 );
109
+ expect( callback ).toHaveBeenCalledWith( {
110
+ type: 'route',
111
+ route: 'panel/menu',
112
+ originalEvent: expect.any( CustomEvent ),
113
+ } );
114
+ } );
115
+
116
+ it( 'should listen to route close', () => {
117
+ // Arrange.
118
+ const routeToListen = 'panel/menu',
119
+ anotherRoute = 'non/related/route',
120
+ callback = jest.fn();
121
+
122
+ // Act.
123
+ listenTo(
124
+ routeCloseEvent( routeToListen ),
125
+ callback
126
+ );
127
+
128
+ // Dispatch the route to test.
129
+ dispatchRouteClose( routeToListen );
130
+
131
+ // Dispatch another route to make sure we don't listen to it.
132
+ dispatchRouteClose( anotherRoute );
133
+
134
+ // Assert.
135
+ expect( callback ).toHaveBeenCalledTimes( 1 );
136
+ expect( callback ).toHaveBeenCalledWith( {
137
+ type: 'route',
138
+ route: 'panel/menu',
139
+ originalEvent: expect.any( CustomEvent ),
140
+ } );
141
+ } );
142
+
143
+ it( 'should listen to window events', () => {
144
+ // Arrange.
145
+ const event = 'test-event',
146
+ callback = jest.fn();
147
+
148
+ // Act.
149
+ listenTo(
150
+ windowEvent( event ),
151
+ callback
152
+ );
153
+
154
+ // Dispatch events.
155
+ dispatchWindowEvent( event );
156
+
157
+ // Assert.
158
+ expect( callback ).toHaveBeenCalledTimes( 1 );
159
+ expect( callback ).toHaveBeenCalledWith( {
160
+ type: 'window-event',
161
+ event: 'test-event',
162
+ originalEvent: expect.any( Event ),
163
+ } );
164
+ } );
165
+
166
+ it( 'should listen to the same event with multiple callbacks', () => {
167
+ // Arrange.
168
+ const event = 'test-event',
169
+ callback1 = jest.fn(),
170
+ callback2 = jest.fn();
171
+
172
+ // Act.
173
+ listenTo(
174
+ windowEvent( event ),
175
+ callback1
176
+ );
177
+
178
+ listenTo(
179
+ windowEvent( event ),
180
+ callback2
181
+ );
182
+
183
+ // Dispatch events.
184
+ dispatchWindowEvent( event );
185
+
186
+ // Assert.
187
+ expect( callback1 ).toHaveBeenCalledTimes( 1 );
188
+ expect( callback2 ).toHaveBeenCalledTimes( 1 );
189
+ } );
190
+
191
+ it( 'should listen to an array of events', () => {
192
+ // Arrange.
193
+ const command = 'test-command',
194
+ route = 'test-route',
195
+ event = 'test-event',
196
+ callback = jest.fn();
197
+
198
+ // Act.
199
+ listenTo( [
200
+ windowEvent( event ),
201
+ commandStartEvent( command ),
202
+ routeOpenEvent( route ),
203
+ ], callback );
204
+
205
+ // Dispatch events.
206
+ dispatchCommandBefore( command );
207
+ dispatchCommandAfter( command );
208
+
209
+ dispatchRouteOpen( route );
210
+ dispatchRouteClose( route );
211
+
212
+ dispatchWindowEvent( event );
213
+
214
+ // Assert.
215
+ expect( callback ).toHaveBeenCalledTimes( 3 );
216
+
217
+ expect( callback ).toHaveBeenNthCalledWith( 1, {
218
+ type: 'command',
219
+ command: 'test-command',
220
+ originalEvent: expect.any( CustomEvent ),
221
+ } );
222
+
223
+ expect( callback ).toHaveBeenNthCalledWith( 2, {
224
+ type: 'route',
225
+ route: 'test-route',
226
+ originalEvent: expect.any( CustomEvent ),
227
+ } );
228
+
229
+ expect( callback ).toHaveBeenNthCalledWith( 3, {
230
+ type: 'window-event',
231
+ event: 'test-event',
232
+ originalEvent: expect.any( Event ),
233
+ } );
234
+ } );
235
+
236
+ it( 'should cleanup listeners', () => {
237
+ // Arrange.
238
+ const event1 = 'test-event-1',
239
+ event2 = 'test-event-2',
240
+ callback1 = jest.fn(),
241
+ callback2 = jest.fn();
242
+
243
+ const cleanup1 = listenTo(
244
+ [
245
+ windowEvent( event1 ),
246
+ windowEvent( event2 ),
247
+ ],
248
+ callback1,
249
+ );
250
+
251
+ const cleanup2 = listenTo(
252
+ windowEvent( event1 ),
253
+ callback2,
254
+ );
255
+
256
+ // Act.
257
+ cleanup1();
258
+ cleanup2();
259
+
260
+ // Dispatch events.
261
+ dispatchWindowEvent( event1 );
262
+ dispatchWindowEvent( event2 );
263
+
264
+ // Assert.
265
+ expect( callback1 ).toHaveBeenCalledTimes( 0 );
266
+ expect( callback2 ).toHaveBeenCalledTimes( 0 );
267
+ } );
268
+
269
+ it( 'should not fail when calling the same cleanup twice', () => {
270
+ // Arrange.
271
+ const event = 'test-event',
272
+ callback = jest.fn();
273
+
274
+ const cleanup = listenTo(
275
+ windowEvent( event ),
276
+ callback,
277
+ );
278
+
279
+ // Act.
280
+ cleanup();
281
+ cleanup();
282
+
283
+ // Dispatch events.
284
+ dispatchWindowEvent( event );
285
+
286
+ // Assert.
287
+ expect( callback ).toHaveBeenCalledTimes( 0 );
288
+ } );
289
+
290
+ it( 'should trigger v1 ready when v1 is loaded after v2', async () => {
291
+ // Arrange.
292
+ const callback = jest.fn();
293
+ const extendedWindow = ( window as unknown as ExtendedWindow );
294
+
295
+ extendedWindow.__elementorEditorV1LoadingPromise = new Promise( ( resolve ) => {
296
+ setTimeout( resolve, 1000 );
297
+ } );
298
+
299
+ // Act.
300
+ listenTo(
301
+ v1ReadyEvent(),
302
+ callback
303
+ );
304
+
305
+ dispatchReadyEvent();
306
+
307
+ await jest.runAllTimers();
308
+
309
+ // Assert.
310
+ expect( callback ).toHaveBeenCalledTimes( 1 );
311
+ } );
312
+
313
+ it( 'should not trigger callback when the application is not ready', () => {
314
+ // Arrange
315
+ setReady( false );
316
+
317
+ const event = 'test-event';
318
+ const callback = jest.fn();
319
+
320
+ // Act.
321
+ listenTo(
322
+ windowEvent( event ),
323
+ callback
324
+ );
325
+
326
+ // Dispatch events.
327
+ dispatchWindowEvent( event );
328
+
329
+ // Assert.
330
+ expect( callback ).not.toHaveBeenCalled();
331
+ } );
332
+
333
+ it( 'should throw when v1 is not loaded', async () => {
334
+ // Act & Assert.
335
+ try {
336
+ await dispatchReadyEvent();
337
+ } catch ( e ) {
338
+ expect( e ).toBe( 'Elementor Editor V1 is not loaded' );
339
+ }
340
+
341
+ expect.assertions( 1 );
342
+ } );
343
+ } );
@@ -0,0 +1,48 @@
1
+ import { CommandEventDescriptor, RouteEventDescriptor, WindowEventDescriptor } from './types';
2
+
3
+ export const commandStartEvent = ( command: CommandEventDescriptor['name'] ): CommandEventDescriptor => {
4
+ return {
5
+ type: 'command',
6
+ name: command,
7
+ state: 'before',
8
+ };
9
+ };
10
+
11
+ export const commandEndEvent = ( command: CommandEventDescriptor['name'] ): CommandEventDescriptor => {
12
+ return {
13
+ type: 'command',
14
+ name: command,
15
+ state: 'after',
16
+ };
17
+ };
18
+
19
+ export const routeOpenEvent = ( route: RouteEventDescriptor['name'] ): RouteEventDescriptor => {
20
+ return {
21
+ type: 'route',
22
+ name: route,
23
+ state: 'open',
24
+ };
25
+ };
26
+
27
+ export const routeCloseEvent = ( route: RouteEventDescriptor['name'] ): RouteEventDescriptor => {
28
+ return {
29
+ type: 'route',
30
+ name: route,
31
+ state: 'close',
32
+ };
33
+ };
34
+
35
+ export const windowEvent = ( event: WindowEventDescriptor['name'] ): WindowEventDescriptor => {
36
+ return {
37
+ type: 'window-event',
38
+ name: event,
39
+ };
40
+ };
41
+
42
+ export const v1ReadyEvent = () => {
43
+ return windowEvent( 'elementor/initialized' );
44
+ };
45
+
46
+ export const editModeChangeEvent = () => {
47
+ return windowEvent( 'elementor/edit-mode/change' );
48
+ };
@@ -0,0 +1,5 @@
1
+ export * from './event-creators';
2
+ export * from './listeners';
3
+ export * from './types';
4
+ export * from './is-ready';
5
+ export { dispatchReadyEvent } from './utils';