Skip to content

Commit 10d9fd5

Browse files
committed
test: update tests for client-only abilities API changes
1 parent b3e8195 commit 10d9fd5

File tree

8 files changed

+69
-145
lines changed

8 files changed

+69
-145
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@wordpress/api-fetch": "^7.30.0",
5454
"@wordpress/core-data": "^7.30.0",
5555
"@wordpress/data": "^10.30.0",
56+
"ajv": "^8.12.0",
5657
"ajv-draft-04": "^1.0.0",
5758
"ajv-formats": "^3.0.1"
5859
},

packages/client/src/__tests__/api.test.ts

Lines changed: 41 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@
55
/**
66
* WordPress dependencies
77
*/
8-
import { dispatch, resolveSelect } from '@wordpress/data';
8+
import { dispatch, select, resolveSelect } from '@wordpress/data';
99
import apiFetch from '@wordpress/api-fetch';
1010

1111
/**
1212
* Internal dependencies
1313
*/
1414
import {
15-
listAbilities,
15+
getAbilities,
1616
getAbility,
1717
registerAbility,
1818
unregisterAbility,
1919
executeAbility,
2020
} from '../api';
2121
import { store } from '../store';
22-
import type { Ability, ClientAbility, ServerAbility } from '../types';
22+
import type { Ability } from '../types';
2323

2424
// Mock WordPress dependencies
2525
jest.mock('@wordpress/data', () => ({
2626
dispatch: jest.fn(),
27+
select: jest.fn(),
2728
resolveSelect: jest.fn(),
2829
}));
2930

@@ -38,22 +39,20 @@ describe('API functions', () => {
3839
jest.clearAllMocks();
3940
});
4041

41-
describe('listAbilities', () => {
42+
describe('getAbilities', () => {
4243
it('should resolve and return all abilities from the store', async () => {
4344
const mockAbilities: Ability[] = [
4445
{
4546
name: 'test/ability1',
4647
label: 'Test Ability 1',
4748
description: 'First test ability',
48-
location: 'server',
4949
input_schema: { type: 'object' },
5050
output_schema: { type: 'object' },
5151
},
5252
{
5353
name: 'test/ability2',
5454
label: 'Test Ability 2',
5555
description: 'Second test ability',
56-
location: 'client',
5756
input_schema: { type: 'object' },
5857
output_schema: { type: 'object' },
5958
},
@@ -64,7 +63,7 @@ describe('API functions', () => {
6463
getAbilities: mockGetAbilities,
6564
});
6665

67-
const result = await listAbilities();
66+
const result = await getAbilities();
6867

6968
expect(resolveSelect).toHaveBeenCalledWith(store);
7069
expect(mockGetAbilities).toHaveBeenCalled();
@@ -78,7 +77,6 @@ describe('API functions', () => {
7877
name: 'test/ability',
7978
label: 'Test Ability',
8079
description: 'Test ability description',
81-
location: 'server',
8280
input_schema: { type: 'object' },
8381
output_schema: { type: 'object' },
8482
};
@@ -115,11 +113,15 @@ describe('API functions', () => {
115113
registerAbility: mockRegisterAbility,
116114
});
117115

116+
// Mock select to return no existing ability
117+
(select as jest.Mock).mockReturnValue({
118+
getAbility: jest.fn().mockReturnValue(null),
119+
});
120+
118121
const ability = {
119122
name: 'test/client-ability',
120123
label: 'Client Ability',
121124
description: 'Test client ability',
122-
location: 'client' as const,
123125
input_schema: { type: 'object' },
124126
output_schema: { type: 'object' },
125127
callback: jest.fn(),
@@ -131,87 +133,68 @@ describe('API functions', () => {
131133
expect(mockRegisterAbility).toHaveBeenCalledWith(ability);
132134
});
133135

134-
it('should throw error for server abilities', () => {
136+
it('should throw error for abilities without callback', () => {
135137
const mockRegisterAbility = jest.fn();
136138
(dispatch as jest.Mock).mockReturnValue({
137139
registerAbility: mockRegisterAbility,
138140
});
139141

140-
const ability: ServerAbility = {
141-
name: 'test/server-ability',
142-
label: 'Server Ability',
143-
description: 'Test server ability',
144-
location: 'server',
145-
input_schema: { type: 'object' },
146-
output_schema: { type: 'object' },
147-
};
148-
149-
// Use type assertion to bypass TypeScript check for testing runtime validation
150-
expect(() =>
151-
registerAbility(ability as unknown as ClientAbility)
152-
).toThrow(
153-
'Server abilities cannot be registered via registerAbility'
154-
);
155-
});
156-
157-
it('should throw error for client abilities without callback', () => {
158-
const mockRegisterAbility = jest.fn();
159-
(dispatch as jest.Mock).mockReturnValue({
160-
registerAbility: mockRegisterAbility,
142+
// Mock select to return no existing ability
143+
(select as jest.Mock).mockReturnValue({
144+
getAbility: jest.fn().mockReturnValue(null),
161145
});
162146

163147
// Create an incomplete client ability for testing runtime validation
164148
const ability = {
165149
name: 'test/client-ability',
166150
label: 'Client Ability',
167151
description: 'Test client ability',
168-
location: 'client' as const,
169152
input_schema: { type: 'object' },
170153
output_schema: { type: 'object' },
171154
// Missing callback property
172155
};
173156

174157
// Use type assertion to bypass TypeScript check
175158
expect(() =>
176-
registerAbility(ability as unknown as ClientAbility)
177-
).toThrow('Client abilities must include a callback function');
159+
registerAbility(ability as unknown as Ability)
160+
).toThrow('Abilities registered on the client require a callback function');
178161
});
179162

180163
it('should throw error for ability without name', () => {
181-
const ability: Partial<ClientAbility> = {
164+
const ability: Partial<Ability> = {
182165
label: 'Test Ability',
183166
description: 'Test ability',
184167
callback: jest.fn(),
185168
// Missing name property
186169
};
187170

188-
expect(() => registerAbility(ability as ClientAbility)).toThrow(
171+
expect(() => registerAbility(ability as Ability)).toThrow(
189172
'Ability name is required'
190173
);
191174
});
192175

193176
it('should throw error for ability without label', () => {
194-
const ability: Partial<ClientAbility> = {
177+
const ability: Partial<Ability> = {
195178
name: 'test/ability',
196179
description: 'Test ability',
197180
callback: jest.fn(),
198181
// Missing label property
199182
};
200183

201-
expect(() => registerAbility(ability as ClientAbility)).toThrow(
184+
expect(() => registerAbility(ability as Ability)).toThrow(
202185
'Ability label is required'
203186
);
204187
});
205188

206189
it('should throw error for ability without description', () => {
207-
const ability: Partial<ClientAbility> = {
190+
const ability: Partial<Ability> = {
208191
name: 'test/ability',
209192
label: 'Test Ability',
210193
callback: jest.fn(),
211194
// Missing description property
212195
};
213196

214-
expect(() => registerAbility(ability as ClientAbility)).toThrow(
197+
expect(() => registerAbility(ability as Ability)).toThrow(
215198
'Ability description is required'
216199
);
217200
});
@@ -237,7 +220,6 @@ describe('API functions', () => {
237220
name: 'test/server-ability',
238221
label: 'Server Ability',
239222
description: 'Test server ability',
240-
location: 'server',
241223
input_schema: {
242224
type: 'object',
243225
properties: {
@@ -274,7 +256,6 @@ describe('API functions', () => {
274256
name: 'test/client-ability',
275257
label: 'Client Ability',
276258
description: 'Test client ability',
277-
location: 'client',
278259
input_schema: { type: 'object' },
279260
output_schema: { type: 'object' },
280261
callback: mockCallback,
@@ -311,7 +292,6 @@ describe('API functions', () => {
311292
name: 'test/client-ability',
312293
label: 'Client Ability',
313294
description: 'Test client ability',
314-
location: 'client',
315295
input_schema: {
316296
type: 'object',
317297
properties: {
@@ -338,7 +318,6 @@ describe('API functions', () => {
338318
name: 'test/resource',
339319
label: 'Resource Ability',
340320
description: 'Test resource ability',
341-
location: 'server',
342321
meta: { type: 'resource' },
343322
input_schema: {
344323
type: 'object',
@@ -373,7 +352,6 @@ describe('API functions', () => {
373352
name: 'test/resource',
374353
label: 'Resource Ability',
375354
description: 'Test resource ability',
376-
location: 'server',
377355
meta: { type: 'resource' },
378356
input_schema: { type: 'object' },
379357
output_schema: { type: 'object' },
@@ -390,7 +368,7 @@ describe('API functions', () => {
390368
const result = await executeAbility('test/resource', {});
391369

392370
expect(apiFetch).toHaveBeenCalledWith({
393-
path: '/wp/v2/abilities/test/resource/run',
371+
path: '/wp/v2/abilities/test/resource/run?',
394372
method: 'GET',
395373
});
396374
expect(result).toEqual(mockResponse);
@@ -407,7 +385,6 @@ describe('API functions', () => {
407385
name: 'test/client-ability',
408386
label: 'Client Ability',
409387
description: 'Test client ability',
410-
location: 'client',
411388
input_schema: { type: 'object' },
412389
output_schema: { type: 'object' },
413390
callback: mockCallback,
@@ -440,7 +417,6 @@ describe('API functions', () => {
440417
name: 'test/server-ability',
441418
label: 'Server Ability',
442419
description: 'Test server ability',
443-
location: 'server',
444420
input_schema: { type: 'object' },
445421
output_schema: { type: 'object' },
446422
};
@@ -464,27 +440,32 @@ describe('API functions', () => {
464440
consoleErrorSpy.mockRestore();
465441
});
466442

467-
it('should throw error when client ability is missing callback during execution', async () => {
443+
it('should execute ability without callback as server ability', async () => {
468444
const mockAbility: Ability = {
469-
name: 'test/client-ability',
470-
label: 'Client Ability',
471-
description: 'Test client ability',
472-
location: 'client',
445+
name: 'test/ability',
446+
label: 'Test Ability',
447+
description: 'Test ability without callback',
473448
input_schema: { type: 'object' },
474449
output_schema: { type: 'object' },
475-
// Intentionally missing callback to test the edge case
476-
} as Ability;
450+
// No callback - should execute as server ability
451+
};
477452

478453
const mockGetAbility = jest.fn().mockResolvedValue(mockAbility);
479454
(resolveSelect as jest.Mock).mockReturnValue({
480455
getAbility: mockGetAbility,
481456
});
482457

483-
await expect(
484-
executeAbility('test/client-ability', {})
485-
).rejects.toThrow(
486-
'Client ability test/client-ability is missing callback function'
487-
);
458+
const mockResponse = { success: true };
459+
(apiFetch as unknown as jest.Mock).mockResolvedValue(mockResponse);
460+
461+
const result = await executeAbility('test/ability', { data: 'test' });
462+
463+
expect(apiFetch).toHaveBeenCalledWith({
464+
path: '/wp/v2/abilities/test/ability/run',
465+
method: 'POST',
466+
data: { input: { data: 'test' } },
467+
});
468+
expect(result).toEqual(mockResponse);
488469
});
489470

490471
it('should validate output for client abilities', async () => {
@@ -495,7 +476,6 @@ describe('API functions', () => {
495476
name: 'test/client-ability',
496477
label: 'Client Ability',
497478
description: 'Test client ability',
498-
location: 'client',
499479
input_schema: { type: 'object' },
500480
output_schema: {
501481
type: 'object',

packages/client/src/api.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,12 @@ export function registerAbility(ability: Ability): void {
7272
throw new Error(__('Ability description is required'));
7373
}
7474

75-
// Runtime check for JavaScript consumers who might pass server abilities
76-
// TypeScript users are protected by the ClientAbility type
77-
const anyAbility = ability as any;
78-
if (anyAbility.location === 'server') {
75+
if (!ability.callback || typeof ability.callback !== 'function') {
7976
throw new Error(
80-
'Server abilities cannot be registered via registerAbility'
77+
__('Abilities registered on the client require a callback function')
8178
);
8279
}
8380

84-
if (!ability.callback || typeof ability.callback !== 'function') {
85-
throw new Error(__('Abilities registered on the client require a callback function'));
86-
}
87-
8881
// Check if ability is already registered
8982
const existingAbility = select(store).getAbility(ability.name);
9083
if (existingAbility) {
@@ -271,7 +264,6 @@ export async function executeAbility(
271264
);
272265
}
273266

274-
275267
if (ability.callback) {
276268
return executeClientAbility(ability, input);
277269
}

packages/client/src/store/__tests__/actions.test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,13 @@ describe('Store Actions', () => {
2525
name: 'test/ability1',
2626
label: 'Test Ability 1',
2727
description: 'First test ability',
28-
location: 'server',
2928
input_schema: { type: 'object' },
3029
output_schema: { type: 'object' },
3130
},
3231
{
3332
name: 'test/ability2',
3433
label: 'Test Ability 2',
3534
description: 'Second test ability',
36-
location: 'client',
3735
input_schema: { type: 'object' },
3836
output_schema: { type: 'object' },
3937
},
@@ -64,7 +62,6 @@ describe('Store Actions', () => {
6462
name: 'test/ability',
6563
label: 'Test Ability',
6664
description: 'Test ability description',
67-
location: 'client',
6865
input_schema: {
6966
type: 'object',
7067
properties: {
@@ -93,7 +90,6 @@ describe('Store Actions', () => {
9390
name: 'test/server-ability',
9491
label: 'Server Ability',
9592
description: 'Server-side ability',
96-
location: 'server',
9793
input_schema: { type: 'object' },
9894
output_schema: { type: 'object' },
9995
};

0 commit comments

Comments
 (0)