Skip to content

Commit e77257e

Browse files
authored
Tests: Expand JavaScript unit tests for field bindings (#330)
1 parent 402b2cc commit e77257e

File tree

5 files changed

+1080
-202
lines changed

5 files changed

+1080
-202
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/**
2+
* Unit tests for block-editor.js - filter registration and module behavior
3+
*
4+
* Note: Testing React HOCs that use hooks requires careful mocking.
5+
* These tests focus on verifying module setup and filter registration.
6+
*/
7+
8+
import { addFilter } from '@wordpress/hooks';
9+
10+
// Mock WordPress dependencies
11+
jest.mock( '@wordpress/hooks', () => ( {
12+
addFilter: jest.fn(),
13+
} ) );
14+
15+
jest.mock( '@wordpress/element', () => ( {
16+
useCallback: jest.fn( ( fn ) => fn ),
17+
useMemo: jest.fn( ( fn ) => fn() ),
18+
} ) );
19+
20+
jest.mock( '@wordpress/compose', () => ( {
21+
createHigherOrderComponent: jest.fn( ( fn, name ) => {
22+
const hoc = fn;
23+
hoc.displayName = name;
24+
return hoc;
25+
} ),
26+
} ) );
27+
28+
jest.mock( '@wordpress/block-editor', () => ( {
29+
InspectorControls: 'InspectorControls',
30+
useBlockBindingsUtils: jest.fn( () => ( {
31+
updateBlockBindings: jest.fn(),
32+
removeAllBlockBindings: jest.fn(),
33+
} ) ),
34+
} ) );
35+
36+
jest.mock( '@wordpress/components', () => ( {
37+
ComboboxControl: 'ComboboxControl',
38+
__experimentalToolsPanel: 'ToolsPanel',
39+
__experimentalToolsPanelItem: 'ToolsPanelItem',
40+
} ) );
41+
42+
jest.mock( '@wordpress/i18n', () => ( {
43+
__: ( str ) => str,
44+
} ) );
45+
46+
jest.mock( '../../../assets/src/js/bindings/constants', () => ( {
47+
BINDING_SOURCE: 'acf/field',
48+
} ) );
49+
50+
jest.mock( '../../../assets/src/js/bindings/utils', () => ( {
51+
getBindableAttributes: jest.fn( () => [] ),
52+
getFilteredFieldOptions: jest.fn( () => [] ),
53+
canUseUnifiedBinding: jest.fn( () => false ),
54+
fieldsToOptions: jest.fn( () => [] ),
55+
} ) );
56+
57+
jest.mock( '../../../assets/src/js/bindings/hooks', () => ( {
58+
useSiteEditorContext: jest.fn( () => ( {
59+
isSiteEditor: false,
60+
templatePostType: null,
61+
} ) ),
62+
usePostEditorFields: jest.fn( () => ( {} ) ),
63+
useSiteEditorFields: jest.fn( () => ( { fields: {}, isLoading: false } ) ),
64+
useBoundFields: jest.fn( () => ( {
65+
boundFields: {},
66+
setBoundFields: jest.fn(),
67+
} ) ),
68+
} ) );
69+
70+
describe( 'Block Editor Module', () => {
71+
beforeEach( () => {
72+
jest.clearAllMocks();
73+
} );
74+
75+
describe( 'Filter Registration', () => {
76+
it( 'should register the editor.BlockEdit filter on module load', () => {
77+
jest.isolateModules( () => {
78+
require( '../../../assets/src/js/bindings/block-editor' );
79+
} );
80+
81+
expect( addFilter ).toHaveBeenCalledWith(
82+
'editor.BlockEdit',
83+
'secure-custom-fields/with-custom-controls',
84+
expect.any( Function )
85+
);
86+
} );
87+
88+
it( 'should register filter with correct hook name', () => {
89+
jest.isolateModules( () => {
90+
require( '../../../assets/src/js/bindings/block-editor' );
91+
} );
92+
93+
const [ hookName ] = addFilter.mock.calls[ 0 ];
94+
expect( hookName ).toBe( 'editor.BlockEdit' );
95+
} );
96+
97+
it( 'should register filter with correct namespace', () => {
98+
jest.isolateModules( () => {
99+
require( '../../../assets/src/js/bindings/block-editor' );
100+
} );
101+
102+
const [ , namespace ] = addFilter.mock.calls[ 0 ];
103+
expect( namespace ).toBe(
104+
'secure-custom-fields/with-custom-controls'
105+
);
106+
} );
107+
108+
it( 'should register filter with a function callback', () => {
109+
jest.isolateModules( () => {
110+
require( '../../../assets/src/js/bindings/block-editor' );
111+
} );
112+
113+
const [ , , callback ] = addFilter.mock.calls[ 0 ];
114+
expect( typeof callback ).toBe( 'function' );
115+
} );
116+
117+
it( 'should only register the filter once', () => {
118+
jest.isolateModules( () => {
119+
require( '../../../assets/src/js/bindings/block-editor' );
120+
} );
121+
122+
expect( addFilter ).toHaveBeenCalledTimes( 1 );
123+
} );
124+
} );
125+
126+
describe( 'HOC Creation', () => {
127+
it( 'should create a higher order component', () => {
128+
const {
129+
createHigherOrderComponent,
130+
} = require( '@wordpress/compose' );
131+
132+
jest.isolateModules( () => {
133+
require( '../../../assets/src/js/bindings/block-editor' );
134+
} );
135+
136+
expect( createHigherOrderComponent ).toHaveBeenCalledWith(
137+
expect.any( Function ),
138+
'withCustomControls'
139+
);
140+
} );
141+
142+
it( 'should name the HOC "withCustomControls"', () => {
143+
const {
144+
createHigherOrderComponent,
145+
} = require( '@wordpress/compose' );
146+
147+
jest.isolateModules( () => {
148+
require( '../../../assets/src/js/bindings/block-editor' );
149+
} );
150+
151+
const [ , hocName ] = createHigherOrderComponent.mock.calls[ 0 ];
152+
expect( hocName ).toBe( 'withCustomControls' );
153+
} );
154+
} );
155+
156+
describe( 'Filter Callback', () => {
157+
let filterCallback;
158+
159+
beforeEach( () => {
160+
jest.isolateModules( () => {
161+
require( '../../../assets/src/js/bindings/block-editor' );
162+
} );
163+
filterCallback = addFilter.mock.calls[ 0 ][ 2 ];
164+
} );
165+
166+
it( 'should return a function when passed a BlockEdit component', () => {
167+
const MockBlockEdit = () => null;
168+
const result = filterCallback( MockBlockEdit );
169+
170+
expect( typeof result ).toBe( 'function' );
171+
} );
172+
173+
it( 'should wrap different BlockEdit components independently', () => {
174+
const BlockEdit1 = () => 'edit1';
175+
const BlockEdit2 = () => 'edit2';
176+
177+
const wrapped1 = filterCallback( BlockEdit1 );
178+
const wrapped2 = filterCallback( BlockEdit2 );
179+
180+
expect( wrapped1 ).not.toBe( wrapped2 );
181+
} );
182+
} );
183+
} );
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Unit tests for block binding constants
3+
*
4+
* These tests validate the configuration structure that determines
5+
* which SCF field types can bind to which block attributes.
6+
*/
7+
8+
import {
9+
BLOCK_BINDINGS_CONFIG,
10+
BINDING_SOURCE,
11+
} from '../../../assets/src/js/bindings/constants';
12+
13+
describe( 'Block Binding Constants', () => {
14+
describe( 'BINDING_SOURCE', () => {
15+
it( 'should be acf/field following WordPress binding source convention', () => {
16+
expect( BINDING_SOURCE ).toBe( 'acf/field' );
17+
expect( BINDING_SOURCE ).toMatch( /^[a-z]+\/[a-z]+$/ );
18+
} );
19+
} );
20+
21+
describe( 'BLOCK_BINDINGS_CONFIG', () => {
22+
it( 'should define bindings for core blocks', () => {
23+
expect( Object.keys( BLOCK_BINDINGS_CONFIG ) ).toEqual( [
24+
'core/paragraph',
25+
'core/heading',
26+
'core/image',
27+
'core/button',
28+
] );
29+
} );
30+
31+
it( 'should map paragraph content to text-like field types', () => {
32+
expect( BLOCK_BINDINGS_CONFIG[ 'core/paragraph' ].content ).toEqual(
33+
[ 'text', 'textarea', 'date_picker', 'number', 'range' ]
34+
);
35+
} );
36+
37+
it( 'should map heading content same as paragraph', () => {
38+
expect( BLOCK_BINDINGS_CONFIG[ 'core/heading' ].content ).toEqual(
39+
BLOCK_BINDINGS_CONFIG[ 'core/paragraph' ].content
40+
);
41+
} );
42+
43+
it( 'should map all image attributes to image field type only', () => {
44+
const imageConfig = BLOCK_BINDINGS_CONFIG[ 'core/image' ];
45+
expect( imageConfig ).toEqual( {
46+
id: [ 'image' ],
47+
url: [ 'image' ],
48+
title: [ 'image' ],
49+
alt: [ 'image' ],
50+
} );
51+
} );
52+
53+
it( 'should map button attributes to appropriate field types', () => {
54+
const buttonConfig = BLOCK_BINDINGS_CONFIG[ 'core/button' ];
55+
expect( buttonConfig.url ).toEqual( [ 'url' ] );
56+
expect( buttonConfig.text ).toEqual( [
57+
'text',
58+
'checkbox',
59+
'select',
60+
'date_picker',
61+
] );
62+
expect( buttonConfig.linkTarget ).toEqual( buttonConfig.rel );
63+
} );
64+
65+
it( 'should have valid structure with non-empty field type arrays', () => {
66+
Object.entries( BLOCK_BINDINGS_CONFIG ).forEach(
67+
( [ blockName, blockConfig ] ) => {
68+
expect( blockName ).toMatch( /^core\// );
69+
Object.entries( blockConfig ).forEach(
70+
( [ , fieldTypes ] ) => {
71+
expect( Array.isArray( fieldTypes ) ).toBe( true );
72+
expect( fieldTypes.length ).toBeGreaterThan( 0 );
73+
fieldTypes.forEach( ( type ) => {
74+
expect( typeof type ).toBe( 'string' );
75+
expect( type ).toMatch( /^[a-z_]+$/ );
76+
} );
77+
}
78+
);
79+
}
80+
);
81+
} );
82+
} );
83+
} );

0 commit comments

Comments
 (0)