@hubspot/ui-extensions 0.11.4 → 0.11.6
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__/crm/hooks/useAssociations.spec.js +96 -0
- package/dist/__tests__/crm/hooks/useCrmProperties.spec.js +170 -1
- package/dist/crm/hooks/useAssociations.d.ts +2 -0
- package/dist/crm/hooks/useAssociations.js +87 -0
- package/dist/crm/hooks/useCrmProperties.d.ts +5 -1
- package/dist/crm/hooks/useCrmProperties.js +81 -2
- package/dist/hooks/useExtensionActions.d.ts +4 -0
- package/dist/hooks/useExtensionActions.js +6 -0
- package/dist/hooks/useExtensionContext.d.ts +4 -0
- package/dist/hooks/useExtensionContext.js +6 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/internal/global-utils.js +4 -0
- package/dist/internal/hook-utils.d.ts +10 -3
- package/dist/internal/hook-utils.js +10 -1
- package/dist/shared/types/components/accordion.d.ts +5 -5
- package/dist/shared/types/components/alert.d.ts +2 -2
- package/dist/shared/types/components/button-row.d.ts +5 -2
- package/dist/shared/types/components/button.d.ts +16 -10
- package/dist/shared/types/components/chart.d.ts +3 -3
- package/dist/shared/types/components/description-list.d.ts +2 -2
- package/dist/shared/types/components/dropdown.d.ts +8 -8
- package/dist/shared/types/components/empty-state.d.ts +5 -7
- package/dist/shared/types/components/error-state.d.ts +2 -2
- package/dist/shared/types/components/form.d.ts +2 -2
- package/dist/shared/types/components/heading.d.ts +1 -1
- package/dist/shared/types/components/icon.d.ts +4 -5
- package/dist/shared/types/components/illustration.d.ts +12 -0
- package/dist/shared/types/components/image.d.ts +9 -4
- package/dist/shared/types/components/inputs.d.ts +51 -64
- package/dist/shared/types/components/layouts.d.ts +17 -24
- package/dist/shared/types/components/link.d.ts +8 -5
- package/dist/shared/types/components/loading-spinner.d.ts +3 -3
- package/dist/shared/types/components/modal.d.ts +5 -5
- package/dist/shared/types/components/panel.d.ts +7 -7
- package/dist/shared/types/components/progress-bar.d.ts +4 -4
- package/dist/shared/types/components/selects.d.ts +11 -20
- package/dist/shared/types/components/statistics.d.ts +2 -2
- package/dist/shared/types/components/status-tag.d.ts +5 -5
- package/dist/shared/types/components/step-indicator.d.ts +5 -7
- package/dist/shared/types/components/table.d.ts +22 -12
- package/dist/shared/types/components/tabs.d.ts +10 -10
- package/dist/shared/types/components/tag.d.ts +2 -2
- package/dist/shared/types/components/text.d.ts +15 -21
- package/dist/shared/types/components/tile.d.ts +2 -2
- package/dist/shared/types/components/toggle.d.ts +12 -14
- package/dist/shared/types/components/toggleInputs.d.ts +26 -19
- package/dist/shared/types/components/tooltip.d.ts +1 -1
- package/dist/shared/types/crm.d.ts +52 -0
- package/dist/shared/types/http-requests.d.ts +2 -2
- package/dist/shared/types/shared.d.ts +123 -78
- package/dist/shared/types/shared.js +123 -78
- package/dist/shared/types/worker-globals.d.ts +15 -0
- package/dist/{experimental/testing → testing}/__tests__/debug.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/find.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/findAll.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/findAllChildren.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/findByTestId.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/findChild.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/fragments.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/invalid-components.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/isMatch.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/logger.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/maybeFind.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/maybeFindByTestId.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/maybeFindChild.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/mocks.runServerlessFunction.spec.js +1 -1
- package/dist/testing/__tests__/mocks.useAssociations.spec.js +135 -0
- package/dist/testing/__tests__/mocks.useCrmProperties.spec.js +106 -0
- package/dist/testing/__tests__/mocks.useExtensionActions.spec.js +32 -0
- package/dist/testing/__tests__/mocks.useExtensionContext.spec.js +46 -0
- package/dist/{experimental/testing → testing}/__tests__/props.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/testId.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/trigger.spec.js +1 -1
- package/dist/{experimental/testing → testing}/__tests__/type-utils.spec.js +1 -1
- package/dist/testing/__tests__/waitFor.spec.d.ts +1 -0
- package/dist/{experimental/testing → testing}/__tests__/waitFor.spec.js +1 -1
- package/dist/{experimental/testing → testing}/internal/convert.js +1 -1
- package/dist/{experimental/testing → testing}/internal/element.d.ts +1 -1
- package/dist/{experimental/testing → testing}/internal/errors.js +1 -1
- package/dist/{experimental/testing → testing}/internal/match.d.ts +1 -1
- package/dist/{experimental/testing → testing}/internal/mocks/index.d.ts +1 -1
- package/dist/{experimental/testing → testing}/internal/mocks/mock-extension-point-api.d.ts +1 -1
- package/dist/{experimental/testing → testing}/internal/mocks/mock-extension-point-api.js +1 -1
- package/dist/testing/internal/mocks/mock-hooks.d.ts +3 -0
- package/dist/{experimental/testing → testing}/internal/mocks/mock-hooks.js +14 -0
- package/dist/{experimental/testing → testing}/internal/print.js +1 -1
- package/dist/{experimental/testing → testing}/internal/query.d.ts +1 -1
- package/dist/{experimental/testing → testing}/internal/query.js +1 -1
- package/dist/{experimental/testing → testing}/internal/types-internal.d.ts +7 -3
- package/dist/testing/internal/types-internal.js +1 -0
- package/dist/{experimental/testing → testing}/render.d.ts +1 -1
- package/dist/{experimental/testing → testing}/render.js +7 -7
- package/dist/{experimental/testing → testing}/types.d.ts +25 -5
- package/dist/{experimental/testing → testing}/utils.d.ts +1 -1
- package/dist/{experimental/testing → testing}/utils.js +1 -1
- package/package.json +3 -3
- package/dist/experimental/testing/__tests__/mocks.useAssociations.spec.js +0 -47
- package/dist/experimental/testing/__tests__/mocks.useCrmProperties.spec.js +0 -58
- package/dist/experimental/testing/internal/mocks/mock-hooks.d.ts +0 -2
- /package/dist/{experimental/testing → testing}/__tests__/createRenderer.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/createRenderer.spec.js +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/debug.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/find.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/findAll.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/findAllChildren.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/findByTestId.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/findChild.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/fragments.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/invalid-components.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/isMatch.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/logger.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/maybeFind.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/maybeFindByTestId.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/maybeFindChild.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.actions.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.actions.spec.js +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.context.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.context.spec.js +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.runServerlessFunction.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.useAssociations.spec.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/__tests__/mocks.useCrmProperties.spec.d.ts +0 -0
- /package/dist/{experimental/testing/__tests__/props.spec.d.ts → testing/__tests__/mocks.useExtensionActions.spec.d.ts} +0 -0
- /package/dist/{experimental/testing/__tests__/testId.spec.d.ts → testing/__tests__/mocks.useExtensionContext.spec.d.ts} +0 -0
- /package/dist/{experimental/testing/__tests__/trigger.spec.d.ts → testing/__tests__/props.spec.d.ts} +0 -0
- /package/dist/{experimental/testing/__tests__/type-utils.spec.d.ts → testing/__tests__/testId.spec.d.ts} +0 -0
- /package/dist/{experimental/testing/__tests__/waitFor.spec.d.ts → testing/__tests__/trigger.spec.d.ts} +0 -0
- /package/dist/{experimental/testing/internal/types-internal.js → testing/__tests__/type-utils.spec.d.ts} +0 -0
- /package/dist/{experimental/testing → testing}/index.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/index.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/constants.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/constants.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/convert.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/debug.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/debug.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/document.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/document.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/element.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/errors.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/fragment.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/fragment.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/match.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/mocks/index.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/print.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/root.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/root.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/text.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/text.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/type-utils-internal.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/type-utils-internal.js +0 -0
- /package/dist/{experimental/testing → testing}/internal/utils/promise-utils.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/internal/utils/promise-utils.js +0 -0
- /package/dist/{experimental/testing → testing}/type-utils.d.ts +0 -0
- /package/dist/{experimental/testing → testing}/type-utils.js +0 -0
- /package/dist/{experimental/testing → testing}/types.js +0 -0
|
@@ -21,6 +21,16 @@ vi.mock('../../../crm/utils/fetchAssociations.ts', async (importOriginal) => {
|
|
|
21
21
|
});
|
|
22
22
|
// Get reference to the mocked function
|
|
23
23
|
const mockFetchAssociations = fetchAssociations;
|
|
24
|
+
// Shared test helpers
|
|
25
|
+
const createAssociation = (id) => ({
|
|
26
|
+
toObjectId: id,
|
|
27
|
+
associationTypes: [],
|
|
28
|
+
properties: {},
|
|
29
|
+
});
|
|
30
|
+
const mockResponse = (results, hasMore = false, nextOffset = 10) => ({
|
|
31
|
+
data: { results, hasMore, nextOffset },
|
|
32
|
+
cleanup: vi.fn(),
|
|
33
|
+
});
|
|
24
34
|
describe('useAssociations with Pagination', () => {
|
|
25
35
|
let originalError;
|
|
26
36
|
beforeAll(() => {
|
|
@@ -539,4 +549,90 @@ describe('useAssociations with Pagination', () => {
|
|
|
539
549
|
});
|
|
540
550
|
});
|
|
541
551
|
});
|
|
552
|
+
describe('refetch', () => {
|
|
553
|
+
it('should handle complete refetch lifecycle with loading states and data preservation', async () => {
|
|
554
|
+
const initialData = [createAssociation(1)];
|
|
555
|
+
const refetchedData = [createAssociation(2)];
|
|
556
|
+
let resolveRefetch;
|
|
557
|
+
const refetchPromise = new Promise((resolve) => {
|
|
558
|
+
resolveRefetch = resolve;
|
|
559
|
+
});
|
|
560
|
+
mockFetchAssociations
|
|
561
|
+
.mockResolvedValueOnce(mockResponse(initialData))
|
|
562
|
+
.mockImplementationOnce(() => refetchPromise);
|
|
563
|
+
const { result } = renderHook(() => useAssociations({ toObjectType: '0-1', pageLength: 10 }));
|
|
564
|
+
await waitFor(() => {
|
|
565
|
+
expect(result.current.results).toEqual(initialData);
|
|
566
|
+
expect(result.current.isLoading).toBe(false);
|
|
567
|
+
expect(result.current.isRefetching).toBe(false);
|
|
568
|
+
});
|
|
569
|
+
const refetchCall = result.current.refetch();
|
|
570
|
+
await waitFor(() => expect(result.current.isRefetching).toBe(true));
|
|
571
|
+
expect(result.current.results).toEqual(initialData);
|
|
572
|
+
resolveRefetch(mockResponse(refetchedData, true, 20));
|
|
573
|
+
await refetchCall;
|
|
574
|
+
await waitFor(() => {
|
|
575
|
+
expect(result.current.isRefetching).toBe(false);
|
|
576
|
+
expect(result.current.results).toEqual(refetchedData);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
it('should handle errors during refetch and allow recovery', async () => {
|
|
580
|
+
const initialData = [createAssociation(1)];
|
|
581
|
+
const recoveredData = [createAssociation(2)];
|
|
582
|
+
mockFetchAssociations
|
|
583
|
+
.mockResolvedValueOnce(mockResponse(initialData))
|
|
584
|
+
.mockRejectedValueOnce(new Error('Failed to refetch associations'))
|
|
585
|
+
.mockResolvedValueOnce(mockResponse(recoveredData));
|
|
586
|
+
const { result } = renderHook(() => useAssociations({ toObjectType: '0-1', pageLength: 10 }));
|
|
587
|
+
await waitFor(() => expect(result.current.results).toEqual(initialData));
|
|
588
|
+
await result.current.refetch();
|
|
589
|
+
await waitFor(() => {
|
|
590
|
+
expect(result.current.error?.message).toBe('Failed to refetch associations');
|
|
591
|
+
expect(result.current.results).toEqual(initialData);
|
|
592
|
+
});
|
|
593
|
+
await result.current.refetch();
|
|
594
|
+
await waitFor(() => {
|
|
595
|
+
expect(result.current.error).toBeNull();
|
|
596
|
+
expect(result.current.results).toEqual(recoveredData);
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
it('should cancel in-flight refetch when called again', async () => {
|
|
600
|
+
const initialData = [createAssociation(1)];
|
|
601
|
+
const firstRefetchCleanup = vi.fn();
|
|
602
|
+
const secondRefetchCleanup = vi.fn();
|
|
603
|
+
mockFetchAssociations
|
|
604
|
+
.mockResolvedValueOnce(mockResponse(initialData))
|
|
605
|
+
.mockResolvedValueOnce({
|
|
606
|
+
...mockResponse([createAssociation(2)], false, 20),
|
|
607
|
+
cleanup: firstRefetchCleanup,
|
|
608
|
+
})
|
|
609
|
+
.mockResolvedValueOnce({
|
|
610
|
+
...mockResponse([createAssociation(3)], false, 30),
|
|
611
|
+
cleanup: secondRefetchCleanup,
|
|
612
|
+
});
|
|
613
|
+
const { result } = renderHook(() => useAssociations({ toObjectType: '0-1', pageLength: 10 }));
|
|
614
|
+
await waitFor(() => expect(result.current.results).toEqual(initialData));
|
|
615
|
+
await Promise.all([result.current.refetch(), result.current.refetch()]);
|
|
616
|
+
await waitFor(() => expect(result.current.results).toEqual([createAssociation(3)]));
|
|
617
|
+
expect(firstRefetchCleanup).toHaveBeenCalledTimes(1);
|
|
618
|
+
expect(secondRefetchCleanup).not.toHaveBeenCalled();
|
|
619
|
+
});
|
|
620
|
+
it('should maintain current page and pagination state during refetch', async () => {
|
|
621
|
+
mockFetchAssociations
|
|
622
|
+
.mockResolvedValueOnce(mockResponse([createAssociation(1)], true, 10))
|
|
623
|
+
.mockResolvedValueOnce(mockResponse([createAssociation(2)], true, 20))
|
|
624
|
+
.mockResolvedValueOnce(mockResponse([createAssociation(2)], true, 20));
|
|
625
|
+
const { result } = renderHook(() => useAssociations({ toObjectType: '0-1', pageLength: 10 }));
|
|
626
|
+
await waitFor(() => expect(result.current.pagination.currentPage).toBe(1));
|
|
627
|
+
result.current.pagination.nextPage();
|
|
628
|
+
await waitFor(() => expect(result.current.pagination.currentPage).toBe(2));
|
|
629
|
+
await result.current.refetch();
|
|
630
|
+
await waitFor(() => {
|
|
631
|
+
expect(result.current.pagination.currentPage).toBe(2);
|
|
632
|
+
expect(result.current.pagination.hasNextPage).toBe(true);
|
|
633
|
+
expect(result.current.pagination.hasPreviousPage).toBe(true);
|
|
634
|
+
});
|
|
635
|
+
expect(mockFetchAssociations).toHaveBeenLastCalledWith(expect.objectContaining({ offset: 10 }), expect.any(Object));
|
|
636
|
+
});
|
|
637
|
+
});
|
|
542
638
|
});
|
|
@@ -21,7 +21,9 @@ describe('useCrmProperties', () => {
|
|
|
21
21
|
// Suppress React act() warning coming from @testing-library/react
|
|
22
22
|
originalError = console.error;
|
|
23
23
|
console.error = (...args) => {
|
|
24
|
-
if (args[0]
|
|
24
|
+
if (typeof args[0] === 'string' &&
|
|
25
|
+
(args[0].includes('ReactDOMTestUtils.act') ||
|
|
26
|
+
args[0].includes('was not wrapped in act')))
|
|
25
27
|
return;
|
|
26
28
|
originalError.call(console, ...args);
|
|
27
29
|
};
|
|
@@ -254,4 +256,171 @@ describe('useCrmProperties', () => {
|
|
|
254
256
|
});
|
|
255
257
|
expect(mockFetchCrmProperties).toHaveBeenCalledWith(2, expect.any(Function), {});
|
|
256
258
|
});
|
|
259
|
+
describe('refetch', () => {
|
|
260
|
+
it('should successfully refetch and update properties', async () => {
|
|
261
|
+
const initialData = { firstname: 'John', lastname: 'Doe' };
|
|
262
|
+
const refetchedData = { firstname: 'Jane', lastname: 'Smith' };
|
|
263
|
+
mockFetchCrmProperties
|
|
264
|
+
.mockResolvedValueOnce({
|
|
265
|
+
data: initialData,
|
|
266
|
+
cleanup: vi.fn(),
|
|
267
|
+
})
|
|
268
|
+
.mockResolvedValueOnce({
|
|
269
|
+
data: refetchedData,
|
|
270
|
+
cleanup: vi.fn(),
|
|
271
|
+
});
|
|
272
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
273
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
274
|
+
await waitFor(() => {
|
|
275
|
+
expect(result.current.properties).toEqual(initialData);
|
|
276
|
+
expect(result.current.isLoading).toBe(false);
|
|
277
|
+
expect(result.current.isRefetching).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
// Call refetch and wait for it to complete
|
|
280
|
+
await result.current.refetch();
|
|
281
|
+
// Wait for state to update with refetched data
|
|
282
|
+
await waitFor(() => {
|
|
283
|
+
expect(result.current.properties).toEqual(refetchedData);
|
|
284
|
+
expect(result.current.isRefetching).toBe(false);
|
|
285
|
+
expect(result.current.error).toBeNull();
|
|
286
|
+
});
|
|
287
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledTimes(2);
|
|
288
|
+
});
|
|
289
|
+
it('should set isRefetching to true during refetch', async () => {
|
|
290
|
+
const initialData = { firstname: 'John', lastname: 'Doe' };
|
|
291
|
+
const refetchedData = { firstname: 'Jane', lastname: 'Smith' };
|
|
292
|
+
let resolveRefetch;
|
|
293
|
+
const refetchPromise = new Promise((resolve) => {
|
|
294
|
+
resolveRefetch = resolve;
|
|
295
|
+
});
|
|
296
|
+
mockFetchCrmProperties
|
|
297
|
+
.mockResolvedValueOnce({
|
|
298
|
+
data: initialData,
|
|
299
|
+
cleanup: vi.fn(),
|
|
300
|
+
})
|
|
301
|
+
.mockImplementationOnce(() => refetchPromise);
|
|
302
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
303
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
304
|
+
await waitFor(() => {
|
|
305
|
+
expect(result.current.properties).toEqual(initialData);
|
|
306
|
+
expect(result.current.isLoading).toBe(false);
|
|
307
|
+
expect(result.current.isRefetching).toBe(false);
|
|
308
|
+
});
|
|
309
|
+
// Call refetch but don't await it
|
|
310
|
+
const refetchCall = result.current.refetch();
|
|
311
|
+
// Check that isRefetching is true immediately after calling refetch
|
|
312
|
+
await waitFor(() => {
|
|
313
|
+
expect(result.current.isRefetching).toBe(true);
|
|
314
|
+
});
|
|
315
|
+
// Verify properties haven't changed yet
|
|
316
|
+
expect(result.current.properties).toEqual(initialData);
|
|
317
|
+
expect(result.current.error).toBeNull();
|
|
318
|
+
// Resolve the refetch promise
|
|
319
|
+
resolveRefetch({
|
|
320
|
+
data: refetchedData,
|
|
321
|
+
cleanup: vi.fn(),
|
|
322
|
+
});
|
|
323
|
+
// Wait for refetch to complete
|
|
324
|
+
await refetchCall;
|
|
325
|
+
// Verify isRefetching is false and properties are updated
|
|
326
|
+
await waitFor(() => {
|
|
327
|
+
expect(result.current.isRefetching).toBe(false);
|
|
328
|
+
expect(result.current.properties).toEqual(refetchedData);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
it('should handle refetch errors correctly', async () => {
|
|
332
|
+
const initialData = { firstname: 'John', lastname: 'Doe' };
|
|
333
|
+
const errorMessage = 'Failed to refetch CRM properties';
|
|
334
|
+
mockFetchCrmProperties
|
|
335
|
+
.mockResolvedValueOnce({
|
|
336
|
+
data: initialData,
|
|
337
|
+
cleanup: vi.fn(),
|
|
338
|
+
})
|
|
339
|
+
.mockRejectedValueOnce(new Error(errorMessage));
|
|
340
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
341
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
342
|
+
await waitFor(() => {
|
|
343
|
+
expect(result.current.properties).toEqual(initialData);
|
|
344
|
+
expect(result.current.isLoading).toBe(false);
|
|
345
|
+
expect(result.current.error).toBeNull();
|
|
346
|
+
});
|
|
347
|
+
// Call refetch and wait for it to complete (will throw error)
|
|
348
|
+
await result.current.refetch();
|
|
349
|
+
// Wait for error state to update
|
|
350
|
+
await waitFor(() => {
|
|
351
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
352
|
+
expect(result.current.error?.message).toBe(errorMessage);
|
|
353
|
+
expect(result.current.properties).toEqual(initialData);
|
|
354
|
+
expect(result.current.isRefetching).toBe(false);
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
it('should clear error on successful refetch', async () => {
|
|
358
|
+
const errorMessage = 'Failed to fetch CRM properties';
|
|
359
|
+
const refetchedData = { firstname: 'Jane', lastname: 'Smith' };
|
|
360
|
+
mockFetchCrmProperties
|
|
361
|
+
.mockRejectedValueOnce(new Error(errorMessage))
|
|
362
|
+
.mockResolvedValueOnce({
|
|
363
|
+
data: refetchedData,
|
|
364
|
+
cleanup: vi.fn(),
|
|
365
|
+
});
|
|
366
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
367
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
368
|
+
await waitFor(() => {
|
|
369
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
370
|
+
expect(result.current.error?.message).toBe(errorMessage);
|
|
371
|
+
expect(result.current.isLoading).toBe(false);
|
|
372
|
+
});
|
|
373
|
+
// Call refetch and wait for it to complete
|
|
374
|
+
await result.current.refetch();
|
|
375
|
+
// Wait for error to clear and data to update
|
|
376
|
+
await waitFor(() => {
|
|
377
|
+
expect(result.current.error).toBeNull();
|
|
378
|
+
expect(result.current.properties).toEqual(refetchedData);
|
|
379
|
+
expect(result.current.isRefetching).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
it('should handle multiple rapid refetch calls correctly', async () => {
|
|
383
|
+
const initialData = { firstname: 'John', lastname: 'Doe' };
|
|
384
|
+
const firstRefetchData = { firstname: 'First', lastname: 'Refetch' };
|
|
385
|
+
const secondRefetchData = { firstname: 'Second', lastname: 'Refetch' };
|
|
386
|
+
const initialCleanup = vi.fn();
|
|
387
|
+
const firstRefetchCleanup = vi.fn();
|
|
388
|
+
const secondRefetchCleanup = vi.fn();
|
|
389
|
+
mockFetchCrmProperties
|
|
390
|
+
.mockResolvedValueOnce({
|
|
391
|
+
data: initialData,
|
|
392
|
+
cleanup: initialCleanup,
|
|
393
|
+
})
|
|
394
|
+
.mockResolvedValueOnce({
|
|
395
|
+
data: firstRefetchData,
|
|
396
|
+
cleanup: firstRefetchCleanup,
|
|
397
|
+
})
|
|
398
|
+
.mockResolvedValueOnce({
|
|
399
|
+
data: secondRefetchData,
|
|
400
|
+
cleanup: secondRefetchCleanup,
|
|
401
|
+
});
|
|
402
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
403
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
404
|
+
// Wait for initial fetch to complete
|
|
405
|
+
await waitFor(() => {
|
|
406
|
+
expect(result.current.properties).toEqual(initialData);
|
|
407
|
+
expect(result.current.isLoading).toBe(false);
|
|
408
|
+
});
|
|
409
|
+
// Call refetch twice rapidly (concurrently) - the second should cancel the first
|
|
410
|
+
const firstRefetch = result.current.refetch();
|
|
411
|
+
const secondRefetch = result.current.refetch();
|
|
412
|
+
// Wait for both to complete
|
|
413
|
+
await Promise.all([firstRefetch, secondRefetch]);
|
|
414
|
+
// Verify the final refetch's data is in state
|
|
415
|
+
await waitFor(() => {
|
|
416
|
+
expect(result.current.properties).toEqual(secondRefetchData);
|
|
417
|
+
expect(result.current.isRefetching).toBe(false);
|
|
418
|
+
expect(result.current.error).toBeNull();
|
|
419
|
+
});
|
|
420
|
+
// Verify cancellation behavior: first refetch was cancelled and cleaned up
|
|
421
|
+
expect(firstRefetchCleanup).toHaveBeenCalledTimes(1);
|
|
422
|
+
// Second refetch's cleanup should NOT be called yet (still active subscription)
|
|
423
|
+
expect(secondRefetchCleanup).not.toHaveBeenCalled();
|
|
424
|
+
});
|
|
425
|
+
});
|
|
257
426
|
});
|
|
@@ -17,7 +17,9 @@ export interface UseAssociationsResult {
|
|
|
17
17
|
results: AssociationResult[];
|
|
18
18
|
error: Error | null;
|
|
19
19
|
isLoading: boolean;
|
|
20
|
+
isRefetching: boolean;
|
|
20
21
|
pagination: UseAssociationsPagination;
|
|
22
|
+
refetch: () => Promise<void>;
|
|
21
23
|
}
|
|
22
24
|
/**
|
|
23
25
|
* A hook to fetch and manage associations between CRM objects with pagination support.
|
|
@@ -6,6 +6,7 @@ function createInitialState(pageSize) {
|
|
|
6
6
|
results: [],
|
|
7
7
|
error: null,
|
|
8
8
|
isLoading: true,
|
|
9
|
+
isRefetching: false,
|
|
9
10
|
currentPage: 1,
|
|
10
11
|
pageSize,
|
|
11
12
|
hasMore: false,
|
|
@@ -75,6 +76,27 @@ function associationsReducer(state, action) {
|
|
|
75
76
|
nextOffset: undefined,
|
|
76
77
|
offsetHistory: [],
|
|
77
78
|
};
|
|
79
|
+
case 'REFETCH_START':
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
isRefetching: true,
|
|
83
|
+
error: null,
|
|
84
|
+
};
|
|
85
|
+
case 'REFETCH_SUCCESS':
|
|
86
|
+
return {
|
|
87
|
+
...state,
|
|
88
|
+
isRefetching: false,
|
|
89
|
+
results: action.payload.results,
|
|
90
|
+
hasMore: action.payload.hasMore,
|
|
91
|
+
nextOffset: action.payload.nextOffset,
|
|
92
|
+
error: null,
|
|
93
|
+
};
|
|
94
|
+
case 'REFETCH_ERROR':
|
|
95
|
+
return {
|
|
96
|
+
...state,
|
|
97
|
+
isRefetching: false,
|
|
98
|
+
error: action.payload,
|
|
99
|
+
};
|
|
78
100
|
default:
|
|
79
101
|
return state;
|
|
80
102
|
}
|
|
@@ -97,6 +119,10 @@ function useAssociationsInternal(config, options = DEFAULT_OPTIONS) {
|
|
|
97
119
|
const lastConfigKeyRef = useRef();
|
|
98
120
|
const lastOptionsRef = useRef();
|
|
99
121
|
const lastOptionsKeyRef = useRef();
|
|
122
|
+
// Track in-flight refetch to support cancellation
|
|
123
|
+
const refetchAbortRef = useRef(null);
|
|
124
|
+
// Track refetch cleanup function to prevent memory leaks
|
|
125
|
+
const refetchCleanupRef = useRef(null);
|
|
100
126
|
const stableConfig = useMemo(() => {
|
|
101
127
|
const configKey = JSON.stringify(config);
|
|
102
128
|
if (configKey === lastConfigKeyRef.current) {
|
|
@@ -178,6 +204,11 @@ function useAssociationsInternal(config, options = DEFAULT_OPTIONS) {
|
|
|
178
204
|
if (cleanup) {
|
|
179
205
|
cleanup();
|
|
180
206
|
}
|
|
207
|
+
// Clean up any active refetch subscription
|
|
208
|
+
if (refetchCleanupRef.current) {
|
|
209
|
+
refetchCleanupRef.current();
|
|
210
|
+
refetchCleanupRef.current = null;
|
|
211
|
+
}
|
|
181
212
|
};
|
|
182
213
|
}, [
|
|
183
214
|
stableConfig,
|
|
@@ -186,12 +217,68 @@ function useAssociationsInternal(config, options = DEFAULT_OPTIONS) {
|
|
|
186
217
|
state.currentOffset,
|
|
187
218
|
pageSize,
|
|
188
219
|
]);
|
|
220
|
+
const refetch = useCallback(async () => {
|
|
221
|
+
if (refetchAbortRef.current) {
|
|
222
|
+
refetchAbortRef.current.cancelled = true;
|
|
223
|
+
}
|
|
224
|
+
if (refetchCleanupRef.current) {
|
|
225
|
+
refetchCleanupRef.current();
|
|
226
|
+
refetchCleanupRef.current = null;
|
|
227
|
+
}
|
|
228
|
+
const abortSignal = { cancelled: false };
|
|
229
|
+
refetchAbortRef.current = abortSignal;
|
|
230
|
+
try {
|
|
231
|
+
dispatch({ type: 'REFETCH_START' });
|
|
232
|
+
// Build request using current offset token to refetch current page
|
|
233
|
+
const request = {
|
|
234
|
+
toObjectType: stableConfig?.toObjectType,
|
|
235
|
+
properties: stableConfig?.properties,
|
|
236
|
+
pageLength: pageSize,
|
|
237
|
+
offset: state.currentOffset,
|
|
238
|
+
};
|
|
239
|
+
const result = await fetchAssociations(request, {
|
|
240
|
+
propertiesToFormat: stableOptions.propertiesToFormat,
|
|
241
|
+
formattingOptions: stableOptions.formattingOptions,
|
|
242
|
+
});
|
|
243
|
+
if (!abortSignal.cancelled) {
|
|
244
|
+
dispatch({
|
|
245
|
+
type: 'REFETCH_SUCCESS',
|
|
246
|
+
payload: {
|
|
247
|
+
results: result.data.results,
|
|
248
|
+
hasMore: result.data.hasMore,
|
|
249
|
+
nextOffset: result.data.nextOffset,
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
refetchCleanupRef.current = result.cleanup;
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
if (result.cleanup) {
|
|
256
|
+
result.cleanup();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
if (!abortSignal.cancelled) {
|
|
262
|
+
const errorData = err instanceof Error
|
|
263
|
+
? err
|
|
264
|
+
: new Error('Failed to refetch associations');
|
|
265
|
+
dispatch({ type: 'REFETCH_ERROR', payload: errorData });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
if (refetchAbortRef.current === abortSignal) {
|
|
270
|
+
refetchAbortRef.current = null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}, [stableConfig, stableOptions, state.currentOffset, pageSize]);
|
|
189
274
|
// Calculate pagination flags
|
|
190
275
|
const paginationFlags = calculatePaginationFlags(state.currentPage, state.hasMore);
|
|
191
276
|
return {
|
|
192
277
|
results: state.results,
|
|
193
278
|
error: state.error,
|
|
194
279
|
isLoading: state.isLoading,
|
|
280
|
+
isRefetching: state.isRefetching,
|
|
281
|
+
refetch,
|
|
195
282
|
pagination: {
|
|
196
283
|
hasNextPage: paginationFlags.hasNextPage,
|
|
197
284
|
hasPreviousPage: paginationFlags.hasPreviousPage,
|
|
@@ -3,10 +3,14 @@ export interface CrmPropertiesState {
|
|
|
3
3
|
properties: Record<string, string | null>;
|
|
4
4
|
error: Error | null;
|
|
5
5
|
isLoading: boolean;
|
|
6
|
+
isRefetching: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface UseCrmPropertiesResult extends CrmPropertiesState {
|
|
9
|
+
refetch: () => Promise<void>;
|
|
6
10
|
}
|
|
7
11
|
/**
|
|
8
12
|
* A hook for using and managing CRM properties.
|
|
9
13
|
*/
|
|
10
|
-
declare function useCrmPropertiesInternal(propertyNames: string[], options?: FetchCrmPropertiesOptions):
|
|
14
|
+
declare function useCrmPropertiesInternal(propertyNames: string[], options?: FetchCrmPropertiesOptions): UseCrmPropertiesResult;
|
|
11
15
|
export declare const useCrmProperties: typeof useCrmPropertiesInternal;
|
|
12
16
|
export {};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { useEffect, useReducer, useMemo, useRef } from 'react';
|
|
1
|
+
import { useEffect, useReducer, useMemo, useRef, useCallback } from 'react';
|
|
2
2
|
import { fetchCrmProperties, } from "../utils/fetchCrmProperties.js";
|
|
3
3
|
import { createMockAwareHook } from "../../internal/hook-utils.js";
|
|
4
4
|
const initialState = {
|
|
5
5
|
properties: {},
|
|
6
6
|
error: null,
|
|
7
7
|
isLoading: true,
|
|
8
|
+
isRefetching: false,
|
|
8
9
|
};
|
|
9
10
|
function crmPropertiesReducer(state, action) {
|
|
10
11
|
switch (action.type) {
|
|
@@ -28,6 +29,25 @@ function crmPropertiesReducer(state, action) {
|
|
|
28
29
|
error: action.payload,
|
|
29
30
|
properties: {},
|
|
30
31
|
};
|
|
32
|
+
case 'REFETCH_START':
|
|
33
|
+
return {
|
|
34
|
+
...state,
|
|
35
|
+
isRefetching: true,
|
|
36
|
+
error: null,
|
|
37
|
+
};
|
|
38
|
+
case 'REFETCH_SUCCESS':
|
|
39
|
+
return {
|
|
40
|
+
...state,
|
|
41
|
+
isRefetching: false,
|
|
42
|
+
properties: action.payload,
|
|
43
|
+
error: null,
|
|
44
|
+
};
|
|
45
|
+
case 'REFETCH_ERROR':
|
|
46
|
+
return {
|
|
47
|
+
...state,
|
|
48
|
+
isRefetching: false,
|
|
49
|
+
error: action.payload,
|
|
50
|
+
};
|
|
31
51
|
default:
|
|
32
52
|
return state;
|
|
33
53
|
}
|
|
@@ -49,6 +69,10 @@ function useCrmPropertiesInternal(propertyNames, options = DEFAULT_OPTIONS) {
|
|
|
49
69
|
const lastPropertyNamesKeyRef = useRef();
|
|
50
70
|
const lastOptionsRef = useRef();
|
|
51
71
|
const lastOptionsKeyRef = useRef();
|
|
72
|
+
// Track in-flight refetch to support cancellation
|
|
73
|
+
const refetchAbortRef = useRef(null);
|
|
74
|
+
// Track refetch cleanup function to prevent memory leaks
|
|
75
|
+
const refetchCleanupRef = useRef(null);
|
|
52
76
|
const stablePropertyNames = useMemo(() => {
|
|
53
77
|
if (!Array.isArray(propertyNames)) {
|
|
54
78
|
return propertyNames;
|
|
@@ -104,8 +128,63 @@ function useCrmPropertiesInternal(propertyNames, options = DEFAULT_OPTIONS) {
|
|
|
104
128
|
if (cleanup) {
|
|
105
129
|
cleanup();
|
|
106
130
|
}
|
|
131
|
+
// Clean up any active refetch subscription
|
|
132
|
+
if (refetchCleanupRef.current) {
|
|
133
|
+
refetchCleanupRef.current();
|
|
134
|
+
refetchCleanupRef.current = null;
|
|
135
|
+
}
|
|
107
136
|
};
|
|
108
137
|
}, [stablePropertyNames, stableOptions]);
|
|
109
|
-
|
|
138
|
+
const refetch = useCallback(async () => {
|
|
139
|
+
// Cancel any in-flight refetch
|
|
140
|
+
if (refetchAbortRef.current) {
|
|
141
|
+
refetchAbortRef.current.cancelled = true;
|
|
142
|
+
}
|
|
143
|
+
// Clean up old refetch subscription to prevent memory leaks
|
|
144
|
+
if (refetchCleanupRef.current) {
|
|
145
|
+
refetchCleanupRef.current();
|
|
146
|
+
refetchCleanupRef.current = null;
|
|
147
|
+
}
|
|
148
|
+
// Create new abort signal for this refetch
|
|
149
|
+
const abortSignal = { cancelled: false };
|
|
150
|
+
refetchAbortRef.current = abortSignal;
|
|
151
|
+
try {
|
|
152
|
+
dispatch({ type: 'REFETCH_START' });
|
|
153
|
+
const result = await fetchCrmProperties(stablePropertyNames, (data) => {
|
|
154
|
+
if (!abortSignal.cancelled) {
|
|
155
|
+
dispatch({ type: 'REFETCH_SUCCESS', payload: data });
|
|
156
|
+
}
|
|
157
|
+
}, stableOptions);
|
|
158
|
+
if (!abortSignal.cancelled) {
|
|
159
|
+
dispatch({ type: 'REFETCH_SUCCESS', payload: result.data });
|
|
160
|
+
// Store cleanup for next refetch or unmount
|
|
161
|
+
refetchCleanupRef.current = result.cleanup;
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
// If cancelled, clean up immediately
|
|
165
|
+
if (result.cleanup) {
|
|
166
|
+
result.cleanup();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
if (!abortSignal.cancelled) {
|
|
172
|
+
const errorData = err instanceof Error
|
|
173
|
+
? err
|
|
174
|
+
: new Error('Failed to refetch CRM properties');
|
|
175
|
+
dispatch({ type: 'REFETCH_ERROR', payload: errorData });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
// Clear the abort ref if this is still the current refetch
|
|
180
|
+
if (refetchAbortRef.current === abortSignal) {
|
|
181
|
+
refetchAbortRef.current = null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}, [stablePropertyNames, stableOptions]);
|
|
185
|
+
return {
|
|
186
|
+
...state,
|
|
187
|
+
refetch,
|
|
188
|
+
};
|
|
110
189
|
}
|
|
111
190
|
export const useCrmProperties = createMockAwareHook('useCrmProperties', useCrmPropertiesInternal);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionPoints } from '../shared/types/extension-points.ts';
|
|
2
|
+
declare function useExtensionActionsInternal<ExtensionPoint extends keyof ExtensionPoints>(): import("../shared/types/extension-points.ts").ExtensionPointApiActions<ExtensionPoint>;
|
|
3
|
+
export declare const useExtensionActions: typeof useExtensionActionsInternal;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getWorkerGlobals } from "../internal/global-utils.js";
|
|
2
|
+
import { createMockAwareHook } from "../internal/hook-utils.js";
|
|
3
|
+
function useExtensionActionsInternal() {
|
|
4
|
+
return getWorkerGlobals().hsWorkerAPI.useExtensionActions();
|
|
5
|
+
}
|
|
6
|
+
export const useExtensionActions = createMockAwareHook('useExtensionActions', useExtensionActionsInternal);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ExtensionPoints } from '../shared/types/extension-points.ts';
|
|
2
|
+
declare function useExtensionContextInternal<ExtensionPoint extends keyof ExtensionPoints>(): import("../shared/types/extension-points.ts").ExtensionPointApiContext<ExtensionPoint>;
|
|
3
|
+
export declare const useExtensionContext: typeof useExtensionContextInternal;
|
|
4
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { getWorkerGlobals } from "../internal/global-utils.js";
|
|
2
|
+
import { createMockAwareHook } from "../internal/hook-utils.js";
|
|
3
|
+
function useExtensionContextInternal() {
|
|
4
|
+
return getWorkerGlobals().hsWorkerAPI.useExtensionContext();
|
|
5
|
+
}
|
|
6
|
+
export const useExtensionContext = createMockAwareHook('useExtensionContext', useExtensionContextInternal);
|
package/dist/index.d.ts
CHANGED
|
@@ -3,3 +3,5 @@ export { hubspot } from './hubspot.ts';
|
|
|
3
3
|
export { logger } from './logger.ts';
|
|
4
4
|
export * from './shared/types/index.ts';
|
|
5
5
|
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from './shared/remoteComponents.tsx';
|
|
6
|
+
export { useExtensionContext } from './hooks/useExtensionContext.tsx';
|
|
7
|
+
export { useExtensionActions } from './hooks/useExtensionActions.tsx';
|
package/dist/index.js
CHANGED
|
@@ -4,3 +4,5 @@ export { hubspot } from "./hubspot.js";
|
|
|
4
4
|
export { logger } from "./logger.js";
|
|
5
5
|
export * from "./shared/types/index.js";
|
|
6
6
|
export { Accordion, Alert, AutoGrid, BarChart, Box, Button, ButtonRow, Card, Checkbox, CurrencyInput, DateInput, DescriptionList, DescriptionListItem, Divider, Dropdown, EmptyState, ErrorState, Flex, Form, Heading, Icon, Illustration, Image, Inline, Input, LineChart, Link, List, LoadingButton, LoadingSpinner, Modal, ModalBody, ModalFooter, MultiSelect, NumberInput, Panel, PanelBody, PanelFooter, PanelSection, ProgressBar, RadioButton, SearchInput, Select, Stack, Statistics, StatisticsItem, StatisticsTrend, StatusTag, StepIndicator, StepperInput, Tab, Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, Tag, Text, TextArea, Textarea, Tile, TimeInput, Toggle, ToggleGroup, Tooltip, } from "./shared/remoteComponents.js";
|
|
7
|
+
export { useExtensionContext } from "./hooks/useExtensionContext.js";
|
|
8
|
+
export { useExtensionActions } from "./hooks/useExtensionActions.js";
|
|
@@ -25,6 +25,10 @@ const fakeWorkerGlobals = {
|
|
|
25
25
|
extend_V2: () => {
|
|
26
26
|
// No-op in test environment
|
|
27
27
|
},
|
|
28
|
+
// @ts-expect-error we are not using the worker endpoint in tests env.
|
|
29
|
+
__useExtensionContext: () => {
|
|
30
|
+
// No-op in test environment
|
|
31
|
+
},
|
|
28
32
|
};
|
|
29
33
|
/**
|
|
30
34
|
* Gets the worker globals object for the current environment.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RendererSpies } from '../testing/types.ts';
|
|
2
2
|
type AnyFunction = (...args: any[]) => any;
|
|
3
3
|
/**
|
|
4
4
|
* Creates a mock-aware hook function that can be used to mock the original hook function.
|
|
@@ -8,12 +8,19 @@ type AnyFunction = (...args: any[]) => any;
|
|
|
8
8
|
* @param originalHookFunction The original hook function to call if no mock is found
|
|
9
9
|
* @returns The mocked hook function or the original hook function if no mock is found
|
|
10
10
|
*/
|
|
11
|
-
export declare const createMockAwareHook: <THookName extends keyof
|
|
11
|
+
export declare const createMockAwareHook: <THookName extends keyof RendererSpies, THookFunction extends AnyFunction>(hookName: THookName, originalHookFunction: THookFunction) => THookFunction;
|
|
12
|
+
/**
|
|
13
|
+
* A hook that provides access to the Mocks context.
|
|
14
|
+
* Returns the mocks object if inside a MocksContextProvider, otherwise returns null.
|
|
15
|
+
*
|
|
16
|
+
* @returns The mocks object or null if not in a test environment.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useMocksContext(): RendererSpies<"home" | "settings" | "crm.record.tab" | "crm.record.sidebar" | "crm.preview" | "helpdesk.sidebar" | "uie.playground.middle"> | null;
|
|
12
19
|
/**
|
|
13
20
|
* A React component that provides the Mocks context that can be used to provide mocks to the mock-aware hook functions.
|
|
14
21
|
*
|
|
15
22
|
* @param children The children to render.
|
|
16
23
|
* @returns The children wrapped in the Mocks context provider.
|
|
17
24
|
*/
|
|
18
|
-
export declare const MocksContextProvider: import("react").Provider<
|
|
25
|
+
export declare const MocksContextProvider: import("react").Provider<RendererSpies<"home" | "settings" | "crm.record.tab" | "crm.record.sidebar" | "crm.preview" | "helpdesk.sidebar" | "uie.playground.middle"> | null>;
|
|
19
26
|
export {};
|
|
@@ -10,7 +10,7 @@ const MocksContext = createContext(null);
|
|
|
10
10
|
*/
|
|
11
11
|
export const createMockAwareHook = (hookName, originalHookFunction) => {
|
|
12
12
|
const useWrapper = (...args) => {
|
|
13
|
-
const mocks =
|
|
13
|
+
const mocks = useMocksContext();
|
|
14
14
|
if (!mocks) {
|
|
15
15
|
// If no mocks are provided, call the original hook function
|
|
16
16
|
return originalHookFunction(...args);
|
|
@@ -25,6 +25,15 @@ export const createMockAwareHook = (hookName, originalHookFunction) => {
|
|
|
25
25
|
};
|
|
26
26
|
return useWrapper;
|
|
27
27
|
};
|
|
28
|
+
/**
|
|
29
|
+
* A hook that provides access to the Mocks context.
|
|
30
|
+
* Returns the mocks object if inside a MocksContextProvider, otherwise returns null.
|
|
31
|
+
*
|
|
32
|
+
* @returns The mocks object or null if not in a test environment.
|
|
33
|
+
*/
|
|
34
|
+
export function useMocksContext() {
|
|
35
|
+
return useContext(MocksContext);
|
|
36
|
+
}
|
|
28
37
|
/**
|
|
29
38
|
* A React component that provides the Mocks context that can be used to provide mocks to the mock-aware hook functions.
|
|
30
39
|
*
|