Skip to content

Commit 03ce918

Browse files
committed
add unit tests for widget-types store
cover registration validation, duplicate prevention, and filter application
1 parent dff0f76 commit 03ce918

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { dispatch, select } from '@wordpress/data';
5+
import { addFilter, removeFilter } from '@wordpress/hooks';
6+
7+
/**
8+
* Internal dependencies
9+
*/
10+
import { store } from '../';
11+
import { unregisterWidgetType } from '../actions';
12+
import type { WidgetName, WidgetType } from '../../types';
13+
14+
const baseSettings = ( name: WidgetName ): Partial< WidgetType > => ( {
15+
name,
16+
apiVersion: 1,
17+
title: 'Test Widget',
18+
renderModule: 'test/widget/render',
19+
} );
20+
21+
describe( 'widget-types actions', () => {
22+
beforeEach( () => {
23+
const types = select( store ).getWidgetTypes();
24+
for ( const type of types ) {
25+
dispatch( store ).unregisterWidgetType( type.name );
26+
}
27+
} );
28+
29+
describe( 'registerWidgetType', () => {
30+
it( 'registers a valid widget type', () => {
31+
dispatch( store ).registerWidgetType(
32+
'test/widget',
33+
baseSettings( 'test/widget' )
34+
);
35+
36+
expect(
37+
select( store ).getWidgetType( 'test/widget' )
38+
).toMatchObject( {
39+
name: 'test/widget',
40+
title: 'Test Widget',
41+
renderModule: 'test/widget/render',
42+
} );
43+
} );
44+
45+
it( 'preserves the rich shape (apiVersion, attributes, example)', () => {
46+
dispatch( store ).registerWidgetType( 'test/widget', {
47+
...baseSettings( 'test/widget' ),
48+
attributes: [
49+
{ id: 'count', type: 'integer', label: 'Count' },
50+
],
51+
example: { attributes: { count: 5 } },
52+
} );
53+
54+
const widget = select( store ).getWidgetType( 'test/widget' );
55+
expect( widget?.apiVersion ).toBe( 1 );
56+
expect( widget?.attributes ).toHaveLength( 1 );
57+
expect( widget?.example ).toEqual( {
58+
attributes: { count: 5 },
59+
} );
60+
} );
61+
62+
it( 'warns when name is not a string', () => {
63+
dispatch( store ).registerWidgetType(
64+
// @ts-expect-error testing runtime validation
65+
123,
66+
baseSettings( 'test/widget' )
67+
);
68+
69+
expect( console ).toHaveWarnedWith(
70+
'Widget type names must be strings.'
71+
);
72+
} );
73+
74+
it( 'warns when name lacks the namespace prefix', () => {
75+
dispatch( store ).registerWidgetType(
76+
'no-prefix' as WidgetName,
77+
baseSettings( 'no-prefix' as WidgetName )
78+
);
79+
80+
expect( console ).toHaveWarnedWith(
81+
'Widget type names must contain a namespace prefix, e.g. core/on-this-day'
82+
);
83+
} );
84+
85+
it( 'warns when title is missing', () => {
86+
dispatch( store ).registerWidgetType( 'test/widget', {
87+
name: 'test/widget',
88+
apiVersion: 1,
89+
renderModule: 'test/widget/render',
90+
} );
91+
92+
expect( console ).toHaveWarnedWith(
93+
'The widget "test/widget" must have a title.'
94+
);
95+
} );
96+
97+
it( 'warns when renderModule is missing', () => {
98+
dispatch( store ).registerWidgetType( 'test/widget', {
99+
name: 'test/widget',
100+
apiVersion: 1,
101+
title: 'Test',
102+
} );
103+
104+
expect( console ).toHaveWarnedWith(
105+
'The widget "test/widget" must have a renderModule.'
106+
);
107+
} );
108+
109+
it( 'warns on duplicate registration and does not overwrite', () => {
110+
dispatch( store ).registerWidgetType( 'test/widget', {
111+
...baseSettings( 'test/widget' ),
112+
title: 'First',
113+
} );
114+
dispatch( store ).registerWidgetType( 'test/widget', {
115+
...baseSettings( 'test/widget' ),
116+
title: 'Second',
117+
} );
118+
119+
expect( console ).toHaveWarnedWith(
120+
'Widget type "test/widget" is already registered.'
121+
);
122+
expect(
123+
select( store ).getWidgetType( 'test/widget' )?.title
124+
).toBe( 'First' );
125+
} );
126+
127+
it( 'applies the widgets.registerWidgetType filter before storing', () => {
128+
addFilter(
129+
'widgets.registerWidgetType',
130+
'test/icon-injector',
131+
( settings: Partial< WidgetType > ) => ( {
132+
...settings,
133+
icon: 'star',
134+
} )
135+
);
136+
137+
dispatch( store ).registerWidgetType(
138+
'test/widget',
139+
baseSettings( 'test/widget' )
140+
);
141+
142+
expect( select( store ).getWidgetType( 'test/widget' )?.icon ).toBe(
143+
'star'
144+
);
145+
146+
removeFilter( 'widgets.registerWidgetType', 'test/icon-injector' );
147+
} );
148+
} );
149+
150+
describe( 'unregisterWidgetType', () => {
151+
it( 'returns the REMOVE_WIDGET_TYPE action', () => {
152+
expect( unregisterWidgetType( 'test/widget' ) ).toEqual( {
153+
type: 'REMOVE_WIDGET_TYPE',
154+
name: 'test/widget',
155+
} );
156+
} );
157+
158+
it( 'removes a registered widget from the store', () => {
159+
dispatch( store ).registerWidgetType(
160+
'test/widget',
161+
baseSettings( 'test/widget' )
162+
);
163+
expect(
164+
select( store ).getWidgetType( 'test/widget' )
165+
).toBeDefined();
166+
167+
dispatch( store ).unregisterWidgetType( 'test/widget' );
168+
expect(
169+
select( store ).getWidgetType( 'test/widget' )
170+
).toBeUndefined();
171+
} );
172+
} );
173+
174+
describe( 'getWidgetTypes', () => {
175+
it( 'returns all registered widgets as an array', () => {
176+
dispatch( store ).registerWidgetType(
177+
'test/one',
178+
baseSettings( 'test/one' )
179+
);
180+
dispatch( store ).registerWidgetType(
181+
'test/two',
182+
baseSettings( 'test/two' )
183+
);
184+
185+
const all = select( store ).getWidgetTypes();
186+
expect( all ).toHaveLength( 2 );
187+
expect( all.map( ( t ) => t.name ).sort() ).toEqual( [
188+
'test/one',
189+
'test/two',
190+
] );
191+
} );
192+
193+
it( 'returns an empty array when none are registered', () => {
194+
expect( select( store ).getWidgetTypes() ).toEqual( [] );
195+
} );
196+
} );
197+
} );

0 commit comments

Comments
 (0)