@wordpress/abilities 0.2.1-next.8b30e05b0.0 → 0.3.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.
- package/CHANGELOG.md +2 -0
- package/README.md +32 -40
- package/build/api.js +19 -66
- package/build/api.js.map +3 -3
- package/build/index.js.map +2 -2
- package/build/store/actions.js +41 -31
- package/build/store/actions.js.map +2 -2
- package/build/store/constants.js +6 -15
- package/build/store/constants.js.map +2 -2
- package/build/store/index.js +1 -27
- package/build/store/index.js.map +3 -3
- package/build/store/reducer.js +2 -22
- package/build/store/reducer.js.map +2 -2
- package/build/store/selectors.js +1 -1
- package/build/store/selectors.js.map +2 -2
- package/build/store/types.js +19 -0
- package/build/store/types.js.map +7 -0
- package/build/types.js.map +1 -1
- package/build-module/api.js +20 -57
- package/build-module/api.js.map +2 -2
- package/build-module/index.js.map +2 -2
- package/build-module/store/actions.js +43 -32
- package/build-module/store/actions.js.map +2 -2
- package/build-module/store/constants.js +4 -10
- package/build-module/store/constants.js.map +2 -2
- package/build-module/store/index.js +3 -34
- package/build-module/store/index.js.map +2 -2
- package/build-module/store/reducer.js +2 -24
- package/build-module/store/reducer.js.map +2 -2
- package/build-module/store/selectors.js +1 -1
- package/build-module/store/selectors.js.map +2 -2
- package/build-module/store/types.js +1 -0
- package/build-module/store/types.js.map +7 -0
- package/build-types/api.d.ts +16 -23
- package/build-types/api.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -1
- package/build-types/index.d.ts.map +1 -1
- package/build-types/store/actions.d.ts +5 -27
- package/build-types/store/actions.d.ts.map +1 -1
- package/build-types/store/constants.d.ts +2 -5
- package/build-types/store/constants.d.ts.map +1 -1
- package/build-types/store/index.d.ts.map +1 -1
- package/build-types/store/reducer.d.ts +21 -11
- package/build-types/store/reducer.d.ts.map +1 -1
- package/build-types/store/selectors.d.ts +2 -1
- package/build-types/store/selectors.d.ts.map +1 -1
- package/build-types/store/types.d.ts +18 -0
- package/build-types/store/types.d.ts.map +1 -0
- package/build-types/types.d.ts +14 -24
- package/build-types/types.d.ts.map +1 -1
- package/package.json +4 -7
- package/src/api.ts +43 -123
- package/src/index.ts +0 -1
- package/src/store/actions.ts +61 -46
- package/src/store/constants.ts +4 -5
- package/src/store/index.ts +2 -34
- package/src/store/reducer.ts +27 -33
- package/src/store/selectors.ts +3 -7
- package/src/store/tests/actions.test.ts +100 -244
- package/src/store/tests/reducer.test.ts +0 -451
- package/src/store/tests/selectors.test.ts +1 -1
- package/src/store/types.ts +19 -0
- package/src/tests/api.test.ts +112 -158
- package/src/types.ts +14 -26
- package/build/store/resolvers.js +0 -107
- package/build/store/resolvers.js.map +0 -7
- package/build-module/store/resolvers.js +0 -84
- package/build-module/store/resolvers.js.map +0 -7
- package/build-types/store/resolvers.d.ts +0 -47
- package/build-types/store/resolvers.d.ts.map +0 -1
- package/src/store/resolvers.ts +0 -151
- package/src/store/tests/resolvers.test.ts +0 -642
package/src/tests/api.test.ts
CHANGED
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* WordPress dependencies
|
|
7
7
|
*/
|
|
8
|
-
import { dispatch, select
|
|
9
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
8
|
+
import { dispatch, select } from '@wordpress/data';
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Internal dependencies
|
|
@@ -23,15 +22,11 @@ import {
|
|
|
23
22
|
import { store } from '../store';
|
|
24
23
|
import type { Ability, AbilityCategory } from '../types';
|
|
25
24
|
|
|
26
|
-
// Mock WordPress dependencies
|
|
27
25
|
jest.mock( '@wordpress/data', () => ( {
|
|
28
|
-
dispatch: jest.fn(),
|
|
29
26
|
select: jest.fn(),
|
|
30
|
-
|
|
27
|
+
dispatch: jest.fn(),
|
|
31
28
|
} ) );
|
|
32
29
|
|
|
33
|
-
jest.mock( '@wordpress/api-fetch' );
|
|
34
|
-
|
|
35
30
|
jest.mock( '../store', () => ( {
|
|
36
31
|
store: 'abilities-api/store',
|
|
37
32
|
} ) );
|
|
@@ -42,7 +37,7 @@ describe( 'API functions', () => {
|
|
|
42
37
|
} );
|
|
43
38
|
|
|
44
39
|
describe( 'getAbilities', () => {
|
|
45
|
-
it( 'should
|
|
40
|
+
it( 'should return all abilities from the store', () => {
|
|
46
41
|
const mockAbilities: Ability[] = [
|
|
47
42
|
{
|
|
48
43
|
name: 'test/ability1',
|
|
@@ -62,21 +57,19 @@ describe( 'API functions', () => {
|
|
|
62
57
|
},
|
|
63
58
|
];
|
|
64
59
|
|
|
65
|
-
const mockGetAbilities = jest
|
|
66
|
-
|
|
67
|
-
.mockResolvedValue( mockAbilities );
|
|
68
|
-
( resolveSelect as jest.Mock ).mockReturnValue( {
|
|
60
|
+
const mockGetAbilities = jest.fn().mockReturnValue( mockAbilities );
|
|
61
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
69
62
|
getAbilities: mockGetAbilities,
|
|
70
63
|
} );
|
|
71
64
|
|
|
72
|
-
const result =
|
|
65
|
+
const result = getAbilities();
|
|
73
66
|
|
|
74
|
-
expect(
|
|
67
|
+
expect( select ).toHaveBeenCalledWith( store );
|
|
75
68
|
expect( mockGetAbilities ).toHaveBeenCalled();
|
|
76
69
|
expect( result ).toEqual( mockAbilities );
|
|
77
70
|
} );
|
|
78
71
|
|
|
79
|
-
it( 'should pass category parameter to store when filtering',
|
|
72
|
+
it( 'should pass category parameter to store when filtering', () => {
|
|
80
73
|
const mockAbilities: Ability[] = [
|
|
81
74
|
{
|
|
82
75
|
name: 'test/ability1',
|
|
@@ -96,16 +89,14 @@ describe( 'API functions', () => {
|
|
|
96
89
|
},
|
|
97
90
|
];
|
|
98
91
|
|
|
99
|
-
const mockGetAbilities = jest
|
|
100
|
-
|
|
101
|
-
.mockResolvedValue( mockAbilities );
|
|
102
|
-
( resolveSelect as jest.Mock ).mockReturnValue( {
|
|
92
|
+
const mockGetAbilities = jest.fn().mockReturnValue( mockAbilities );
|
|
93
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
103
94
|
getAbilities: mockGetAbilities,
|
|
104
95
|
} );
|
|
105
96
|
|
|
106
|
-
const result =
|
|
97
|
+
const result = getAbilities( { category: 'data-retrieval' } );
|
|
107
98
|
|
|
108
|
-
expect(
|
|
99
|
+
expect( select ).toHaveBeenCalledWith( store );
|
|
109
100
|
expect( mockGetAbilities ).toHaveBeenCalledWith( {
|
|
110
101
|
category: 'data-retrieval',
|
|
111
102
|
} );
|
|
@@ -114,7 +105,7 @@ describe( 'API functions', () => {
|
|
|
114
105
|
} );
|
|
115
106
|
|
|
116
107
|
describe( 'getAbility', () => {
|
|
117
|
-
it( 'should return a specific ability by name',
|
|
108
|
+
it( 'should return a specific ability by name', () => {
|
|
118
109
|
const mockAbility: Ability = {
|
|
119
110
|
name: 'test/ability',
|
|
120
111
|
label: 'Test Ability',
|
|
@@ -124,29 +115,17 @@ describe( 'API functions', () => {
|
|
|
124
115
|
output_schema: { type: 'object' },
|
|
125
116
|
};
|
|
126
117
|
|
|
127
|
-
const mockGetAbility = jest.fn().
|
|
128
|
-
(
|
|
118
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
119
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
129
120
|
getAbility: mockGetAbility,
|
|
130
121
|
} );
|
|
131
122
|
|
|
132
|
-
const result =
|
|
123
|
+
const result = getAbility( 'test/ability' );
|
|
133
124
|
|
|
134
|
-
expect(
|
|
125
|
+
expect( select ).toHaveBeenCalledWith( store );
|
|
135
126
|
expect( mockGetAbility ).toHaveBeenCalledWith( 'test/ability' );
|
|
136
127
|
expect( result ).toEqual( mockAbility );
|
|
137
128
|
} );
|
|
138
|
-
|
|
139
|
-
it( 'should return null if ability not found', async () => {
|
|
140
|
-
const mockGetAbility = jest.fn().mockResolvedValue( null );
|
|
141
|
-
( resolveSelect as jest.Mock ).mockReturnValue( {
|
|
142
|
-
getAbility: mockGetAbility,
|
|
143
|
-
} );
|
|
144
|
-
|
|
145
|
-
const result = await getAbility( 'non-existent' );
|
|
146
|
-
|
|
147
|
-
expect( mockGetAbility ).toHaveBeenCalledWith( 'non-existent' );
|
|
148
|
-
expect( result ).toBeNull();
|
|
149
|
-
} );
|
|
150
129
|
} );
|
|
151
130
|
|
|
152
131
|
describe( 'registerAbility', () => {
|
|
@@ -195,7 +174,10 @@ describe( 'API functions', () => {
|
|
|
195
174
|
} );
|
|
196
175
|
|
|
197
176
|
describe( 'executeAbility', () => {
|
|
198
|
-
it( 'should execute a server-side ability via
|
|
177
|
+
it( 'should execute a server-side ability via callback', async () => {
|
|
178
|
+
const mockServerCallback = jest
|
|
179
|
+
.fn()
|
|
180
|
+
.mockResolvedValue( { success: true, result: 'test' } );
|
|
199
181
|
const mockAbility: Ability = {
|
|
200
182
|
name: 'test/server-ability',
|
|
201
183
|
label: 'Server Ability',
|
|
@@ -209,30 +191,23 @@ describe( 'API functions', () => {
|
|
|
209
191
|
required: [ 'message' ],
|
|
210
192
|
},
|
|
211
193
|
output_schema: { type: 'object' },
|
|
194
|
+
callback: mockServerCallback,
|
|
195
|
+
meta: { annotations: { serverRegistered: true } },
|
|
212
196
|
};
|
|
213
197
|
|
|
214
|
-
const mockGetAbility = jest.fn().
|
|
215
|
-
(
|
|
198
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
199
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
216
200
|
getAbility: mockGetAbility,
|
|
217
201
|
} );
|
|
218
202
|
|
|
219
|
-
const mockResponse = { success: true, result: 'test' };
|
|
220
|
-
( apiFetch as unknown as jest.Mock ).mockResolvedValue(
|
|
221
|
-
mockResponse
|
|
222
|
-
);
|
|
223
|
-
|
|
224
203
|
const input = { message: 'Hello' };
|
|
225
204
|
const result = await executeAbility( 'test/server-ability', input );
|
|
226
205
|
|
|
227
206
|
expect( mockGetAbility ).toHaveBeenCalledWith(
|
|
228
207
|
'test/server-ability'
|
|
229
208
|
);
|
|
230
|
-
expect(
|
|
231
|
-
|
|
232
|
-
method: 'POST',
|
|
233
|
-
data: { input },
|
|
234
|
-
} );
|
|
235
|
-
expect( result ).toEqual( mockResponse );
|
|
209
|
+
expect( mockServerCallback ).toHaveBeenCalledWith( input );
|
|
210
|
+
expect( result ).toEqual( { success: true, result: 'test' } );
|
|
236
211
|
} );
|
|
237
212
|
|
|
238
213
|
it( 'should execute a client-side ability locally', async () => {
|
|
@@ -247,10 +222,11 @@ describe( 'API functions', () => {
|
|
|
247
222
|
input_schema: { type: 'object' },
|
|
248
223
|
output_schema: { type: 'object' },
|
|
249
224
|
callback: mockCallback,
|
|
225
|
+
meta: { annotations: { clientRegistered: true } },
|
|
250
226
|
};
|
|
251
227
|
|
|
252
|
-
const mockGetAbility = jest.fn().
|
|
253
|
-
(
|
|
228
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
229
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
254
230
|
getAbility: mockGetAbility,
|
|
255
231
|
} );
|
|
256
232
|
|
|
@@ -261,13 +237,12 @@ describe( 'API functions', () => {
|
|
|
261
237
|
'test/client-ability'
|
|
262
238
|
);
|
|
263
239
|
expect( mockCallback ).toHaveBeenCalledWith( input );
|
|
264
|
-
expect( apiFetch ).not.toHaveBeenCalled();
|
|
265
240
|
expect( result ).toEqual( { success: true } );
|
|
266
241
|
} );
|
|
267
242
|
|
|
268
243
|
it( 'should throw error if ability not found', async () => {
|
|
269
|
-
const mockGetAbility = jest.fn().
|
|
270
|
-
(
|
|
244
|
+
const mockGetAbility = jest.fn().mockReturnValue( null );
|
|
245
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
271
246
|
getAbility: mockGetAbility,
|
|
272
247
|
} );
|
|
273
248
|
|
|
@@ -292,10 +267,11 @@ describe( 'API functions', () => {
|
|
|
292
267
|
},
|
|
293
268
|
output_schema: { type: 'object' },
|
|
294
269
|
callback: mockCallback,
|
|
270
|
+
meta: { annotations: { clientRegistered: true } },
|
|
295
271
|
};
|
|
296
272
|
|
|
297
|
-
const mockGetAbility = jest.fn().
|
|
298
|
-
(
|
|
273
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
274
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
299
275
|
getAbility: mockGetAbility,
|
|
300
276
|
} );
|
|
301
277
|
|
|
@@ -304,7 +280,10 @@ describe( 'API functions', () => {
|
|
|
304
280
|
).rejects.toThrow( 'invalid input' );
|
|
305
281
|
} );
|
|
306
282
|
|
|
307
|
-
it( 'should execute a read-only ability
|
|
283
|
+
it( 'should execute a read-only server ability', async () => {
|
|
284
|
+
const mockServerCallback = jest
|
|
285
|
+
.fn()
|
|
286
|
+
.mockResolvedValue( { data: 'read-only data' } );
|
|
308
287
|
const mockAbility: Ability = {
|
|
309
288
|
name: 'test/read-only',
|
|
310
289
|
label: 'Read-only Ability',
|
|
@@ -319,31 +298,27 @@ describe( 'API functions', () => {
|
|
|
319
298
|
},
|
|
320
299
|
output_schema: { type: 'object' },
|
|
321
300
|
meta: {
|
|
322
|
-
annotations: { readonly: true },
|
|
301
|
+
annotations: { serverRegistered: true, readonly: true },
|
|
323
302
|
},
|
|
303
|
+
callback: mockServerCallback,
|
|
324
304
|
};
|
|
325
305
|
|
|
326
|
-
const mockGetAbility = jest.fn().
|
|
327
|
-
(
|
|
306
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
307
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
328
308
|
getAbility: mockGetAbility,
|
|
329
309
|
} );
|
|
330
310
|
|
|
331
|
-
const mockResponse = { data: 'read-only data' };
|
|
332
|
-
( apiFetch as unknown as jest.Mock ).mockResolvedValue(
|
|
333
|
-
mockResponse
|
|
334
|
-
);
|
|
335
|
-
|
|
336
311
|
const input = { id: '123', format: 'json' };
|
|
337
312
|
const result = await executeAbility( 'test/read-only', input );
|
|
338
313
|
|
|
339
|
-
expect(
|
|
340
|
-
|
|
341
|
-
method: 'GET',
|
|
342
|
-
} );
|
|
343
|
-
expect( result ).toEqual( mockResponse );
|
|
314
|
+
expect( mockServerCallback ).toHaveBeenCalledWith( input );
|
|
315
|
+
expect( result ).toEqual( { data: 'read-only data' } );
|
|
344
316
|
} );
|
|
345
317
|
|
|
346
318
|
it( 'should execute a read-only ability with empty input', async () => {
|
|
319
|
+
const mockServerCallback = jest
|
|
320
|
+
.fn()
|
|
321
|
+
.mockResolvedValue( { data: 'read-only data' } );
|
|
347
322
|
const mockAbility: Ability = {
|
|
348
323
|
name: 'test/read-only',
|
|
349
324
|
label: 'Read-only Ability',
|
|
@@ -352,30 +327,26 @@ describe( 'API functions', () => {
|
|
|
352
327
|
input_schema: { type: 'object' },
|
|
353
328
|
output_schema: { type: 'object' },
|
|
354
329
|
meta: {
|
|
355
|
-
annotations: { readonly: true },
|
|
330
|
+
annotations: { serverRegistered: true, readonly: true },
|
|
356
331
|
},
|
|
332
|
+
callback: mockServerCallback,
|
|
357
333
|
};
|
|
358
334
|
|
|
359
|
-
const mockGetAbility = jest.fn().
|
|
360
|
-
(
|
|
335
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
336
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
361
337
|
getAbility: mockGetAbility,
|
|
362
338
|
} );
|
|
363
339
|
|
|
364
|
-
const mockResponse = { data: 'read-only data' };
|
|
365
|
-
( apiFetch as unknown as jest.Mock ).mockResolvedValue(
|
|
366
|
-
mockResponse
|
|
367
|
-
);
|
|
368
|
-
|
|
369
340
|
const result = await executeAbility( 'test/read-only', {} );
|
|
370
341
|
|
|
371
|
-
expect(
|
|
372
|
-
|
|
373
|
-
method: 'GET',
|
|
374
|
-
} );
|
|
375
|
-
expect( result ).toEqual( mockResponse );
|
|
342
|
+
expect( mockServerCallback ).toHaveBeenCalledWith( {} );
|
|
343
|
+
expect( result ).toEqual( { data: 'read-only data' } );
|
|
376
344
|
} );
|
|
377
345
|
|
|
378
|
-
it( 'should execute a destructive idempotent ability
|
|
346
|
+
it( 'should execute a destructive idempotent server ability', async () => {
|
|
347
|
+
const mockServerCallback = jest
|
|
348
|
+
.fn()
|
|
349
|
+
.mockResolvedValue( 'Item deleted successfully.' );
|
|
379
350
|
const mockAbility: Ability = {
|
|
380
351
|
name: 'test/destructive',
|
|
381
352
|
label: 'Destructive Ability',
|
|
@@ -391,30 +362,24 @@ describe( 'API functions', () => {
|
|
|
391
362
|
output_schema: { type: 'string' },
|
|
392
363
|
meta: {
|
|
393
364
|
annotations: {
|
|
365
|
+
serverRegistered: true,
|
|
394
366
|
destructive: true,
|
|
395
367
|
idempotent: true,
|
|
396
368
|
},
|
|
397
369
|
},
|
|
370
|
+
callback: mockServerCallback,
|
|
398
371
|
};
|
|
399
372
|
|
|
400
|
-
const mockGetAbility = jest.fn().
|
|
401
|
-
(
|
|
373
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
374
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
402
375
|
getAbility: mockGetAbility,
|
|
403
376
|
} );
|
|
404
377
|
|
|
405
|
-
const mockResponse = 'Item deleted successfully.';
|
|
406
|
-
( apiFetch as unknown as jest.Mock ).mockResolvedValue(
|
|
407
|
-
mockResponse
|
|
408
|
-
);
|
|
409
|
-
|
|
410
378
|
const input = { id: '123', format: 'json' };
|
|
411
379
|
const result = await executeAbility( 'test/destructive', input );
|
|
412
380
|
|
|
413
|
-
expect(
|
|
414
|
-
|
|
415
|
-
method: 'DELETE',
|
|
416
|
-
} );
|
|
417
|
-
expect( result ).toEqual( mockResponse );
|
|
381
|
+
expect( mockServerCallback ).toHaveBeenCalledWith( input );
|
|
382
|
+
expect( result ).toEqual( 'Item deleted successfully.' );
|
|
418
383
|
} );
|
|
419
384
|
|
|
420
385
|
it( 'should handle errors in client ability execution', async () => {
|
|
@@ -432,10 +397,11 @@ describe( 'API functions', () => {
|
|
|
432
397
|
input_schema: { type: 'object' },
|
|
433
398
|
output_schema: { type: 'object' },
|
|
434
399
|
callback: mockCallback,
|
|
400
|
+
meta: { annotations: { clientRegistered: true } },
|
|
435
401
|
};
|
|
436
402
|
|
|
437
|
-
const mockGetAbility = jest.fn().
|
|
438
|
-
(
|
|
403
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
404
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
439
405
|
getAbility: mockGetAbility,
|
|
440
406
|
} );
|
|
441
407
|
|
|
@@ -444,7 +410,7 @@ describe( 'API functions', () => {
|
|
|
444
410
|
).rejects.toThrow( 'Execution failed' );
|
|
445
411
|
|
|
446
412
|
expect( consoleErrorSpy ).toHaveBeenCalledWith(
|
|
447
|
-
'Error executing
|
|
413
|
+
'Error executing ability test/client-ability:',
|
|
448
414
|
executionError
|
|
449
415
|
);
|
|
450
416
|
|
|
@@ -455,7 +421,10 @@ describe( 'API functions', () => {
|
|
|
455
421
|
const consoleErrorSpy = jest
|
|
456
422
|
.spyOn( console, 'error' )
|
|
457
423
|
.mockImplementation();
|
|
458
|
-
const
|
|
424
|
+
const serverError = new Error( 'Server execution failed' );
|
|
425
|
+
const mockServerCallback = jest
|
|
426
|
+
.fn()
|
|
427
|
+
.mockRejectedValue( serverError );
|
|
459
428
|
|
|
460
429
|
const mockAbility: Ability = {
|
|
461
430
|
name: 'test/server-ability',
|
|
@@ -464,28 +433,31 @@ describe( 'API functions', () => {
|
|
|
464
433
|
category: 'test-category',
|
|
465
434
|
input_schema: { type: 'object' },
|
|
466
435
|
output_schema: { type: 'object' },
|
|
436
|
+
callback: mockServerCallback,
|
|
437
|
+
meta: { annotations: { serverRegistered: true } },
|
|
467
438
|
};
|
|
468
439
|
|
|
469
|
-
const mockGetAbility = jest.fn().
|
|
470
|
-
(
|
|
440
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
441
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
471
442
|
getAbility: mockGetAbility,
|
|
472
443
|
} );
|
|
473
444
|
|
|
474
|
-
( apiFetch as unknown as jest.Mock ).mockRejectedValue( apiError );
|
|
475
|
-
|
|
476
445
|
await expect(
|
|
477
446
|
executeAbility( 'test/server-ability', {} )
|
|
478
|
-
).rejects.toThrow( '
|
|
447
|
+
).rejects.toThrow( 'Server execution failed' );
|
|
479
448
|
|
|
480
449
|
expect( consoleErrorSpy ).toHaveBeenCalledWith(
|
|
481
450
|
'Error executing ability test/server-ability:',
|
|
482
|
-
|
|
451
|
+
serverError
|
|
483
452
|
);
|
|
484
453
|
|
|
485
454
|
consoleErrorSpy.mockRestore();
|
|
486
455
|
} );
|
|
487
456
|
|
|
488
457
|
it( 'should execute ability without callback as server ability', async () => {
|
|
458
|
+
const mockServerCallback = jest
|
|
459
|
+
.fn()
|
|
460
|
+
.mockResolvedValue( { success: true } );
|
|
489
461
|
const mockAbility: Ability = {
|
|
490
462
|
name: 'test/ability',
|
|
491
463
|
label: 'Test Ability',
|
|
@@ -493,29 +465,24 @@ describe( 'API functions', () => {
|
|
|
493
465
|
category: 'test-category',
|
|
494
466
|
input_schema: { type: 'object' },
|
|
495
467
|
output_schema: { type: 'object' },
|
|
496
|
-
//
|
|
468
|
+
// Server ability - should execute without client validation
|
|
469
|
+
callback: mockServerCallback,
|
|
470
|
+
meta: { annotations: { serverRegistered: true } },
|
|
497
471
|
};
|
|
498
472
|
|
|
499
|
-
const mockGetAbility = jest.fn().
|
|
500
|
-
(
|
|
473
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
474
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
501
475
|
getAbility: mockGetAbility,
|
|
502
476
|
} );
|
|
503
477
|
|
|
504
|
-
const mockResponse = { success: true };
|
|
505
|
-
( apiFetch as unknown as jest.Mock ).mockResolvedValue(
|
|
506
|
-
mockResponse
|
|
507
|
-
);
|
|
508
|
-
|
|
509
478
|
const result = await executeAbility( 'test/ability', {
|
|
510
479
|
data: 'test',
|
|
511
480
|
} );
|
|
512
481
|
|
|
513
|
-
expect(
|
|
514
|
-
|
|
515
|
-
method: 'POST',
|
|
516
|
-
data: { input: { data: 'test' } },
|
|
482
|
+
expect( mockServerCallback ).toHaveBeenCalledWith( {
|
|
483
|
+
data: 'test',
|
|
517
484
|
} );
|
|
518
|
-
expect( result ).toEqual(
|
|
485
|
+
expect( result ).toEqual( { success: true } );
|
|
519
486
|
} );
|
|
520
487
|
|
|
521
488
|
it( 'should validate output for client abilities', async () => {
|
|
@@ -536,10 +503,11 @@ describe( 'API functions', () => {
|
|
|
536
503
|
required: [ 'result' ],
|
|
537
504
|
},
|
|
538
505
|
callback: mockCallback,
|
|
506
|
+
meta: { annotations: { clientRegistered: true } },
|
|
539
507
|
};
|
|
540
508
|
|
|
541
|
-
const mockGetAbility = jest.fn().
|
|
542
|
-
(
|
|
509
|
+
const mockGetAbility = jest.fn().mockReturnValue( mockAbility );
|
|
510
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
543
511
|
getAbility: mockGetAbility,
|
|
544
512
|
} );
|
|
545
513
|
|
|
@@ -550,7 +518,7 @@ describe( 'API functions', () => {
|
|
|
550
518
|
} );
|
|
551
519
|
|
|
552
520
|
describe( 'getAbilityCategories', () => {
|
|
553
|
-
it( 'should
|
|
521
|
+
it( 'should return all categories from the store', () => {
|
|
554
522
|
const mockCategories: AbilityCategory[] = [
|
|
555
523
|
{
|
|
556
524
|
slug: 'data-retrieval',
|
|
@@ -566,32 +534,32 @@ describe( 'API functions', () => {
|
|
|
566
534
|
|
|
567
535
|
const mockGetAbilityCategories = jest
|
|
568
536
|
.fn()
|
|
569
|
-
.
|
|
570
|
-
(
|
|
537
|
+
.mockReturnValue( mockCategories );
|
|
538
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
571
539
|
getAbilityCategories: mockGetAbilityCategories,
|
|
572
540
|
} );
|
|
573
541
|
|
|
574
|
-
const result =
|
|
542
|
+
const result = getAbilityCategories();
|
|
575
543
|
|
|
576
|
-
expect(
|
|
544
|
+
expect( select ).toHaveBeenCalledWith( store );
|
|
577
545
|
expect( mockGetAbilityCategories ).toHaveBeenCalled();
|
|
578
546
|
expect( result ).toEqual( mockCategories );
|
|
579
547
|
} );
|
|
580
548
|
|
|
581
|
-
it( 'should return empty array when no categories exist',
|
|
582
|
-
const mockGetAbilityCategories = jest.fn().
|
|
583
|
-
(
|
|
549
|
+
it( 'should return empty array when no categories exist', () => {
|
|
550
|
+
const mockGetAbilityCategories = jest.fn().mockReturnValue( [] );
|
|
551
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
584
552
|
getAbilityCategories: mockGetAbilityCategories,
|
|
585
553
|
} );
|
|
586
554
|
|
|
587
|
-
const result =
|
|
555
|
+
const result = getAbilityCategories();
|
|
588
556
|
|
|
589
557
|
expect( result ).toEqual( [] );
|
|
590
558
|
} );
|
|
591
559
|
} );
|
|
592
560
|
|
|
593
561
|
describe( 'getAbilityCategory', () => {
|
|
594
|
-
it( 'should return a specific category by slug',
|
|
562
|
+
it( 'should return a specific category by slug', () => {
|
|
595
563
|
const mockCategory: AbilityCategory = {
|
|
596
564
|
slug: 'data-retrieval',
|
|
597
565
|
label: 'Data Retrieval',
|
|
@@ -600,56 +568,42 @@ describe( 'API functions', () => {
|
|
|
600
568
|
|
|
601
569
|
const mockGetAbilityCategory = jest
|
|
602
570
|
.fn()
|
|
603
|
-
.
|
|
604
|
-
(
|
|
571
|
+
.mockReturnValue( mockCategory );
|
|
572
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
605
573
|
getAbilityCategory: mockGetAbilityCategory,
|
|
606
574
|
} );
|
|
607
575
|
|
|
608
|
-
const result =
|
|
576
|
+
const result = getAbilityCategory( 'data-retrieval' );
|
|
609
577
|
|
|
610
|
-
expect(
|
|
578
|
+
expect( select ).toHaveBeenCalledWith( store );
|
|
611
579
|
expect( mockGetAbilityCategory ).toHaveBeenCalledWith(
|
|
612
580
|
'data-retrieval'
|
|
613
581
|
);
|
|
614
582
|
expect( result ).toEqual( mockCategory );
|
|
615
583
|
} );
|
|
616
584
|
|
|
617
|
-
it( 'should
|
|
618
|
-
const mockGetAbilityCategory = jest.fn().mockResolvedValue( null );
|
|
619
|
-
( resolveSelect as jest.Mock ).mockReturnValue( {
|
|
620
|
-
getAbilityCategory: mockGetAbilityCategory,
|
|
621
|
-
} );
|
|
622
|
-
|
|
623
|
-
const result = await getAbilityCategory( 'non-existent' );
|
|
624
|
-
|
|
625
|
-
expect( mockGetAbilityCategory ).toHaveBeenCalledWith(
|
|
626
|
-
'non-existent'
|
|
627
|
-
);
|
|
628
|
-
expect( result ).toBeNull();
|
|
629
|
-
} );
|
|
630
|
-
|
|
631
|
-
it( 'should handle categories with meta', async () => {
|
|
585
|
+
it( 'should handle categories with meta', () => {
|
|
632
586
|
const mockCategory: AbilityCategory = {
|
|
633
587
|
slug: 'user-management',
|
|
634
588
|
label: 'User Management',
|
|
635
589
|
description: 'Abilities for managing users',
|
|
636
590
|
meta: {
|
|
637
|
-
|
|
591
|
+
annotations: { clientRegistered: true },
|
|
638
592
|
},
|
|
639
593
|
};
|
|
640
594
|
|
|
641
595
|
const mockGetAbilityCategory = jest
|
|
642
596
|
.fn()
|
|
643
|
-
.
|
|
644
|
-
(
|
|
597
|
+
.mockReturnValue( mockCategory );
|
|
598
|
+
( select as jest.Mock ).mockReturnValue( {
|
|
645
599
|
getAbilityCategory: mockGetAbilityCategory,
|
|
646
600
|
} );
|
|
647
601
|
|
|
648
|
-
const result =
|
|
602
|
+
const result = getAbilityCategory( 'user-management' );
|
|
649
603
|
|
|
650
604
|
expect( result ).toEqual( mockCategory );
|
|
651
605
|
expect( result?.meta ).toBeDefined();
|
|
652
|
-
expect( result?.meta?.
|
|
606
|
+
expect( result?.meta?.annotations?.clientRegistered ).toBe( true );
|
|
653
607
|
} );
|
|
654
608
|
} );
|
|
655
609
|
} );
|
package/src/types.ts
CHANGED
|
@@ -63,14 +63,13 @@ export interface Ability {
|
|
|
63
63
|
output_schema?: Record< string, any >;
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
|
-
* Callback function for
|
|
67
|
-
*
|
|
68
|
-
* If not present, the ability will be executed via REST API on the server.
|
|
66
|
+
* Callback function for ability execution.
|
|
67
|
+
* This property is required for all abilities.
|
|
69
68
|
*/
|
|
70
69
|
callback?: AbilityCallback;
|
|
71
70
|
|
|
72
71
|
/**
|
|
73
|
-
*
|
|
72
|
+
* Permission callback for abilities.
|
|
74
73
|
* Called before executing the ability to check if it's allowed.
|
|
75
74
|
* If it returns false, the ability execution will be denied.
|
|
76
75
|
*/
|
|
@@ -78,15 +77,15 @@ export interface Ability {
|
|
|
78
77
|
|
|
79
78
|
/**
|
|
80
79
|
* Metadata about the ability.
|
|
81
|
-
* @see WP_Ability::get_meta()
|
|
82
80
|
*/
|
|
83
81
|
meta?: {
|
|
84
82
|
annotations?: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
clientRegistered?: boolean;
|
|
84
|
+
serverRegistered?: boolean;
|
|
85
|
+
readonly?: boolean;
|
|
86
|
+
destructive?: boolean;
|
|
87
|
+
idempotent?: boolean;
|
|
88
88
|
};
|
|
89
|
-
[ key: string ]: any;
|
|
90
89
|
};
|
|
91
90
|
}
|
|
92
91
|
|
|
@@ -128,9 +127,13 @@ export interface AbilityCategory {
|
|
|
128
127
|
|
|
129
128
|
/**
|
|
130
129
|
* Metadata about the category.
|
|
131
|
-
* @see WP_Ability_Category::get_meta()
|
|
132
130
|
*/
|
|
133
|
-
meta?:
|
|
131
|
+
meta?: {
|
|
132
|
+
annotations?: {
|
|
133
|
+
clientRegistered?: boolean;
|
|
134
|
+
serverRegistered?: boolean;
|
|
135
|
+
};
|
|
136
|
+
};
|
|
134
137
|
}
|
|
135
138
|
|
|
136
139
|
/**
|
|
@@ -156,21 +159,6 @@ export interface AbilityCategoryArgs {
|
|
|
156
159
|
meta?: Record< string, any >;
|
|
157
160
|
}
|
|
158
161
|
|
|
159
|
-
/**
|
|
160
|
-
* The state shape for the abilities store.
|
|
161
|
-
*/
|
|
162
|
-
export interface AbilitiesState {
|
|
163
|
-
/**
|
|
164
|
-
* Map of ability names to ability objects.
|
|
165
|
-
*/
|
|
166
|
-
abilitiesByName: Record< string, Ability >;
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Map of category slugs to category objects.
|
|
170
|
-
*/
|
|
171
|
-
categoriesBySlug: Record< string, AbilityCategory >;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
162
|
/**
|
|
175
163
|
* Input parameters for ability execution.
|
|
176
164
|
* Can be any JSON-serializable value: primitive, array, object, or null.
|