airdcpp-apisocket 3.0.0-beta.1 → 3.0.0-beta.11
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/dist-es/PublicHelpers.d.ts +1 -1
- package/dist-es/PublicHelpers.js +24 -7
- package/dist-es/PublicHelpers.js.map +1 -1
- package/dist-es/SocketBase.js +13 -11
- package/dist-es/SocketBase.js.map +1 -1
- package/dist-es/SocketRequestHandler.js +3 -5
- package/dist-es/SocketRequestHandler.js.map +1 -1
- package/dist-es/SocketSubscriptionHandler.js +9 -5
- package/dist-es/SocketSubscriptionHandler.js.map +1 -1
- package/dist-es/tests/mocks/index.d.ts +3 -0
- package/dist-es/tests/mocks/index.js +4 -0
- package/dist-es/tests/mocks/index.js.map +1 -0
- package/dist-es/tests/mocks/mock-data.d.ts +54 -0
- package/dist-es/tests/mocks/mock-data.js +34 -0
- package/dist-es/tests/mocks/mock-data.js.map +1 -0
- package/dist-es/tests/mocks/mock-server.d.ts +43 -0
- package/dist-es/tests/mocks/mock-server.js +163 -0
- package/dist-es/tests/mocks/mock-server.js.map +1 -0
- package/dist-es/tests/mocks/mock-socket.d.ts +24 -0
- package/dist-es/tests/mocks/mock-socket.js +35 -0
- package/dist-es/tests/mocks/mock-socket.js.map +1 -0
- package/dist-es/tests/test-utils.d.ts +8 -0
- package/dist-es/tests/test-utils.js +28 -0
- package/dist-es/tests/test-utils.js.map +1 -0
- package/dist-es/types/public_helpers.d.ts +12 -3
- package/dist-es/types/public_helpers_internal.d.ts +4 -4
- package/dist-es/types/socket.d.ts +1 -0
- package/dist-es/types/subscriptions.d.ts +1 -1
- package/package.json +10 -7
- package/src/PublicHelpers.ts +25 -7
- package/src/SocketBase.ts +15 -12
- package/src/SocketRequestHandler.ts +3 -5
- package/src/SocketSubscriptionHandler.ts +7 -6
- package/src/tests/Socket.test.ts +129 -111
- package/src/tests/mocks/index.ts +3 -0
- package/src/tests/mocks/mock-data.ts +36 -0
- package/src/tests/mocks/mock-server.ts +276 -0
- package/src/tests/mocks/mock-socket.ts +72 -0
- package/src/tests/public_helpers.test.ts +64 -63
- package/src/tests/test-utils.ts +31 -0
- package/src/types/api_internal.ts +0 -1
- package/src/types/public_helpers.ts +11 -11
- package/src/types/public_helpers_internal.ts +4 -4
- package/src/types/socket.ts +1 -0
- package/src/types/subscriptions.ts +1 -1
- package/tsconfig.json +1 -1
- package/src/tests/helpers.ts +0 -195
@@ -0,0 +1,276 @@
|
|
1
|
+
import { Client, Server, WebSocket } from 'mock-socket';
|
2
|
+
|
3
|
+
import { OutgoingRequest, RequestSuccessResponse, RequestErrorResponse } from '../../types/api_internal.js';
|
4
|
+
import { EventEmitter } from 'events';
|
5
|
+
import { MOCK_SERVER_URL } from './mock-data.js';
|
6
|
+
|
7
|
+
interface MockFunctionCreator {
|
8
|
+
fn: (...args: any[]) => any;
|
9
|
+
};
|
10
|
+
|
11
|
+
type RequestCallback = (requestData: object) => void;
|
12
|
+
|
13
|
+
const toEmitId = (path: string, method: string) => {
|
14
|
+
return `${path}_${method}`;
|
15
|
+
};
|
16
|
+
|
17
|
+
const getDefaultMockCreatorF = () => ({
|
18
|
+
fn: () => {},
|
19
|
+
});
|
20
|
+
|
21
|
+
interface MockServerOptions {
|
22
|
+
url: string;
|
23
|
+
reportMissingListeners?: boolean;
|
24
|
+
mockF: MockFunctionCreator;
|
25
|
+
}
|
26
|
+
|
27
|
+
const DEFAULT_MOCK_SERVER_OPTIONS: MockServerOptions = {
|
28
|
+
url: MOCK_SERVER_URL,
|
29
|
+
reportMissingListeners: true,
|
30
|
+
mockF: getDefaultMockCreatorF(),
|
31
|
+
}
|
32
|
+
|
33
|
+
type MockRequestResponseDataObject<DataT extends object | undefined> = Omit<RequestSuccessResponse<DataT>, 'callback_id'> | Omit<RequestErrorResponse, 'callback_id'>;
|
34
|
+
type MockRequestResponseDataHandler<DataT extends object | undefined> = (request: OutgoingRequest, s: WebSocket) => MockRequestResponseDataObject<DataT>;
|
35
|
+
type MockRequestResponseData<DataT extends object | undefined> = MockRequestResponseDataObject<DataT> | MockRequestResponseDataHandler<DataT>;
|
36
|
+
|
37
|
+
const getMockServer = (initialOptions: Partial<MockServerOptions> = {}) => {
|
38
|
+
const { url, reportMissingListeners, mockF }: MockServerOptions = {
|
39
|
+
...DEFAULT_MOCK_SERVER_OPTIONS,
|
40
|
+
...initialOptions,
|
41
|
+
};
|
42
|
+
|
43
|
+
const mockServer = new Server(url);
|
44
|
+
let socket: Client;
|
45
|
+
const emitter = new EventEmitter();
|
46
|
+
emitter.setMaxListeners(1);
|
47
|
+
|
48
|
+
const send = (data: object) => {
|
49
|
+
socket.send(JSON.stringify(data));
|
50
|
+
};
|
51
|
+
|
52
|
+
const handlers: Map<string, () => any> = new Map();
|
53
|
+
|
54
|
+
const addServerHandler = <DataT extends object | undefined>(
|
55
|
+
method: string,
|
56
|
+
path: string,
|
57
|
+
responseData: MockRequestResponseData<DataT>,
|
58
|
+
subscriptionCallback?: RequestCallback,
|
59
|
+
) => {
|
60
|
+
const requestHandler = (request: OutgoingRequest, s: WebSocket) => {
|
61
|
+
if (subscriptionCallback) {
|
62
|
+
subscriptionCallback(request);
|
63
|
+
}
|
64
|
+
|
65
|
+
const data = typeof responseData === 'function' ? responseData(request, s) : responseData;
|
66
|
+
if (!data ||!data.code) {
|
67
|
+
throw new Error(`Mock server: response handler for path ${path} must return a status code`);
|
68
|
+
}
|
69
|
+
|
70
|
+
const response: RequestSuccessResponse | RequestErrorResponse = {
|
71
|
+
callback_id: request.callback_id,
|
72
|
+
...data,
|
73
|
+
};
|
74
|
+
|
75
|
+
s.send(JSON.stringify(response));
|
76
|
+
};
|
77
|
+
|
78
|
+
// Don't add duplicates
|
79
|
+
const emitId = toEmitId(path, method);
|
80
|
+
const removeExisting = handlers.get(emitId);
|
81
|
+
if (removeExisting) {
|
82
|
+
removeExisting();
|
83
|
+
handlers.delete(emitId);
|
84
|
+
}
|
85
|
+
|
86
|
+
// Add new
|
87
|
+
emitter.addListener(emitId, requestHandler);
|
88
|
+
|
89
|
+
// Prepare for removal
|
90
|
+
const removeListener = () => emitter.removeListener(emitId, requestHandler);
|
91
|
+
handlers.set(emitId, removeListener)
|
92
|
+
return removeListener;
|
93
|
+
};
|
94
|
+
|
95
|
+
const addDummyDataHandler = (method: string, path: string) => {
|
96
|
+
const handler = (request: OutgoingRequest, s: WebSocket) => {
|
97
|
+
// Do nothing
|
98
|
+
};
|
99
|
+
|
100
|
+
const emitId = toEmitId(path, method);
|
101
|
+
emitter.addListener(
|
102
|
+
emitId,
|
103
|
+
handler
|
104
|
+
);
|
105
|
+
emitter.setMaxListeners(1);
|
106
|
+
|
107
|
+
return () => emitter.removeListener(emitId, handler);
|
108
|
+
}
|
109
|
+
|
110
|
+
const addRequestHandler = <DataT extends object | undefined>(
|
111
|
+
method: string,
|
112
|
+
path: string,
|
113
|
+
data?: DataT | MockRequestResponseDataHandler<DataT>,
|
114
|
+
subscriptionCallback?: RequestCallback
|
115
|
+
) => {
|
116
|
+
const handlerData = typeof data === 'function' ? data : {
|
117
|
+
data,
|
118
|
+
code: data ? 200 : 204,
|
119
|
+
}
|
120
|
+
|
121
|
+
return addServerHandler<DataT>(
|
122
|
+
method,
|
123
|
+
path,
|
124
|
+
handlerData,
|
125
|
+
subscriptionCallback
|
126
|
+
);
|
127
|
+
}
|
128
|
+
|
129
|
+
const addErrorHandler = (
|
130
|
+
method: string,
|
131
|
+
path: string,
|
132
|
+
errorStr: string | null,
|
133
|
+
errorCode: number,
|
134
|
+
subscriptionCallback?: RequestCallback
|
135
|
+
) => {
|
136
|
+
return addServerHandler(
|
137
|
+
method,
|
138
|
+
path,
|
139
|
+
{
|
140
|
+
error: !errorStr ? null as any : {
|
141
|
+
message: errorStr,
|
142
|
+
},
|
143
|
+
code: errorCode,
|
144
|
+
},
|
145
|
+
subscriptionCallback
|
146
|
+
);
|
147
|
+
}
|
148
|
+
|
149
|
+
const addSubscriptionHandlerImpl = (
|
150
|
+
moduleName: string,
|
151
|
+
type: string,
|
152
|
+
listenerName: string,
|
153
|
+
entityId?: string | number,
|
154
|
+
) => {
|
155
|
+
const subscribeFn = mockF.fn();
|
156
|
+
const unsubscribeFn = mockF.fn();
|
157
|
+
|
158
|
+
const path = entityId ? `${moduleName}/${entityId}/${type}/${listenerName}` : `${moduleName}/${type}/${listenerName}`;
|
159
|
+
|
160
|
+
const subscribeRemove = addRequestHandler('POST', path, undefined, subscribeFn);
|
161
|
+
const unsubscribeRemove = addRequestHandler('DELETE', path, undefined, unsubscribeFn);
|
162
|
+
|
163
|
+
const fire = (data: object, entityId?: string | number) => {
|
164
|
+
send({
|
165
|
+
event: listenerName,
|
166
|
+
data,
|
167
|
+
id: entityId,
|
168
|
+
});
|
169
|
+
}
|
170
|
+
|
171
|
+
const remove = () => {
|
172
|
+
subscribeRemove();
|
173
|
+
unsubscribeRemove();
|
174
|
+
};
|
175
|
+
|
176
|
+
return {
|
177
|
+
fire,
|
178
|
+
remove,
|
179
|
+
|
180
|
+
subscribeFn,
|
181
|
+
unsubscribeFn,
|
182
|
+
|
183
|
+
path,
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
|
188
|
+
const addSubscriptionHandler = (
|
189
|
+
moduleName: string,
|
190
|
+
listenerName: string,
|
191
|
+
entityId?: string | number,
|
192
|
+
) => {
|
193
|
+
return addSubscriptionHandlerImpl(moduleName, 'listeners', listenerName, entityId);
|
194
|
+
}
|
195
|
+
|
196
|
+
const addHookHandler = (
|
197
|
+
moduleName: string,
|
198
|
+
listenerName: string,
|
199
|
+
) => {
|
200
|
+
const subscriber = addSubscriptionHandlerImpl(moduleName, 'hooks', listenerName);
|
201
|
+
|
202
|
+
const addResolver = (completionId: number) => {
|
203
|
+
const resolveFn = mockF.fn();
|
204
|
+
const rejectFn = mockF.fn();
|
205
|
+
|
206
|
+
const resolveRemove = addRequestHandler(
|
207
|
+
'POST',
|
208
|
+
`${subscriber.path}/${completionId}/resolve`,
|
209
|
+
undefined,
|
210
|
+
resolveFn
|
211
|
+
);
|
212
|
+
|
213
|
+
const rejectRemove = addRequestHandler(
|
214
|
+
'POST',
|
215
|
+
`${subscriber.path}/${completionId}/reject`,
|
216
|
+
undefined,
|
217
|
+
rejectFn
|
218
|
+
);
|
219
|
+
|
220
|
+
const remove = () => {
|
221
|
+
resolveRemove();
|
222
|
+
rejectRemove();
|
223
|
+
}
|
224
|
+
|
225
|
+
const fire = (data: object) => {
|
226
|
+
send({
|
227
|
+
event: listenerName,
|
228
|
+
data,
|
229
|
+
completion_id: completionId,
|
230
|
+
});
|
231
|
+
}
|
232
|
+
|
233
|
+
return { fire, remove, resolveFn, rejectFn };
|
234
|
+
};
|
235
|
+
|
236
|
+
return {
|
237
|
+
addResolver,
|
238
|
+
|
239
|
+
...subscriber,
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
mockServer.on('connection', s => {
|
245
|
+
socket = s;
|
246
|
+
|
247
|
+
socket.on('message', (messageObj) => {
|
248
|
+
const request: OutgoingRequest = JSON.parse(messageObj as string);
|
249
|
+
const emitId = toEmitId(request.path, request.method);
|
250
|
+
const processed = emitter.emit(emitId, request, s);
|
251
|
+
if (reportMissingListeners && !processed) {
|
252
|
+
console.warn(`Mock server: no listeners for event ${request.method} ${request.path}`);
|
253
|
+
}
|
254
|
+
});
|
255
|
+
});
|
256
|
+
|
257
|
+
return {
|
258
|
+
addRequestHandler,
|
259
|
+
addErrorHandler,
|
260
|
+
|
261
|
+
addSubscriptionHandler,
|
262
|
+
addHookHandler,
|
263
|
+
|
264
|
+
ignoreMissingHandler: addDummyDataHandler,
|
265
|
+
stop: () => {
|
266
|
+
mockServer.stop(() => {
|
267
|
+
emitter.removeAllListeners();
|
268
|
+
handlers.clear();
|
269
|
+
});
|
270
|
+
},
|
271
|
+
send,
|
272
|
+
url,
|
273
|
+
};
|
274
|
+
};
|
275
|
+
|
276
|
+
export { getMockServer };
|
@@ -0,0 +1,72 @@
|
|
1
|
+
import { Socket } from '../../NodeSocket.js';
|
2
|
+
import { WebSocket } from 'mock-socket';
|
3
|
+
|
4
|
+
import * as Options from '../../types/options.js';
|
5
|
+
import ApiConstants from '../../ApiConstants.js';
|
6
|
+
|
7
|
+
import { getMockServer } from './mock-server.js';
|
8
|
+
import { DEFAULT_AUTH_RESPONSE, DEFAULT_CONNECT_CREDENTIALS, MOCK_SERVER_URL } from './mock-data.js';
|
9
|
+
|
10
|
+
const getDefaultSocketOptions = (): Options.APISocketOptions => ({
|
11
|
+
url: MOCK_SERVER_URL,
|
12
|
+
logOutput: console,
|
13
|
+
logLevel: 'warn',
|
14
|
+
});
|
15
|
+
|
16
|
+
export type MockSocketConnectOptions = Omit<Options.APISocketOptions, 'username' | 'password' | 'url'> & {
|
17
|
+
username?: string;
|
18
|
+
password?: string;
|
19
|
+
url?: string;
|
20
|
+
};
|
21
|
+
|
22
|
+
|
23
|
+
type RequestCallback = (requestData: object) => void;
|
24
|
+
|
25
|
+
interface MockSocketOptions {
|
26
|
+
console: Options.LogOutput;
|
27
|
+
socketOptions?: MockSocketConnectOptions;
|
28
|
+
}
|
29
|
+
|
30
|
+
export interface MockConnectedSocketOptions extends MockSocketOptions {
|
31
|
+
authCallback?: RequestCallback;
|
32
|
+
authResponse: object;
|
33
|
+
}
|
34
|
+
|
35
|
+
|
36
|
+
export const getSocket = (socketOptions: MockSocketConnectOptions = {}) => {
|
37
|
+
const socket = Socket(
|
38
|
+
{
|
39
|
+
...getDefaultSocketOptions(),
|
40
|
+
...socketOptions
|
41
|
+
},
|
42
|
+
WebSocket as any
|
43
|
+
);
|
44
|
+
|
45
|
+
return { socket };
|
46
|
+
};
|
47
|
+
|
48
|
+
const getDefaultConnectOptions = () => ({
|
49
|
+
console,
|
50
|
+
authResponse: DEFAULT_AUTH_RESPONSE,
|
51
|
+
});
|
52
|
+
|
53
|
+
export const getConnectedSocket = async (
|
54
|
+
server: ReturnType<typeof getMockServer>,
|
55
|
+
userOptions?: Partial<MockConnectedSocketOptions>,
|
56
|
+
) => {
|
57
|
+
const options: MockConnectedSocketOptions = {
|
58
|
+
...getDefaultConnectOptions(),
|
59
|
+
...userOptions,
|
60
|
+
};
|
61
|
+
|
62
|
+
const removeAuthHandler = server.addRequestHandler('POST', ApiConstants.LOGIN_URL, options.authResponse, options.authCallback);
|
63
|
+
|
64
|
+
const { socket } = getSocket({
|
65
|
+
...DEFAULT_CONNECT_CREDENTIALS,
|
66
|
+
...getDefaultSocketOptions(),
|
67
|
+
...options.socketOptions
|
68
|
+
});
|
69
|
+
await socket.connect();
|
70
|
+
|
71
|
+
return { socket, removeAuthHandler };
|
72
|
+
};
|
@@ -1,15 +1,14 @@
|
|
1
1
|
import {
|
2
2
|
getMockServer,
|
3
3
|
getConnectedSocket,
|
4
|
-
|
5
|
-
} from './
|
4
|
+
} from './mocks';
|
5
|
+
import { waitForExpect } from './test-utils.js';
|
6
6
|
|
7
7
|
import { jest } from '@jest/globals';
|
8
8
|
|
9
9
|
import { addContextMenuItems } from '../PublicHelpers.js';
|
10
10
|
import { SelectedMenuItemListenerData, MenuItemListHookData, MenuItemListHookAcceptData } from '../types/public_helpers_internal.js';
|
11
|
-
import { HookSubscriberInfo } from '../types/index.js';
|
12
|
-
import { IncomingSubscriptionEvent } from '../types/api_internal.js';
|
11
|
+
import { HookSubscriberInfo, MenuCallbackProperties, MenuClickHandlerProperties } from '../types/index.js';
|
13
12
|
|
14
13
|
|
15
14
|
let server: ReturnType<typeof getMockServer>;
|
@@ -18,7 +17,9 @@ let server: ReturnType<typeof getMockServer>;
|
|
18
17
|
describe('public helpers', () => {
|
19
18
|
|
20
19
|
beforeEach(() => {
|
21
|
-
server = getMockServer(
|
20
|
+
server = getMockServer({
|
21
|
+
mockF: jest,
|
22
|
+
});
|
22
23
|
});
|
23
24
|
|
24
25
|
afterEach(() => {
|
@@ -97,19 +98,10 @@ describe('public helpers', () => {
|
|
97
98
|
// Socket handlers
|
98
99
|
const { socket } = await getConnectedSocket(server);
|
99
100
|
|
100
|
-
const
|
101
|
-
server.addDataHandler('POST', `menus/listeners/${MENU_ID}_menuitem_selected`, undefined, listenerAddCallback);
|
102
|
-
|
103
|
-
const hookAddCallback = jest.fn();
|
104
|
-
const hookResolveCallback = jest.fn();
|
105
|
-
server.addDataHandler('POST', `menus/hooks/${MENU_ID}_list_menuitems`, undefined, hookAddCallback);
|
106
|
-
server.addDataHandler(
|
107
|
-
'POST',
|
108
|
-
`menus/hooks/${MENU_ID}_list_menuitems/${HOOK_COMPLETION_ID}/resolve`,
|
109
|
-
menuItemListData,
|
110
|
-
hookResolveCallback
|
111
|
-
);
|
101
|
+
const itemSelectedListener = server.addSubscriptionHandler('menus', `${MENU_ID}_menuitem_selected`);
|
112
102
|
|
103
|
+
const listItemsHook = server.addHookHandler('menus', `${MENU_ID}_list_menuitems`);
|
104
|
+
const listItemsResolver = listItemsHook.addResolver(HOOK_COMPLETION_ID);
|
113
105
|
|
114
106
|
// Add menu items
|
115
107
|
const onClickItem1Mock = jest.fn();
|
@@ -123,42 +115,42 @@ describe('public helpers', () => {
|
|
123
115
|
[
|
124
116
|
{
|
125
117
|
...MENU_ITEM1,
|
126
|
-
filter: async (
|
118
|
+
filter: async () => {
|
127
119
|
return true;
|
128
120
|
},
|
129
121
|
access: VALID_ACCESS,
|
130
|
-
onClick: (
|
131
|
-
onClickItem1Mock(
|
122
|
+
onClick: (props) => {
|
123
|
+
onClickItem1Mock(props);
|
132
124
|
},
|
133
|
-
formDefinitions: (
|
125
|
+
formDefinitions: () => {
|
134
126
|
return FORM_DEFINITIONS;
|
135
127
|
},
|
136
128
|
}, {
|
137
129
|
...MENU_ITEM2,
|
138
|
-
onClick: (
|
139
|
-
onClickItem2Mock(
|
130
|
+
onClick: (props) => {
|
131
|
+
onClickItem2Mock(props);
|
140
132
|
}
|
141
133
|
}, {
|
142
134
|
...MENU_ITEM3,
|
143
|
-
urls: (
|
144
|
-
onGetUrlsItem3Mock(
|
135
|
+
urls: (props) => {
|
136
|
+
onGetUrlsItem3Mock(props);
|
145
137
|
return URLS;
|
146
138
|
}
|
147
139
|
}, {
|
148
140
|
id: 'ignored_filter_id',
|
149
141
|
title: 'Mock item ignored by filter',
|
150
|
-
filter: (
|
142
|
+
filter: () => {
|
151
143
|
return false;
|
152
144
|
},
|
153
|
-
onClick: (
|
154
|
-
onClickItemIgnoredMock(
|
145
|
+
onClick: (props) => {
|
146
|
+
onClickItemIgnoredMock(props);
|
155
147
|
}
|
156
148
|
}, {
|
157
149
|
id: 'ignored_access_id',
|
158
150
|
title: 'Mock item ignored by access',
|
159
151
|
access: 'invalid_access',
|
160
|
-
onClick: (
|
161
|
-
onClickItemIgnoredMock(
|
152
|
+
onClick: (props) => {
|
153
|
+
onClickItemIgnoredMock(props);
|
162
154
|
}
|
163
155
|
}
|
164
156
|
],
|
@@ -166,33 +158,29 @@ describe('public helpers', () => {
|
|
166
158
|
SUBSCRIBER_INFO,
|
167
159
|
);
|
168
160
|
|
169
|
-
expect(
|
170
|
-
expect(
|
161
|
+
expect(itemSelectedListener.subscribeFn).toHaveBeenCalledTimes(1);
|
162
|
+
expect(listItemsHook.subscribeFn).toHaveBeenCalledTimes(1);
|
171
163
|
|
172
164
|
|
173
165
|
// List items hook
|
174
166
|
{
|
175
|
-
const
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
permissions: PERMISSIONS,
|
181
|
-
supports: SUPPORTS,
|
182
|
-
},
|
183
|
-
completion_id: 1,
|
167
|
+
const menuItemListData: MenuItemListHookData<string, null> = {
|
168
|
+
selected_ids: selectedMenuIds,
|
169
|
+
entity_id: null,
|
170
|
+
permissions: PERMISSIONS,
|
171
|
+
supports: SUPPORTS,
|
184
172
|
};
|
185
173
|
|
186
|
-
|
174
|
+
listItemsResolver.fire(menuItemListData);
|
187
175
|
}
|
188
176
|
|
189
177
|
// Validate list items results
|
190
178
|
{
|
191
179
|
await waitForExpect(() => {
|
192
|
-
expect(
|
180
|
+
expect(listItemsResolver.resolveFn).toHaveBeenCalledTimes(1);
|
193
181
|
});
|
194
182
|
|
195
|
-
expect(
|
183
|
+
expect(listItemsResolver.resolveFn).toHaveBeenCalledWith(
|
196
184
|
expect.objectContaining({
|
197
185
|
data: menuItemListData
|
198
186
|
}),
|
@@ -201,27 +189,31 @@ describe('public helpers', () => {
|
|
201
189
|
await waitForExpect(() => {
|
202
190
|
expect(onGetUrlsItem3Mock).toHaveBeenCalledTimes(1);
|
203
191
|
});
|
204
|
-
|
192
|
+
|
193
|
+
const urlCallbackProps: MenuCallbackProperties<string, null> = {
|
194
|
+
selectedIds: selectedMenuIds,
|
195
|
+
entityId: null,
|
196
|
+
permissions: PERMISSIONS,
|
197
|
+
supports: SUPPORTS
|
198
|
+
}
|
199
|
+
|
200
|
+
expect(onGetUrlsItem3Mock).toHaveBeenCalledWith(urlCallbackProps);
|
205
201
|
}
|
206
202
|
|
207
203
|
// Select event listener
|
208
204
|
{
|
209
|
-
const
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
supports: SUPPORTS,
|
219
|
-
form_values: FORM_VALUES
|
220
|
-
},
|
221
|
-
completion_id: HOOK_COMPLETION_ID,
|
205
|
+
const selectData: SelectedMenuItemListenerData<string, null> = {
|
206
|
+
menuitem_id: MENU_ITEM1_ID,
|
207
|
+
hook_id: SUBSCRIBER_INFO.id,
|
208
|
+
menu_id: MENU_ID,
|
209
|
+
entity_id: null,
|
210
|
+
selected_ids: selectedMenuIds,
|
211
|
+
permissions: PERMISSIONS,
|
212
|
+
supports: SUPPORTS,
|
213
|
+
form_values: FORM_VALUES
|
222
214
|
};
|
223
215
|
|
224
|
-
|
216
|
+
itemSelectedListener.fire(selectData);
|
225
217
|
}
|
226
218
|
|
227
219
|
// Validate select event results
|
@@ -229,13 +221,22 @@ describe('public helpers', () => {
|
|
229
221
|
await waitForExpect(() => {
|
230
222
|
expect(onClickItem1Mock).toHaveBeenCalledTimes(1);
|
231
223
|
});
|
232
|
-
|
233
|
-
|
234
|
-
|
224
|
+
|
225
|
+
const clickHandlerProps: MenuClickHandlerProperties<string, null> = {
|
226
|
+
selectedIds: selectedMenuIds,
|
227
|
+
entityId: null,
|
228
|
+
permissions: PERMISSIONS,
|
229
|
+
supports: SUPPORTS,
|
230
|
+
formValues: FORM_VALUES
|
231
|
+
}
|
232
|
+
|
233
|
+
expect(onClickItem1Mock).toHaveBeenCalledWith(clickHandlerProps);
|
234
|
+
expect(onClickItem2Mock).not.toHaveBeenCalled();
|
235
|
+
expect(onClickItemIgnoredMock).not.toHaveBeenCalled();
|
235
236
|
}
|
236
237
|
|
237
238
|
// Remove items
|
238
|
-
removeMenuItems();
|
239
|
+
await removeMenuItems();
|
239
240
|
expect(socket.hasListeners()).toBe(false);
|
240
241
|
});
|
241
242
|
});
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import waitForExpectOriginal from 'wait-for-expect';
|
2
|
+
|
3
|
+
const EXCEPT_TIMEOUT = 1000;
|
4
|
+
//@ts-ignore
|
5
|
+
export const waitForExpect = (func: () => void | Promise<void>) => waitForExpectOriginal.default(func, EXCEPT_TIMEOUT);
|
6
|
+
|
7
|
+
|
8
|
+
// This is a helper function that will suppress the error of a promise.
|
9
|
+
export const defusedPromise = (promise: Promise<any>) => {
|
10
|
+
promise.catch(() => {});
|
11
|
+
return promise;
|
12
|
+
}
|
13
|
+
|
14
|
+
export const getMockConsole = (verbose = false) => ({
|
15
|
+
log: jest.fn((a1: any, a2: any, a3: any, a4: any) => {
|
16
|
+
if (verbose) {
|
17
|
+
console.log(a1, a2, a3, a4);
|
18
|
+
}
|
19
|
+
}),
|
20
|
+
info: jest.fn((a1: any, a2: any, a3: any, a4: any) => {
|
21
|
+
if (verbose) {
|
22
|
+
console.info(a1, a2, a3, a4);
|
23
|
+
}
|
24
|
+
}),
|
25
|
+
warn: jest.fn((a1: any, a2: any, a3: any, a4: any) => {
|
26
|
+
console.warn(a1, a2, a3, a4);
|
27
|
+
}),
|
28
|
+
error: jest.fn((a1: any, a2: any, a3: any, a4: any) => {
|
29
|
+
console.error(a1, a2, a3, a4);
|
30
|
+
}),
|
31
|
+
});
|
@@ -1,29 +1,29 @@
|
|
1
1
|
import { HookSubscriberInfo } from './subscriptions.js';
|
2
2
|
|
3
|
-
|
3
|
+
export interface MenuCallbackProperties<IdT, EntityIdT> {
|
4
4
|
selectedIds: IdT[],
|
5
|
-
entityId: EntityIdT
|
5
|
+
entityId: EntityIdT,
|
6
6
|
permissions: string[],
|
7
7
|
supports: string[]
|
8
|
-
|
8
|
+
}
|
9
|
+
|
10
|
+
export interface MenuClickHandlerProperties<IdT, EntityIdT, FormValueT extends object = object> extends MenuCallbackProperties<IdT, EntityIdT> {
|
11
|
+
formValues: FormValueT
|
12
|
+
}
|
13
|
+
|
14
|
+
type AsyncCallbackProperty<IdT, EntityIdT, ReturnT> = (props: MenuCallbackProperties<IdT, EntityIdT>) => ReturnT | Promise<ReturnT>;
|
9
15
|
|
10
16
|
export type ContextMenuIcon = { [key in string]: string };
|
11
17
|
|
12
18
|
export interface ContextMenu extends HookSubscriberInfo {
|
13
19
|
icon?: ContextMenuIcon;
|
14
20
|
}
|
15
|
-
export interface ContextMenuItem<IdT, EntityIdT> {
|
21
|
+
export interface ContextMenuItem<IdT, EntityIdT, FormValueT extends object = object> {
|
16
22
|
id: string;
|
17
23
|
title: string;
|
18
24
|
icon?: ContextMenuIcon;
|
19
25
|
urls?: string[] | AsyncCallbackProperty<IdT, EntityIdT, string[] | undefined>;
|
20
|
-
onClick?: (
|
21
|
-
selectedIds: IdT[],
|
22
|
-
entityId: EntityIdT | null,
|
23
|
-
permissions: string[],
|
24
|
-
supports: string[],
|
25
|
-
formValues: object
|
26
|
-
) => void;
|
26
|
+
onClick?: (props: MenuClickHandlerProperties<IdT, EntityIdT, FormValueT>) => void;
|
27
27
|
filter?: AsyncCallbackProperty<IdT, EntityIdT, boolean>;
|
28
28
|
access?: string;
|
29
29
|
formDefinitions?: object[] | AsyncCallbackProperty<IdT, EntityIdT, object[]>;
|
@@ -6,7 +6,7 @@ export interface SelectedMenuItemListenerData<IdT, EntityIdT> {
|
|
6
6
|
menu_id: string;
|
7
7
|
menuitem_id: string;
|
8
8
|
selected_ids: IdT[];
|
9
|
-
entity_id: EntityIdT
|
9
|
+
entity_id: EntityIdT;
|
10
10
|
permissions: string[];
|
11
11
|
supports: string[];
|
12
12
|
form_values: object;
|
@@ -14,14 +14,14 @@ export interface SelectedMenuItemListenerData<IdT, EntityIdT> {
|
|
14
14
|
|
15
15
|
export interface MenuItemListHookData<IdT, EntityIdT> {
|
16
16
|
selected_ids: IdT[];
|
17
|
-
entity_id: EntityIdT
|
17
|
+
entity_id: EntityIdT;
|
18
18
|
permissions: string[];
|
19
19
|
supports: string[];
|
20
20
|
}
|
21
21
|
|
22
22
|
export interface ResponseMenuItemCallbackFields {
|
23
|
-
urls?: string[]
|
24
|
-
form_definitions?: object[]
|
23
|
+
urls?: string[];
|
24
|
+
form_definitions?: object[];
|
25
25
|
}
|
26
26
|
|
27
27
|
export type ResponseMenuItem<IdT, EntityIdT> = Omit<ContextMenuItem<IdT, EntityIdT>, 'onClick' | 'filter' | 'urls' | 'form_definitions'> & ResponseMenuItemCallbackFields;
|
package/src/types/socket.ts
CHANGED