@hubspot/ui-extensions 0.9.2 → 0.9.4
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/__tests__/experimental/crm/fetchAssociations.spec.d.ts +1 -0
- package/dist/__tests__/experimental/crm/fetchAssociations.spec.js +396 -0
- package/dist/__tests__/experimental/crm/fetchCrmProperties.spec.js +180 -14
- package/dist/__tests__/experimental/hooks/useAssociations.spec.d.ts +1 -0
- package/dist/__tests__/experimental/hooks/useAssociations.spec.js +368 -0
- package/dist/__tests__/experimental/hooks/useCrmProperties.spec.js +151 -4
- package/dist/experimental/crm/fetchAssociations.d.ts +29 -0
- package/dist/experimental/crm/fetchAssociations.js +42 -0
- package/dist/experimental/crm/fetchCrmProperties.d.ts +23 -2
- package/dist/experimental/crm/fetchCrmProperties.js +22 -16
- package/dist/experimental/hooks/useAssociations.d.ts +10 -0
- package/dist/experimental/hooks/useAssociations.js +116 -0
- package/dist/experimental/hooks/useCrmProperties.d.ts +3 -2
- package/dist/experimental/hooks/useCrmProperties.js +95 -31
- package/dist/experimental/index.d.ts +34 -1
- package/dist/experimental/index.js +19 -4
- package/dist/experimental/types.d.ts +212 -6
- package/dist/types.d.ts +15 -1
- package/dist/types.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
// Set up the mock before importing the module
|
|
2
|
+
const mockFetchAssociations = jest.fn();
|
|
3
|
+
const mockSelf = {
|
|
4
|
+
fetchAssociations: mockFetchAssociations,
|
|
5
|
+
};
|
|
6
|
+
// Mock the global self object
|
|
7
|
+
Object.defineProperty(global, 'self', {
|
|
8
|
+
value: mockSelf,
|
|
9
|
+
writable: true,
|
|
10
|
+
});
|
|
11
|
+
import { fetchAssociations } from '../../../experimental/crm/fetchAssociations';
|
|
12
|
+
describe('fetchAssociations', () => {
|
|
13
|
+
// Helper functions
|
|
14
|
+
const createMockResponse = (data, overrides = {}) => ({
|
|
15
|
+
ok: true,
|
|
16
|
+
json: jest.fn().mockResolvedValue({ data, cleanup: jest.fn() }),
|
|
17
|
+
...overrides,
|
|
18
|
+
});
|
|
19
|
+
const createErrorResponse = (statusText) => ({
|
|
20
|
+
ok: false,
|
|
21
|
+
statusText,
|
|
22
|
+
json: jest.fn().mockResolvedValue({}),
|
|
23
|
+
});
|
|
24
|
+
const expectError = async (request, expectedMessage) => {
|
|
25
|
+
await expect(fetchAssociations(request)).rejects.toThrow(expectedMessage);
|
|
26
|
+
};
|
|
27
|
+
const createBasicRequest = (overrides = {}) => ({
|
|
28
|
+
toObjectType: '0-1',
|
|
29
|
+
properties: ['firstname', 'lastname'],
|
|
30
|
+
...overrides,
|
|
31
|
+
});
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
jest.clearAllMocks();
|
|
34
|
+
// Set up dynamic mock that responds to request parameters
|
|
35
|
+
mockFetchAssociations.mockImplementation((request) => {
|
|
36
|
+
const { pageLength = 100, offset = 0, properties = [] } = request;
|
|
37
|
+
// Generate mock properties based on the request
|
|
38
|
+
const mockProperties = {};
|
|
39
|
+
properties.forEach((prop) => {
|
|
40
|
+
mockProperties[prop] = `Mock ${prop} (page ${Math.floor(offset / pageLength) + 1})`;
|
|
41
|
+
});
|
|
42
|
+
const mockResults = [
|
|
43
|
+
{
|
|
44
|
+
toObjectId: 1000 + offset,
|
|
45
|
+
associationTypes: [
|
|
46
|
+
{ category: 'HUBSPOT_DEFINED', typeId: 1, label: 'Primary' },
|
|
47
|
+
],
|
|
48
|
+
properties: mockProperties,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
toObjectId: 1001 + offset,
|
|
52
|
+
associationTypes: [
|
|
53
|
+
{
|
|
54
|
+
category: 'USER_DEFINED',
|
|
55
|
+
typeId: 100,
|
|
56
|
+
label: 'Custom Association',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
properties: mockProperties,
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
const mockData = {
|
|
63
|
+
results: mockResults,
|
|
64
|
+
hasMore: offset < 200,
|
|
65
|
+
nextOffset: offset + pageLength,
|
|
66
|
+
};
|
|
67
|
+
return Promise.resolve(createMockResponse(mockData));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('basic functionality', () => {
|
|
71
|
+
it('should return mock associations with default parameters', async () => {
|
|
72
|
+
const request = createBasicRequest();
|
|
73
|
+
const result = await fetchAssociations(request);
|
|
74
|
+
expect(result).toHaveProperty('data');
|
|
75
|
+
expect(result).toHaveProperty('cleanup');
|
|
76
|
+
expect(typeof result.cleanup).toBe('function');
|
|
77
|
+
const { data } = result;
|
|
78
|
+
expect(data.results).toHaveLength(2);
|
|
79
|
+
expect(data.hasMore).toBe(true);
|
|
80
|
+
expect(data.nextOffset).toBe(100);
|
|
81
|
+
// Check first association
|
|
82
|
+
expect(data.results[0]).toEqual({
|
|
83
|
+
toObjectId: 1000,
|
|
84
|
+
associationTypes: [
|
|
85
|
+
{ category: 'HUBSPOT_DEFINED', typeId: 1, label: 'Primary' },
|
|
86
|
+
],
|
|
87
|
+
properties: {
|
|
88
|
+
firstname: 'Mock firstname (page 1)',
|
|
89
|
+
lastname: 'Mock lastname (page 1)',
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Check second association
|
|
93
|
+
expect(data.results[1]).toEqual({
|
|
94
|
+
toObjectId: 1001,
|
|
95
|
+
associationTypes: [
|
|
96
|
+
{
|
|
97
|
+
category: 'USER_DEFINED',
|
|
98
|
+
typeId: 100,
|
|
99
|
+
label: 'Custom Association',
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
properties: {
|
|
103
|
+
firstname: 'Mock firstname (page 1)',
|
|
104
|
+
lastname: 'Mock lastname (page 1)',
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
it('should return a cleanup function', async () => {
|
|
109
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
110
|
+
const result = await fetchAssociations(request);
|
|
111
|
+
expect(typeof result.cleanup).toBe('function');
|
|
112
|
+
// Should not throw when called
|
|
113
|
+
expect(() => result.cleanup()).not.toThrow();
|
|
114
|
+
});
|
|
115
|
+
it('should return a Promise', () => {
|
|
116
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
117
|
+
const result = fetchAssociations(request);
|
|
118
|
+
expect(result).toBeInstanceOf(Promise);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
describe('pagination handling', () => {
|
|
122
|
+
it('should handle custom page length and offset', async () => {
|
|
123
|
+
const request = createBasicRequest({
|
|
124
|
+
properties: ['email'],
|
|
125
|
+
pageLength: 25,
|
|
126
|
+
offset: 50,
|
|
127
|
+
});
|
|
128
|
+
const result = await fetchAssociations(request);
|
|
129
|
+
const { data } = result;
|
|
130
|
+
expect(data.results).toHaveLength(2);
|
|
131
|
+
expect(data.nextOffset).toBe(75); // 50 + 25
|
|
132
|
+
expect(data.hasMore).toBe(true);
|
|
133
|
+
// Check that offset is reflected in object IDs
|
|
134
|
+
expect(data.results[0].toObjectId).toBe(1050); // 1000 + 50
|
|
135
|
+
expect(data.results[1].toObjectId).toBe(1051); // 1001 + 50
|
|
136
|
+
// Check that page number is reflected in properties
|
|
137
|
+
expect(data.results[0].properties.email).toBe('Mock email (page 3)'); // Math.floor(50 / 25) + 1 = 3
|
|
138
|
+
});
|
|
139
|
+
it('should calculate hasMore correctly based on offset', async () => {
|
|
140
|
+
// Test when there are more results
|
|
141
|
+
const request1 = createBasicRequest({
|
|
142
|
+
properties: ['firstname'],
|
|
143
|
+
offset: 100,
|
|
144
|
+
});
|
|
145
|
+
const result1 = await fetchAssociations(request1);
|
|
146
|
+
expect(result1.data.hasMore).toBe(true);
|
|
147
|
+
// Test when there are no more results
|
|
148
|
+
const request2 = createBasicRequest({
|
|
149
|
+
properties: ['firstname'],
|
|
150
|
+
offset: 200,
|
|
151
|
+
});
|
|
152
|
+
const result2 = await fetchAssociations(request2);
|
|
153
|
+
expect(result2.data.hasMore).toBe(false);
|
|
154
|
+
// Test edge case at the boundary
|
|
155
|
+
const request3 = createBasicRequest({
|
|
156
|
+
properties: ['firstname'],
|
|
157
|
+
offset: 199,
|
|
158
|
+
});
|
|
159
|
+
const result3 = await fetchAssociations(request3);
|
|
160
|
+
expect(result3.data.hasMore).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
it('should generate different page numbers for different offsets', async () => {
|
|
163
|
+
const request1 = createBasicRequest({
|
|
164
|
+
properties: ['firstname'],
|
|
165
|
+
pageLength: 10,
|
|
166
|
+
offset: 0,
|
|
167
|
+
});
|
|
168
|
+
const result1 = await fetchAssociations(request1);
|
|
169
|
+
expect(result1.data.results[0].properties.firstname).toBe('Mock firstname (page 1)');
|
|
170
|
+
const request2 = createBasicRequest({
|
|
171
|
+
properties: ['firstname'],
|
|
172
|
+
pageLength: 10,
|
|
173
|
+
offset: 20,
|
|
174
|
+
});
|
|
175
|
+
const result2 = await fetchAssociations(request2);
|
|
176
|
+
expect(result2.data.results[0].properties.firstname).toBe('Mock firstname (page 3)'); // Math.floor(20 / 10) + 1 = 3
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
describe('properties handling', () => {
|
|
180
|
+
it('should handle empty properties array', async () => {
|
|
181
|
+
const request = createBasicRequest({ properties: [] });
|
|
182
|
+
const result = await fetchAssociations(request);
|
|
183
|
+
const { data } = result;
|
|
184
|
+
expect(data.results).toHaveLength(2);
|
|
185
|
+
expect(data.results[0].properties).toEqual({});
|
|
186
|
+
expect(data.results[1].properties).toEqual({});
|
|
187
|
+
});
|
|
188
|
+
it('should handle missing properties in request', async () => {
|
|
189
|
+
const request = createBasicRequest();
|
|
190
|
+
// Remove properties to test missing case
|
|
191
|
+
delete request.properties;
|
|
192
|
+
const result = await fetchAssociations(request);
|
|
193
|
+
const { data } = result;
|
|
194
|
+
expect(data.results).toHaveLength(2);
|
|
195
|
+
expect(data.results[0].properties).toEqual({});
|
|
196
|
+
expect(data.results[1].properties).toEqual({});
|
|
197
|
+
});
|
|
198
|
+
it('should handle multiple properties correctly', async () => {
|
|
199
|
+
const request = createBasicRequest({
|
|
200
|
+
properties: ['firstname', 'lastname', 'email', 'phone'],
|
|
201
|
+
});
|
|
202
|
+
const result = await fetchAssociations(request);
|
|
203
|
+
const { data } = result;
|
|
204
|
+
expect(data.results[0].properties).toEqual({
|
|
205
|
+
firstname: 'Mock firstname (page 1)',
|
|
206
|
+
lastname: 'Mock lastname (page 1)',
|
|
207
|
+
email: 'Mock email (page 1)',
|
|
208
|
+
phone: 'Mock phone (page 1)',
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it('should handle different object types', async () => {
|
|
212
|
+
const request = createBasicRequest({
|
|
213
|
+
toObjectType: '0-2',
|
|
214
|
+
properties: ['name'],
|
|
215
|
+
});
|
|
216
|
+
const result = await fetchAssociations(request);
|
|
217
|
+
const { data } = result;
|
|
218
|
+
expect(data.results).toHaveLength(2);
|
|
219
|
+
expect(data.results[0].properties.name).toBe('Mock name (page 1)');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('error handling', () => {
|
|
223
|
+
it('should throw an error when response is not OK', async () => {
|
|
224
|
+
mockFetchAssociations.mockResolvedValueOnce(createErrorResponse('Not Found'));
|
|
225
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
226
|
+
await expectError(request, 'Failed to fetch associations: Not Found');
|
|
227
|
+
});
|
|
228
|
+
it('should throw an error when fetch fails', async () => {
|
|
229
|
+
mockFetchAssociations.mockRejectedValueOnce(new Error('Network error'));
|
|
230
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
231
|
+
await expectError(request, 'Network error');
|
|
232
|
+
});
|
|
233
|
+
it('should handle unknown errors', async () => {
|
|
234
|
+
mockFetchAssociations.mockRejectedValueOnce('Unknown error');
|
|
235
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
236
|
+
await expectError(request, 'Failed to fetch associations: Unknown error');
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('response validation', () => {
|
|
240
|
+
it('should throw error for invalid response format - missing results', async () => {
|
|
241
|
+
const invalidData = { hasMore: true, nextOffset: 0 }; // missing results
|
|
242
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(invalidData));
|
|
243
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
244
|
+
await expectError(request, 'Invalid response format');
|
|
245
|
+
});
|
|
246
|
+
it('should throw error for invalid response format - null data', async () => {
|
|
247
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(null));
|
|
248
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
249
|
+
await expectError(request, 'Invalid response format');
|
|
250
|
+
});
|
|
251
|
+
it('should throw error for invalid association result structure', async () => {
|
|
252
|
+
const invalidData = {
|
|
253
|
+
results: [{ invalidStructure: true }],
|
|
254
|
+
hasMore: false,
|
|
255
|
+
nextOffset: 0,
|
|
256
|
+
};
|
|
257
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(invalidData));
|
|
258
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
259
|
+
await expectError(request, 'Invalid response format');
|
|
260
|
+
});
|
|
261
|
+
it('should throw error for invalid association types array', async () => {
|
|
262
|
+
const invalidData = {
|
|
263
|
+
results: [
|
|
264
|
+
{
|
|
265
|
+
toObjectId: 1000,
|
|
266
|
+
associationTypes: 'not an array',
|
|
267
|
+
properties: {},
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
hasMore: false,
|
|
271
|
+
nextOffset: 0,
|
|
272
|
+
};
|
|
273
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(invalidData));
|
|
274
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
275
|
+
await expectError(request, 'Invalid response format');
|
|
276
|
+
});
|
|
277
|
+
it('should throw error for missing hasMore field', async () => {
|
|
278
|
+
const invalidData = {
|
|
279
|
+
results: [],
|
|
280
|
+
nextOffset: 0, // missing hasMore
|
|
281
|
+
};
|
|
282
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(invalidData));
|
|
283
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
284
|
+
await expectError(request, 'Invalid response format');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
describe('API integration', () => {
|
|
288
|
+
it('should call self.fetchAssociations with correct parameters', async () => {
|
|
289
|
+
const emptyData = { results: [], hasMore: false, nextOffset: 0 };
|
|
290
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(emptyData));
|
|
291
|
+
const request = createBasicRequest({
|
|
292
|
+
properties: ['firstname'],
|
|
293
|
+
pageLength: 50,
|
|
294
|
+
});
|
|
295
|
+
await fetchAssociations(request);
|
|
296
|
+
expect(mockFetchAssociations).toHaveBeenCalledWith(request, undefined);
|
|
297
|
+
});
|
|
298
|
+
it('should return cleanup function from API response', async () => {
|
|
299
|
+
const mockCleanup = jest.fn();
|
|
300
|
+
const emptyData = { results: [], hasMore: false, nextOffset: 0 };
|
|
301
|
+
const mockApiResponse = { data: emptyData, cleanup: mockCleanup };
|
|
302
|
+
mockFetchAssociations.mockResolvedValueOnce({
|
|
303
|
+
ok: true,
|
|
304
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
305
|
+
});
|
|
306
|
+
const result = await fetchAssociations(createBasicRequest());
|
|
307
|
+
expect(result.cleanup).toBe(mockCleanup);
|
|
308
|
+
result.cleanup();
|
|
309
|
+
expect(mockCleanup).toHaveBeenCalledTimes(1);
|
|
310
|
+
});
|
|
311
|
+
it('should provide default cleanup function when none provided', async () => {
|
|
312
|
+
const emptyData = { results: [], hasMore: false, nextOffset: 0 };
|
|
313
|
+
const mockApiResponse = { data: emptyData }; // no cleanup function provided
|
|
314
|
+
mockFetchAssociations.mockResolvedValueOnce({
|
|
315
|
+
ok: true,
|
|
316
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
317
|
+
});
|
|
318
|
+
const result = await fetchAssociations(createBasicRequest());
|
|
319
|
+
expect(typeof result.cleanup).toBe('function');
|
|
320
|
+
expect(() => result.cleanup()).not.toThrow();
|
|
321
|
+
});
|
|
322
|
+
it('should successfully fetch associations with valid response', async () => {
|
|
323
|
+
const validData = {
|
|
324
|
+
results: [
|
|
325
|
+
{
|
|
326
|
+
toObjectId: 2000,
|
|
327
|
+
associationTypes: [
|
|
328
|
+
{ category: 'HUBSPOT_DEFINED', typeId: 1, label: 'Primary' },
|
|
329
|
+
],
|
|
330
|
+
properties: {
|
|
331
|
+
firstname: 'John',
|
|
332
|
+
lastname: 'Doe',
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
hasMore: true,
|
|
337
|
+
nextOffset: 100,
|
|
338
|
+
};
|
|
339
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(validData));
|
|
340
|
+
const request = createBasicRequest();
|
|
341
|
+
const result = await fetchAssociations(request);
|
|
342
|
+
expect(mockFetchAssociations).toHaveBeenCalledWith(request, undefined);
|
|
343
|
+
expect(result.data).toEqual(validData);
|
|
344
|
+
expect(typeof result.cleanup).toBe('function');
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
describe('options handling', () => {
|
|
348
|
+
it('should pass formatting options to the underlying fetch function', async () => {
|
|
349
|
+
const emptyData = { results: [], hasMore: false, nextOffset: 0 };
|
|
350
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(emptyData));
|
|
351
|
+
const options = {
|
|
352
|
+
propertiesToFormat: ['firstname'],
|
|
353
|
+
formattingOptions: {
|
|
354
|
+
date: { format: 'MM/dd/yyyy' },
|
|
355
|
+
currency: { addSymbol: true },
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
const request = createBasicRequest();
|
|
359
|
+
await fetchAssociations(request, options);
|
|
360
|
+
expect(mockFetchAssociations).toHaveBeenCalledWith(request, options);
|
|
361
|
+
});
|
|
362
|
+
it('should handle propertiesToFormat set to "all"', async () => {
|
|
363
|
+
const emptyData = { results: [], hasMore: false, nextOffset: 0 };
|
|
364
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse(emptyData));
|
|
365
|
+
const options = {
|
|
366
|
+
propertiesToFormat: 'all',
|
|
367
|
+
formattingOptions: {
|
|
368
|
+
dateTime: { relative: true },
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
const request = createBasicRequest();
|
|
372
|
+
await fetchAssociations(request, options);
|
|
373
|
+
expect(mockFetchAssociations).toHaveBeenCalledWith(request, options);
|
|
374
|
+
});
|
|
375
|
+
it('should preserve error handling with formatting options', async () => {
|
|
376
|
+
mockFetchAssociations.mockRejectedValueOnce(new Error('Network error'));
|
|
377
|
+
const options = {
|
|
378
|
+
propertiesToFormat: ['firstname'],
|
|
379
|
+
formattingOptions: {
|
|
380
|
+
date: { format: 'MM/dd/yyyy' },
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
384
|
+
await expect(fetchAssociations(request, options)).rejects.toThrow('Network error');
|
|
385
|
+
expect(mockFetchAssociations).toHaveBeenCalledWith(request, options);
|
|
386
|
+
});
|
|
387
|
+
it('should preserve response validation with formatting options', async () => {
|
|
388
|
+
mockFetchAssociations.mockResolvedValueOnce(createMockResponse('Invalid response')); // data should be an object
|
|
389
|
+
const options = {
|
|
390
|
+
propertiesToFormat: 'all',
|
|
391
|
+
};
|
|
392
|
+
const request = createBasicRequest({ properties: ['firstname'] });
|
|
393
|
+
await expect(fetchAssociations(request, options)).rejects.toThrow('Invalid response format');
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
});
|
|
@@ -8,15 +8,19 @@ Object.defineProperty(global, 'self', {
|
|
|
8
8
|
value: mockSelf,
|
|
9
9
|
writable: true,
|
|
10
10
|
});
|
|
11
|
-
import { fetchCrmProperties
|
|
11
|
+
import { fetchCrmProperties } from '../../../experimental/crm/fetchCrmProperties';
|
|
12
|
+
const DEFAULT_OPTIONS = {};
|
|
12
13
|
describe('fetchCrmProperties', () => {
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
jest.clearAllMocks();
|
|
15
16
|
});
|
|
16
17
|
it('successfully fetches CRM properties', async () => {
|
|
17
18
|
const mockApiResponse = {
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
data: {
|
|
20
|
+
firstname: 'Test value for firstname',
|
|
21
|
+
lastname: 'Test value for lastname',
|
|
22
|
+
},
|
|
23
|
+
cleanup: jest.fn(),
|
|
20
24
|
};
|
|
21
25
|
const mockResponse = {
|
|
22
26
|
ok: true,
|
|
@@ -24,42 +28,97 @@ describe('fetchCrmProperties', () => {
|
|
|
24
28
|
};
|
|
25
29
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
26
30
|
const propertyNames = ['firstname', 'lastname'];
|
|
27
|
-
const result = await fetchCrmProperties(propertyNames, jest.fn());
|
|
28
|
-
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function));
|
|
31
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
32
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), DEFAULT_OPTIONS);
|
|
29
33
|
expect(result).toEqual({
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
data: {
|
|
35
|
+
firstname: 'Test value for firstname',
|
|
36
|
+
lastname: 'Test value for lastname',
|
|
37
|
+
},
|
|
38
|
+
cleanup: expect.any(Function),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('successfully fetches CRM properties with null values', async () => {
|
|
42
|
+
const mockApiResponse = {
|
|
43
|
+
data: {
|
|
44
|
+
firstname: 'John',
|
|
45
|
+
lastname: null,
|
|
46
|
+
email: 'john@example.com',
|
|
47
|
+
phone: null,
|
|
48
|
+
},
|
|
49
|
+
cleanup: jest.fn(),
|
|
50
|
+
};
|
|
51
|
+
const mockResponse = {
|
|
52
|
+
ok: true,
|
|
53
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
54
|
+
};
|
|
55
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
56
|
+
const propertyNames = ['firstname', 'lastname', 'email', 'phone'];
|
|
57
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
58
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), DEFAULT_OPTIONS);
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
data: {
|
|
61
|
+
firstname: 'John',
|
|
62
|
+
lastname: null,
|
|
63
|
+
email: 'john@example.com',
|
|
64
|
+
phone: null,
|
|
65
|
+
},
|
|
66
|
+
cleanup: expect.any(Function),
|
|
32
67
|
});
|
|
33
68
|
});
|
|
34
69
|
it('throws an error when response is not OK', async () => {
|
|
35
70
|
const mockResponse = {
|
|
36
71
|
ok: false,
|
|
37
72
|
statusText: 'Not Found',
|
|
73
|
+
json: jest.fn().mockResolvedValue({}),
|
|
38
74
|
};
|
|
39
75
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
40
76
|
const propertyNames = ['firstname'];
|
|
41
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Failed to fetch CRM properties: Not Found');
|
|
77
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Failed to fetch CRM properties: Not Found');
|
|
42
78
|
});
|
|
43
79
|
it('throws an error when fetch fails', async () => {
|
|
44
80
|
mockFetchCrmProperties.mockRejectedValue(new Error('Network error'));
|
|
45
81
|
const propertyNames = ['firstname'];
|
|
46
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Network error');
|
|
82
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Network error');
|
|
47
83
|
});
|
|
48
84
|
it('throws an error if the response is not an object', async () => {
|
|
49
|
-
const mockApiResponse =
|
|
85
|
+
const mockApiResponse = {
|
|
86
|
+
data: 'Invalid response',
|
|
87
|
+
cleanup: jest.fn(),
|
|
88
|
+
};
|
|
50
89
|
const mockResponse = {
|
|
51
90
|
ok: true,
|
|
52
91
|
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
53
92
|
};
|
|
54
93
|
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
55
94
|
const propertyNames = ['firstname'];
|
|
56
|
-
await expect(fetchCrmProperties(propertyNames, jest.fn())).rejects.toThrow('Invalid response format');
|
|
95
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Invalid response format');
|
|
96
|
+
});
|
|
97
|
+
it('throws an error if response contains invalid property values', async () => {
|
|
98
|
+
const mockApiResponse = {
|
|
99
|
+
data: {
|
|
100
|
+
firstname: 'John',
|
|
101
|
+
lastname: 123,
|
|
102
|
+
email: 'john@example.com',
|
|
103
|
+
},
|
|
104
|
+
cleanup: jest.fn(),
|
|
105
|
+
};
|
|
106
|
+
const mockResponse = {
|
|
107
|
+
ok: true,
|
|
108
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
109
|
+
};
|
|
110
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
111
|
+
const propertyNames = ['firstname', 'lastname', 'email'];
|
|
112
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS)).rejects.toThrow('Invalid response format');
|
|
57
113
|
});
|
|
58
114
|
it('passes the propertiesUpdatedCallback and allows it to be called', async () => {
|
|
59
115
|
let capturedCallback;
|
|
60
116
|
const mockApiResponse = {
|
|
61
|
-
|
|
62
|
-
|
|
117
|
+
data: {
|
|
118
|
+
firstname: 'Initial',
|
|
119
|
+
lastname: 'Initial',
|
|
120
|
+
},
|
|
121
|
+
cleanup: jest.fn(),
|
|
63
122
|
};
|
|
64
123
|
const mockResponse = {
|
|
65
124
|
ok: true,
|
|
@@ -71,7 +130,7 @@ describe('fetchCrmProperties', () => {
|
|
|
71
130
|
});
|
|
72
131
|
const propertyNames = ['firstname', 'lastname'];
|
|
73
132
|
const mockCallback = jest.fn();
|
|
74
|
-
await fetchCrmProperties(propertyNames, mockCallback);
|
|
133
|
+
await fetchCrmProperties(propertyNames, mockCallback, DEFAULT_OPTIONS);
|
|
75
134
|
expect(typeof capturedCallback).toBe('function');
|
|
76
135
|
// Simulate the callback being called with new properties
|
|
77
136
|
const updatedProps = { firstname: 'Updated', lastname: 'Updated' };
|
|
@@ -80,4 +139,111 @@ describe('fetchCrmProperties', () => {
|
|
|
80
139
|
}
|
|
81
140
|
expect(mockCallback).toHaveBeenCalledWith(updatedProps);
|
|
82
141
|
});
|
|
142
|
+
it('passes the propertiesUpdatedCallback and allows it to be called with null values', async () => {
|
|
143
|
+
let capturedCallback;
|
|
144
|
+
const mockApiResponse = {
|
|
145
|
+
data: {
|
|
146
|
+
firstname: 'Initial',
|
|
147
|
+
lastname: null,
|
|
148
|
+
},
|
|
149
|
+
cleanup: jest.fn(),
|
|
150
|
+
};
|
|
151
|
+
const mockResponse = {
|
|
152
|
+
ok: true,
|
|
153
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
154
|
+
};
|
|
155
|
+
mockFetchCrmProperties.mockImplementation((propertyNames, callback) => {
|
|
156
|
+
capturedCallback = callback;
|
|
157
|
+
return Promise.resolve(mockResponse);
|
|
158
|
+
});
|
|
159
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
160
|
+
const mockCallback = jest.fn();
|
|
161
|
+
await fetchCrmProperties(propertyNames, mockCallback, DEFAULT_OPTIONS);
|
|
162
|
+
expect(typeof capturedCallback).toBe('function');
|
|
163
|
+
// Simulate the callback being called with new properties including null values
|
|
164
|
+
const updatedProps = { firstname: null, lastname: 'Updated Value' };
|
|
165
|
+
if (capturedCallback) {
|
|
166
|
+
capturedCallback(updatedProps);
|
|
167
|
+
}
|
|
168
|
+
expect(mockCallback).toHaveBeenCalledWith(updatedProps);
|
|
169
|
+
});
|
|
170
|
+
it('passes formatting options to the underlying fetch function', async () => {
|
|
171
|
+
const mockApiResponse = {
|
|
172
|
+
data: {
|
|
173
|
+
firstname: 'John',
|
|
174
|
+
lastname: 'Doe',
|
|
175
|
+
},
|
|
176
|
+
cleanup: jest.fn(),
|
|
177
|
+
};
|
|
178
|
+
const mockResponse = {
|
|
179
|
+
ok: true,
|
|
180
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
181
|
+
};
|
|
182
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
183
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
184
|
+
const options = {
|
|
185
|
+
propertiesToFormat: ['firstname'],
|
|
186
|
+
formattingOptions: {
|
|
187
|
+
date: {
|
|
188
|
+
format: 'MM/dd/yyyy',
|
|
189
|
+
},
|
|
190
|
+
currency: {
|
|
191
|
+
addSymbol: true,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
await fetchCrmProperties(propertyNames, jest.fn(), options);
|
|
196
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), options);
|
|
197
|
+
});
|
|
198
|
+
it('preserves error handling with formatting options', async () => {
|
|
199
|
+
mockFetchCrmProperties.mockRejectedValue(new Error('Network error'));
|
|
200
|
+
const propertyNames = ['firstname'];
|
|
201
|
+
const options = {
|
|
202
|
+
propertiesToFormat: ['firstname'],
|
|
203
|
+
formattingOptions: {
|
|
204
|
+
date: {
|
|
205
|
+
format: 'MM/dd/yyyy',
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), options)).rejects.toThrow('Network error');
|
|
210
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames, expect.any(Function), options);
|
|
211
|
+
});
|
|
212
|
+
it('preserves response validation with formatting options', async () => {
|
|
213
|
+
const mockApiResponse = {
|
|
214
|
+
data: 'Invalid response',
|
|
215
|
+
cleanup: jest.fn(),
|
|
216
|
+
};
|
|
217
|
+
const mockResponse = {
|
|
218
|
+
ok: true,
|
|
219
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
220
|
+
};
|
|
221
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
222
|
+
const propertyNames = ['firstname'];
|
|
223
|
+
const options = {
|
|
224
|
+
propertiesToFormat: 'all',
|
|
225
|
+
};
|
|
226
|
+
await expect(fetchCrmProperties(propertyNames, jest.fn(), options)).rejects.toThrow('Invalid response format');
|
|
227
|
+
});
|
|
228
|
+
it('returns cleanup function that can be called', async () => {
|
|
229
|
+
const mockCleanup = jest.fn();
|
|
230
|
+
const mockApiResponse = {
|
|
231
|
+
data: {
|
|
232
|
+
firstname: 'John',
|
|
233
|
+
},
|
|
234
|
+
cleanup: mockCleanup,
|
|
235
|
+
};
|
|
236
|
+
const mockResponse = {
|
|
237
|
+
ok: true,
|
|
238
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
239
|
+
};
|
|
240
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
241
|
+
const propertyNames = ['firstname'];
|
|
242
|
+
const result = await fetchCrmProperties(propertyNames, jest.fn(), DEFAULT_OPTIONS);
|
|
243
|
+
expect(result.cleanup).toBe(mockCleanup);
|
|
244
|
+
expect(typeof result.cleanup).toBe('function');
|
|
245
|
+
// Verify cleanup can be called without errors
|
|
246
|
+
result.cleanup();
|
|
247
|
+
expect(mockCleanup).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
83
249
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|