@elementor/editor-canvas 0.15.4 → 0.17.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.
Files changed (33) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/CHANGELOG.md +38 -0
  3. package/dist/index.d.mts +4 -1
  4. package/dist/index.d.ts +4 -1
  5. package/dist/index.js +448 -281
  6. package/dist/index.js.map +1 -1
  7. package/dist/index.mjs +437 -267
  8. package/dist/index.mjs.map +1 -1
  9. package/package.json +6 -5
  10. package/src/__tests__/styles-prop-resolver.test.ts +1 -0
  11. package/src/components/__tests__/style-renderer.test.tsx +65 -0
  12. package/src/components/style-renderer.tsx +32 -0
  13. package/src/hooks/__tests__/use-style-items.test.ts +136 -0
  14. package/src/hooks/__tests__/use-style-prop-resolver.test.ts +51 -0
  15. package/src/hooks/use-on-mount.ts +13 -0
  16. package/src/hooks/use-style-items.ts +78 -0
  17. package/src/hooks/use-style-prop-resolver.ts +22 -0
  18. package/src/hooks/use-style-renderer.ts +19 -0
  19. package/src/index.ts +1 -0
  20. package/src/init.tsx +9 -2
  21. package/src/legacy/create-element-type.ts +14 -0
  22. package/src/legacy/create-templated-element-type.ts +1 -1
  23. package/src/legacy/types.ts +9 -0
  24. package/src/prevent-link-in-link-commands.ts +132 -0
  25. package/src/renderers/__tests__/{render-styles.test.ts → create-styles-renderer.test.ts} +35 -19
  26. package/src/renderers/{render-styles.ts → create-styles-renderer.ts} +35 -32
  27. package/src/sync/{get-canvas-iframe-body.ts → get-canvas-iframe-head.ts} +2 -2
  28. package/src/sync/types.ts +7 -0
  29. package/src/utils/abort-previous-runs.ts +16 -0
  30. package/src/__tests__/init-styles-renderer.test.ts +0 -112
  31. package/src/init-styles-renderer.ts +0 -84
  32. /package/src/{legacy → utils}/__tests__/signalized-process.test.ts +0 -0
  33. /package/src/{legacy → utils}/signalized-process.ts +0 -0
@@ -0,0 +1,136 @@
1
+ import { createMockStyleDefinition, createMockStylesProvider } from 'test-utils';
2
+ import { stylesRepository } from '@elementor/editor-styles-repository';
3
+ import { registerDataHook } from '@elementor/editor-v1-adapters';
4
+ import { act, renderHook } from '@testing-library/react';
5
+
6
+ import { useStyleItems } from '../use-style-items';
7
+ import { useStyleRenderer } from '../use-style-renderer';
8
+
9
+ jest.mock( '@elementor/editor-styles-repository', () => ( {
10
+ stylesRepository: {
11
+ getProviders: jest.fn(),
12
+ },
13
+ } ) );
14
+
15
+ jest.mock( '@elementor/editor-v1-adapters', () => ( {
16
+ ...jest.requireActual( '@elementor/editor-v1-adapters' ),
17
+ registerDataHook: jest.fn(),
18
+ } ) );
19
+
20
+ jest.mock( '../use-style-prop-resolver', () => ( {
21
+ useStylePropResolver: jest.fn(),
22
+ } ) );
23
+
24
+ jest.mock( '../use-style-renderer', () => ( {
25
+ useStyleRenderer: jest.fn(),
26
+ } ) );
27
+
28
+ describe( 'useStyleItems', () => {
29
+ beforeEach( () => {
30
+ jest.mocked( useStyleRenderer ).mockReturnValue(
31
+ jest
32
+ .fn()
33
+ .mockImplementation( ( { styles } ) => styles.map( ( style: { id: string } ) => ( { id: style.id } ) ) )
34
+ );
35
+ } );
36
+
37
+ it( 'should return style items from providers when subscribed', async () => {
38
+ // Arrange.
39
+ const mockProvider1 = createMockStylesProvider( {
40
+ key: 'provider1',
41
+ priority: 2,
42
+ styleDefinitions: [
43
+ createMockStyleDefinition( { id: 'style1' } ),
44
+ createMockStyleDefinition( { id: 'style2' } ),
45
+ ],
46
+ } );
47
+
48
+ const mockProvider2 = createMockStylesProvider( {
49
+ key: 'provider2',
50
+ priority: 1,
51
+ styleDefinitions: [
52
+ createMockStyleDefinition( { id: 'style3' } ),
53
+ createMockStyleDefinition( { id: 'style4' } ),
54
+ ],
55
+ } );
56
+
57
+ jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider1, mockProvider2 ] );
58
+
59
+ // Act.
60
+ const { result } = renderHook( () => useStyleItems() );
61
+
62
+ // Assert.
63
+ expect( result.current ).toEqual( [] );
64
+
65
+ // Act.
66
+ await act( async () => {
67
+ mockProvider1.actions.updateProps?.( {
68
+ id: 'style1',
69
+ meta: { breakpoint: null, state: null },
70
+ props: { a: 1 },
71
+ } );
72
+ } );
73
+
74
+ // Assert.
75
+ expect( result.current ).toEqual( [ { id: 'style1' }, { id: 'style2' } ] );
76
+
77
+ // Act.
78
+ await act( async () => {
79
+ mockProvider2.actions.updateProps?.( {
80
+ id: 'style3',
81
+ meta: { breakpoint: null, state: null },
82
+ props: { a: 1 },
83
+ } );
84
+ } );
85
+
86
+ // Assert.
87
+ expect( result.current ).toEqual( [ { id: 'style3' }, { id: 'style4' }, { id: 'style1' }, { id: 'style2' } ] );
88
+ } );
89
+
90
+ it( 'should return style items when attach-preview command is triggered', async () => {
91
+ // Arrange.
92
+ const mockProvider1 = createMockStylesProvider( {
93
+ key: 'provider1',
94
+ priority: 2,
95
+ styleDefinitions: [
96
+ createMockStyleDefinition( { id: 'style1' } ),
97
+ createMockStyleDefinition( { id: 'style2' } ),
98
+ ],
99
+ } );
100
+
101
+ const mockProvider2 = createMockStylesProvider( {
102
+ key: 'provider2',
103
+ priority: 1,
104
+ styleDefinitions: [
105
+ createMockStyleDefinition( { id: 'style3' } ),
106
+ createMockStyleDefinition( { id: 'style4' } ),
107
+ ],
108
+ } );
109
+
110
+ jest.mocked( stylesRepository ).getProviders.mockReturnValue( [ mockProvider1, mockProvider2 ] );
111
+
112
+ let attachPreviewCallback: () => Promise< void >;
113
+
114
+ jest.mocked( registerDataHook ).mockImplementation( ( position, command, callback ) => {
115
+ if ( command === 'editor/documents/attach-preview' && position === 'after' ) {
116
+ attachPreviewCallback = callback as never;
117
+ }
118
+
119
+ return null as never;
120
+ } );
121
+
122
+ // Act.
123
+ const { result } = renderHook( () => useStyleItems() );
124
+
125
+ // Assert.
126
+ expect( result.current ).toEqual( [] );
127
+
128
+ // Act.
129
+ await act( async () => {
130
+ await attachPreviewCallback?.();
131
+ } );
132
+
133
+ // Assert.
134
+ expect( result.current ).toEqual( [ { id: 'style3' }, { id: 'style4' }, { id: 'style1' }, { id: 'style2' } ] );
135
+ } );
136
+ } );
@@ -0,0 +1,51 @@
1
+ import { createMockPropType } from 'test-utils';
2
+ import { stringPropTypeUtil } from '@elementor/editor-props';
3
+ import { getStylesSchema } from '@elementor/editor-styles';
4
+ import { act, renderHook } from '@testing-library/react';
5
+
6
+ import { initStyleTransformers } from '../../init-style-transformers';
7
+ import { enqueueFont } from '../../sync/enqueue-font';
8
+ import { useStylePropResolver } from '../use-style-prop-resolver';
9
+
10
+ jest.mock( '../../sync/enqueue-font' );
11
+ jest.mock( '@elementor/editor-styles' );
12
+
13
+ describe( 'useStylePropResolver', () => {
14
+ it( 'should call enqueueFont with the correct value when font-family is resolved', async () => {
15
+ // Arrange.
16
+ initStyleTransformers();
17
+
18
+ jest.mocked( getStylesSchema ).mockReturnValue( {
19
+ 'font-family': createMockPropType( { key: 'string', kind: 'plain' } ),
20
+ 'another-prop': createMockPropType( { key: 'string', kind: 'plain' } ),
21
+ } );
22
+
23
+ const { result } = renderHook( useStylePropResolver );
24
+
25
+ // Act.
26
+ const resolve = result.current;
27
+
28
+ await act( () =>
29
+ resolve( {
30
+ props: {
31
+ 'another-prop': stringPropTypeUtil.create( 'value' ),
32
+ },
33
+ } )
34
+ );
35
+
36
+ // Assert.
37
+ expect( enqueueFont ).not.toHaveBeenCalled();
38
+
39
+ // Act.
40
+ await act( () =>
41
+ resolve( {
42
+ props: {
43
+ 'font-family': stringPropTypeUtil.create( 'arial' ),
44
+ },
45
+ } )
46
+ );
47
+
48
+ // Assert.
49
+ expect( enqueueFont ).toHaveBeenCalledWith( 'arial' );
50
+ } );
51
+ } );
@@ -0,0 +1,13 @@
1
+ import { useEffect, useRef } from 'react';
2
+
3
+ export function useOnMount( cb: () => void ) {
4
+ const mounted = useRef( false );
5
+
6
+ useEffect( () => {
7
+ if ( ! mounted.current ) {
8
+ mounted.current = true;
9
+
10
+ cb();
11
+ }
12
+ }, [] ); // eslint-disable-line react-hooks/exhaustive-deps
13
+ }
@@ -0,0 +1,78 @@
1
+ import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react';
2
+ import { type StylesProvider, stylesRepository } from '@elementor/editor-styles-repository';
3
+ import { registerDataHook } from '@elementor/editor-v1-adapters';
4
+
5
+ import { type StyleItem, type StyleRenderer } from '../renderers/create-styles-renderer';
6
+ import { abortPreviousRuns } from '../utils/abort-previous-runs';
7
+ import { signalizedProcess } from '../utils/signalized-process';
8
+ import { useOnMount } from './use-on-mount';
9
+ import { useStylePropResolver } from './use-style-prop-resolver';
10
+ import { useStyleRenderer } from './use-style-renderer';
11
+
12
+ type ProviderAndStyleItems = { provider: StylesProvider; items: StyleItem[] };
13
+
14
+ type ProviderAndSubscriber = { provider: StylesProvider; subscriber: () => Promise< void > };
15
+
16
+ type ProviderAndStyleItemsMap = Record< string, ProviderAndStyleItems >;
17
+
18
+ export function useStyleItems() {
19
+ const resolve = useStylePropResolver();
20
+ const renderStyles = useStyleRenderer( resolve );
21
+
22
+ const [ styleItems, setStyleItems ] = useState< ProviderAndStyleItemsMap >( {} );
23
+
24
+ const providerAndSubscribers = useMemo( () => {
25
+ return stylesRepository.getProviders().map( ( provider ): ProviderAndSubscriber => {
26
+ return {
27
+ provider,
28
+ subscriber: createProviderSubscriber( {
29
+ provider,
30
+ renderStyles,
31
+ setStyleItems,
32
+ } ),
33
+ };
34
+ } );
35
+ }, [ renderStyles ] );
36
+
37
+ useEffect( () => {
38
+ const unsubscribes = providerAndSubscribers.map( ( { provider, subscriber } ) =>
39
+ provider.subscribe( subscriber )
40
+ );
41
+
42
+ return () => {
43
+ unsubscribes.forEach( ( unsubscribe ) => unsubscribe() );
44
+ };
45
+ }, [ providerAndSubscribers ] );
46
+
47
+ useOnMount( () => {
48
+ registerDataHook( 'after', 'editor/documents/attach-preview', async () => {
49
+ const promises = providerAndSubscribers.map( async ( { subscriber } ) => subscriber() );
50
+
51
+ await Promise.all( promises );
52
+ } );
53
+ } );
54
+
55
+ return Object.values( styleItems )
56
+ .sort( ( { provider: providerA }, { provider: providerB } ) => providerA.priority - providerB.priority )
57
+ .flatMap( ( { items } ) => items );
58
+ }
59
+
60
+ type CreateProviderSubscriberArgs = {
61
+ provider: StylesProvider;
62
+ renderStyles: StyleRenderer;
63
+ setStyleItems: Dispatch< SetStateAction< ProviderAndStyleItemsMap > >;
64
+ };
65
+
66
+ function createProviderSubscriber( { provider, renderStyles, setStyleItems }: CreateProviderSubscriberArgs ) {
67
+ return abortPreviousRuns( ( abortController ) =>
68
+ signalizedProcess( abortController.signal )
69
+ .then( ( _, signal ) => renderStyles( { styles: provider.actions.get(), signal } ) )
70
+ .then( ( items ) => {
71
+ setStyleItems( ( prev ) => ( {
72
+ ...prev,
73
+ [ provider.key ]: { provider, items },
74
+ } ) );
75
+ } )
76
+ .execute()
77
+ );
78
+ }
@@ -0,0 +1,22 @@
1
+ import { useMemo } from 'react';
2
+ import { getStylesSchema } from '@elementor/editor-styles';
3
+
4
+ import { createPropsResolver } from '../renderers/create-props-resolver';
5
+ import { styleTransformersRegistry } from '../style-transformers-registry';
6
+ import { enqueueFont } from '../sync/enqueue-font';
7
+
8
+ export function useStylePropResolver() {
9
+ return useMemo( () => {
10
+ return createPropsResolver( {
11
+ transformers: styleTransformersRegistry,
12
+ schema: getStylesSchema(),
13
+ onPropResolve: ( { key, value } ) => {
14
+ if ( key !== 'font-family' || typeof value !== 'string' ) {
15
+ return;
16
+ }
17
+
18
+ enqueueFont( value );
19
+ },
20
+ } );
21
+ }, [] );
22
+ }
@@ -0,0 +1,19 @@
1
+ import { useMemo } from 'react';
2
+ import { useBreakpointsMap } from '@elementor/editor-responsive';
3
+
4
+ import { type PropsResolver } from '../renderers/create-props-resolver';
5
+ import { createStylesRenderer } from '../renderers/create-styles-renderer';
6
+
7
+ const SELECTOR_PREFIX = '.elementor';
8
+
9
+ export function useStyleRenderer( resolve: PropsResolver ) {
10
+ const breakpoints = useBreakpointsMap();
11
+
12
+ return useMemo( () => {
13
+ return createStylesRenderer( {
14
+ selectorPrefix: SELECTOR_PREFIX,
15
+ breakpoints,
16
+ resolve,
17
+ } );
18
+ }, [ resolve, breakpoints ] );
19
+ }
package/src/index.ts CHANGED
@@ -2,5 +2,6 @@ import { init } from './init';
2
2
 
3
3
  export { styleTransformersRegistry } from './style-transformers-registry';
4
4
  export { settingsTransformersRegistry } from './settings-transformers-registry';
5
+ export { createTransformer } from './transformers/create-transformer';
5
6
 
6
7
  init();
package/src/init.tsx CHANGED
@@ -1,17 +1,19 @@
1
1
  import { injectIntoTop } from '@elementor/editor';
2
2
 
3
3
  import { ElementsOverlays } from './components/elements-overlays';
4
+ import { StyleRenderer } from './components/style-renderer';
4
5
  import { initSettingsTransformers } from './init-settings-transformers';
5
6
  import { initStyleTransformers } from './init-style-transformers';
6
- import { initStylesRenderer } from './init-styles-renderer';
7
7
  import { initLegacyViews } from './legacy/init-legacy-views';
8
+ import { initLinkInLinkPrevention } from './prevent-link-in-link-commands';
8
9
  import { initStyleCommands } from './style-commands/init-style-commands';
9
10
 
10
11
  export function init() {
11
12
  initStyleTransformers();
12
- initStylesRenderer();
13
13
  initStyleCommands();
14
14
 
15
+ initLinkInLinkPrevention();
16
+
15
17
  initLegacyViews();
16
18
 
17
19
  initSettingsTransformers();
@@ -20,4 +22,9 @@ export function init() {
20
22
  id: 'elements-overlays',
21
23
  component: ElementsOverlays,
22
24
  } );
25
+
26
+ injectIntoTop( {
27
+ id: 'canvas-style-render',
28
+ component: StyleRenderer,
29
+ } );
23
30
  }
@@ -27,6 +27,7 @@ export function createElementViewClassDeclaration(): typeof ElementView {
27
27
  super.onRender( ...args );
28
28
 
29
29
  this.#dispatchEvent( 'elementor/preview/atomic-widget/render' );
30
+ this.#dispatchPreviewEvent( 'elementor/element/render' );
30
31
  }
31
32
 
32
33
  // Dispatch `destroy` event so the overlay layer will be updated
@@ -34,6 +35,7 @@ export function createElementViewClassDeclaration(): typeof ElementView {
34
35
  super.onDestroy( ...args );
35
36
 
36
37
  this.#dispatchEvent( 'elementor/preview/atomic-widget/destroy' );
38
+ this.#dispatchPreviewEvent( 'elementor/element/destroy' );
37
39
  }
38
40
 
39
41
  attributes() {
@@ -77,6 +79,18 @@ export function createElementViewClassDeclaration(): typeof ElementView {
77
79
  );
78
80
  }
79
81
 
82
+ #dispatchPreviewEvent( eventType: string ) {
83
+ legacyWindow.elementor?.$preview?.[ 0 ]?.contentWindow.dispatchEvent(
84
+ new CustomEvent( eventType, {
85
+ detail: {
86
+ id: this.model.get( 'id' ),
87
+ type: this.model.get( 'widgetType' ),
88
+ element: this.getDomElement().get( 0 ),
89
+ },
90
+ } )
91
+ );
92
+ }
93
+
80
94
  getContextMenuGroups() {
81
95
  return super.getContextMenuGroups().filter( ( group ) => group.name !== 'save' );
82
96
  }
@@ -3,8 +3,8 @@ import type { V1ElementConfig } from '@elementor/editor-elements';
3
3
  import { type DomRenderer } from '../renderers/create-dom-renderer';
4
4
  import { createPropsResolver, type PropsResolver } from '../renderers/create-props-resolver';
5
5
  import { settingsTransformersRegistry } from '../settings-transformers-registry';
6
+ import { signalizedProcess } from '../utils/signalized-process';
6
7
  import { createElementViewClassDeclaration } from './create-element-type';
7
- import { signalizedProcess } from './signalized-process';
8
8
  import { type ElementType, type ElementView, type LegacyWindow } from './types';
9
9
 
10
10
  type CreateTypeOptions = {
@@ -15,6 +15,13 @@ export type LegacyWindow = Window & {
15
15
  elementsManager: {
16
16
  registerElementType: ( type: ElementType ) => void;
17
17
  };
18
+ $preview: [
19
+ {
20
+ contentWindow: {
21
+ dispatchEvent: ( event: Event ) => void;
22
+ };
23
+ },
24
+ ];
18
25
  };
19
26
  };
20
27
 
@@ -62,6 +69,7 @@ export declare class ElementView {
62
69
  type JQueryElement = {
63
70
  find: ( selector: string ) => JQueryElement;
64
71
  html: ( html: string ) => void;
72
+ get: ( index: number ) => HTMLElement;
65
73
  };
66
74
 
67
75
  type BackboneModel< Model extends object > = {
@@ -72,6 +80,7 @@ type BackboneModel< Model extends object > = {
72
80
  type ElementModel = {
73
81
  id: string;
74
82
  settings: BackboneModel< Props >;
83
+ widgetType: string;
75
84
  };
76
85
 
77
86
  type ToJSON< T > = {
@@ -0,0 +1,132 @@
1
+ import {
2
+ getAnchoredAncestorId,
3
+ getAnchoredDescendantId,
4
+ isElementAnchored,
5
+ type V1Element,
6
+ } from '@elementor/editor-elements';
7
+ import { type NotificationData, notify } from '@elementor/editor-notifications';
8
+ import { blockCommand } from '@elementor/editor-v1-adapters';
9
+ import { type ButtonProps } from '@elementor/ui';
10
+ import { __ } from '@wordpress/i18n';
11
+
12
+ import { type CanvasExtendedWindow } from './sync/types';
13
+
14
+ export function initLinkInLinkPrevention() {
15
+ blockCommand( {
16
+ command: 'document/elements/paste',
17
+ condition: blockLinkInLinkPaste,
18
+ } );
19
+
20
+ blockCommand( {
21
+ command: 'document/elements/move',
22
+ condition: blockLinkInLinkMove,
23
+ } );
24
+ }
25
+
26
+ type PasteArgs = {
27
+ containers?: V1Element[];
28
+ container?: V1Element;
29
+ storageType?: string;
30
+ };
31
+
32
+ type MoveArgs = {
33
+ containers?: V1Element[];
34
+ container?: V1Element;
35
+ target?: V1Element;
36
+ };
37
+
38
+ export type StorageContent = {
39
+ clipboard?: {
40
+ elements?: { id?: string }[];
41
+ };
42
+ };
43
+
44
+ const learnMoreActionProps: Partial< ButtonProps > = {
45
+ href: 'https://go.elementor.com/element-link-inside-link-infotip',
46
+ target: '_blank',
47
+ color: 'inherit',
48
+ variant: 'text',
49
+ sx: {
50
+ marginInlineStart: '20px',
51
+ },
52
+ children: 'Learn more',
53
+ };
54
+
55
+ function blockLinkInLinkPaste( args: PasteArgs ): boolean {
56
+ const { containers = [ args.container ], storageType } = args;
57
+ const targetElements = containers;
58
+
59
+ if ( storageType !== 'localstorage' ) {
60
+ return false;
61
+ }
62
+
63
+ const data = ( window as CanvasExtendedWindow )?.elementorCommon?.storage?.get();
64
+
65
+ if ( ! data?.clipboard?.elements ) {
66
+ return false;
67
+ }
68
+
69
+ const sourceElements = data.clipboard.elements;
70
+
71
+ const notification: NotificationData = {
72
+ type: 'default',
73
+ message: __(
74
+ "To paste a link to this element, first remove the link from it's parent container.",
75
+ 'elementor'
76
+ ),
77
+ id: 'paste-in-link-blocked',
78
+ additionalActionProps: [ learnMoreActionProps ],
79
+ };
80
+
81
+ const blocked = shouldBlock( sourceElements, targetElements );
82
+
83
+ if ( blocked ) {
84
+ notify( notification );
85
+ }
86
+
87
+ return blocked;
88
+ }
89
+
90
+ function blockLinkInLinkMove( args: MoveArgs ): boolean {
91
+ const { containers = [ args.container ], target } = args;
92
+ const sourceElements = containers;
93
+ const targetElement = target;
94
+
95
+ const notification: NotificationData = {
96
+ type: 'default',
97
+ message: __( "To drag a link to this element, first remove the link from it's parent container.", 'elementor' ),
98
+ id: 'move-in-link-blocked',
99
+ additionalActionProps: [ learnMoreActionProps ],
100
+ };
101
+
102
+ const isBlocked = shouldBlock( sourceElements, [ targetElement ] );
103
+
104
+ if ( isBlocked ) {
105
+ notify( notification );
106
+ }
107
+
108
+ return isBlocked;
109
+ }
110
+
111
+ function shouldBlock(
112
+ sourceElements?: ( { id?: string } | undefined )[],
113
+ targetElements?: ( V1Element | undefined )[]
114
+ ): boolean {
115
+ if ( ! sourceElements?.length || ! targetElements?.length ) {
116
+ return false;
117
+ }
118
+
119
+ const isSourceContainsAnAnchor = sourceElements.some( ( src ) => {
120
+ return src?.id ? isElementAnchored( src.id ) || !! getAnchoredDescendantId( src.id ) : false;
121
+ } );
122
+
123
+ if ( ! isSourceContainsAnAnchor ) {
124
+ return false;
125
+ }
126
+
127
+ const isTargetContainsAnAnchor = targetElements.some( ( target ) => {
128
+ return target?.id ? isElementAnchored( target.id ) || !! getAnchoredAncestorId( target.id ) : false;
129
+ } );
130
+
131
+ return isTargetContainsAnAnchor;
132
+ }
@@ -2,7 +2,7 @@
2
2
  import type { BreakpointsMap } from '@elementor/editor-responsive';
3
3
  import { type StyleDefinition } from '@elementor/editor-styles';
4
4
 
5
- import renderStyles from '../render-styles';
5
+ import { createStylesRenderer } from '../create-styles-renderer';
6
6
 
7
7
  describe( 'renderStyles', () => {
8
8
  it( 'should render media queries', async () => {
@@ -25,18 +25,24 @@ describe( 'renderStyles', () => {
25
25
 
26
26
  const resolve = jest.fn( ( { props } ) => props );
27
27
 
28
- // Act.
29
- const cssString = await renderStyles( {
30
- styles: [ styleDef ],
31
- resolve,
28
+ const renderStyles = createStylesRenderer( {
32
29
  breakpoints: { tablet: { width: 992, type: 'max-width' } } as BreakpointsMap,
30
+ resolve,
33
31
  } );
34
32
 
33
+ // Act.
34
+ const result = await renderStyles( { styles: [ styleDef ] } );
35
+
35
36
  // Assert.
36
37
  const defaultStyle = '.test{font-size:24px;}';
37
38
  const tabletStyle = '@media(max-width:992px){.test{font-size:18px;}}';
38
39
 
39
- expect( cssString ).toBe( `<style data-style-id="test">${ defaultStyle }${ tabletStyle }</style>` );
40
+ expect( result ).toEqual( [
41
+ {
42
+ id: 'test',
43
+ value: `${ defaultStyle }${ tabletStyle }`,
44
+ },
45
+ ] );
40
46
 
41
47
  expect( resolve ).toHaveBeenCalledTimes( 2 );
42
48
  expect( resolve ).toHaveBeenNthCalledWith( 1, { props: { 'font-size': '24px' } } );
@@ -67,21 +73,25 @@ describe( 'renderStyles', () => {
67
73
 
68
74
  const resolve = jest.fn( ( { props } ) => props );
69
75
 
70
- // Act.
71
- const cssString = await renderStyles( {
72
- styles: [ styleDef ],
73
- resolve,
76
+ const renderStyles = createStylesRenderer( {
74
77
  breakpoints: { mobile: { width: 768, type: 'max-width' } } as BreakpointsMap,
78
+ resolve,
75
79
  } );
76
80
 
81
+ // Act.
82
+ const cssString = await renderStyles( { styles: [ styleDef ] } );
83
+
77
84
  // Assert.
78
85
  const defaultStyle = '.test{font-size:24px;}';
79
86
  const hoverStyle = '.test:hover{font-size:18px;}';
80
87
  const focusStyle = '@media(max-width:768px){.test:focus{font-size:12px;}}';
81
88
 
82
- expect( cssString ).toBe(
83
- `<style data-style-id="test">${ defaultStyle }${ hoverStyle }${ focusStyle }</style>`
84
- );
89
+ expect( cssString ).toEqual( [
90
+ {
91
+ id: 'test',
92
+ value: `${ defaultStyle }${ hoverStyle }${ focusStyle }`,
93
+ },
94
+ ] );
85
95
  } );
86
96
 
87
97
  it( 'should add selector prefix to the output', async () => {
@@ -100,15 +110,21 @@ describe( 'renderStyles', () => {
100
110
 
101
111
  const resolve = jest.fn( ( { props } ) => props );
102
112
 
103
- // Act.
104
- const result = await renderStyles( {
105
- styles: [ styleDef ],
106
- resolve,
107
- selectorPrefix: '.elementor-prefix',
113
+ const renderStyles = createStylesRenderer( {
108
114
  breakpoints: {} as BreakpointsMap,
115
+ selectorPrefix: '.elementor-prefix',
116
+ resolve,
109
117
  } );
110
118
 
119
+ // Act.
120
+ const result = await renderStyles( { styles: [ styleDef ] } );
121
+
111
122
  // Assert.
112
- expect( result ).toBe( `<style data-style-id="test">.elementor-prefix .test{font-size:24px;}</style>` );
123
+ expect( result ).toEqual( [
124
+ {
125
+ id: 'test',
126
+ value: '.elementor-prefix .test{font-size:24px;}',
127
+ },
128
+ ] );
113
129
  } );
114
130
  } );