@hubspot/ui-extensions 0.12.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/crm/hooks/useAssociations.spec.js +28 -0
- package/dist/__tests__/crm/utils/fetchAssociations.spec.js +2 -1
- package/dist/__tests__/hooks/useDebounce.spec.js +123 -0
- package/dist/__tests__/hooks/utils/useFetchLifecycle.spec.js +324 -0
- package/dist/__tests__/internal/hook-utils.spec.d.ts +1 -0
- package/dist/__tests__/internal/hook-utils.spec.js +17 -0
- package/dist/__tests__/test-d/extension-points.test-d.js +1 -0
- package/dist/crm/hooks/useAssociations.d.ts +4 -12
- package/dist/crm/hooks/useAssociations.js +46 -138
- package/dist/crm/hooks/useCrmProperties.js +29 -125
- package/dist/crm/utils/fetchAssociations.d.ts +0 -8
- package/dist/crm/utils/fetchAssociations.js +0 -10
- package/dist/hooks/useDebounce.d.ts +19 -0
- package/dist/hooks/useDebounce.js +32 -0
- package/dist/hooks/utils/useFetchLifecycle.d.ts +35 -0
- package/dist/hooks/utils/useFetchLifecycle.js +103 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/hook-utils.d.ts +13 -6
- package/dist/internal/hook-utils.js +24 -10
- package/dist/pages/components/index.d.ts +1 -0
- package/dist/pages/components/index.js +1 -0
- package/dist/{shared/types/pages → pages}/components/page-routes.d.ts +6 -39
- package/dist/pages/components/page-routes.js +62 -0
- package/dist/pages/create-page-router.d.ts +33 -0
- package/dist/pages/create-page-router.js +121 -0
- package/dist/pages/create-page-router.test.d.ts +1 -0
- package/dist/pages/create-page-router.test.js +296 -0
- package/dist/pages/hooks.d.ts +7 -0
- package/dist/pages/hooks.js +14 -0
- package/dist/pages/index.d.ts +6 -0
- package/dist/pages/index.js +4 -0
- package/dist/pages/internal/app-page-route-context.d.ts +16 -0
- package/dist/pages/internal/app-page-route-context.js +12 -0
- package/dist/pages/internal/convert-page-routes-react-elements.d.ts +9 -0
- package/dist/pages/internal/convert-page-routes-react-elements.js +138 -0
- package/dist/pages/internal/page-router-internal-types.d.ts +40 -0
- package/dist/pages/internal/page-router-internal-types.js +5 -0
- package/dist/pages/internal/trie-router.d.ts +31 -0
- package/dist/pages/internal/trie-router.js +141 -0
- package/dist/pages/internal/trie-router.test.d.ts +1 -0
- package/dist/pages/internal/trie-router.test.js +263 -0
- package/dist/pages/internal/useAppPageLocation.d.ts +1 -0
- package/dist/pages/internal/useAppPageLocation.js +13 -0
- package/dist/pages/types.d.ts +28 -0
- package/dist/pages/types.js +1 -0
- package/dist/shared/remoteComponents.d.ts +24 -0
- package/dist/shared/remoteComponents.js +28 -0
- package/dist/shared/types/actions.d.ts +12 -2
- package/dist/shared/types/components/button.d.ts +2 -2
- package/dist/shared/types/components/image.d.ts +2 -2
- package/dist/shared/types/components/inputs.d.ts +7 -1
- package/dist/shared/types/components/link.d.ts +2 -2
- package/dist/shared/types/context.d.ts +5 -0
- package/dist/shared/types/crm.d.ts +4 -0
- package/dist/shared/types/extension-points.d.ts +9 -3
- package/dist/shared/types/extension-points.js +1 -0
- package/dist/shared/types/pages/components/index.d.ts +3 -1
- package/dist/shared/types/pages/components/page-breadcrumbs.d.ts +34 -0
- package/dist/shared/types/pages/components/page-breadcrumbs.js +1 -0
- package/dist/shared/types/pages/components/page-header.d.ts +82 -0
- package/dist/shared/types/pages/components/page-header.js +1 -0
- package/dist/shared/types/pages/components/page-link.d.ts +4 -2
- package/dist/shared/types/pages/components/page-title.d.ts +12 -0
- package/dist/shared/types/pages/components/page-title.js +1 -0
- package/dist/shared/types/shared.d.ts +8 -0
- package/dist/shared/types/worker-globals.d.ts +3 -12
- package/dist/testing/__tests__/createRenderer.spec.js +1 -1
- package/dist/testing/internal/mocks/index.d.ts +14 -6
- package/dist/testing/internal/mocks/index.js +19 -5
- package/dist/testing/internal/mocks/mock-app-page-location.d.ts +7 -0
- package/dist/testing/internal/mocks/mock-app-page-location.js +35 -0
- package/dist/testing/internal/mocks/mock-extension-point-api.js +21 -1
- package/dist/testing/internal/mocks/mock-hooks.js +12 -7
- package/dist/testing/render.js +7 -6
- package/dist/testing/types.d.ts +19 -3
- package/dist/utils/pagination.d.ts +17 -0
- package/dist/utils/pagination.js +10 -0
- package/package.json +2 -2
- package/dist/experimental/pages/index.d.ts +0 -4
- package/dist/experimental/pages/index.js +0 -3
- package/dist/shared/types/pages/app-pages-types.d.ts +0 -75
- /package/dist/{shared/types/pages/app-pages-types.js → __tests__/hooks/useDebounce.spec.d.ts} +0 -0
- /package/dist/{shared/types/pages/components/page-routes.js → __tests__/hooks/utils/useFetchLifecycle.spec.d.ts} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import type { ReactionsHandler } from '../../reactions.ts';
|
|
3
|
+
import type { BaseComponentProps, ExtensionEvent } from '../../shared.ts';
|
|
4
|
+
/**
|
|
5
|
+
* The props type for [PageHeader](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-header).
|
|
6
|
+
*
|
|
7
|
+
* @category Component Props
|
|
8
|
+
*/
|
|
9
|
+
export interface PageHeaderProps extends BaseComponentProps {
|
|
10
|
+
/**
|
|
11
|
+
* Sets the content that will render inside the component. Can contain only <code>PageHeader.PrimaryAction</code> and/or <code>PageHeader.SecondaryActions</code> components.
|
|
12
|
+
*/
|
|
13
|
+
children: ReactElement<PageHeaderPrimaryActionProps> | ReactElement<PageHeaderSecondaryActionsProps>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The props type for [PageHeader.PrimaryAction](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-header#pageheader-primaryaction).
|
|
17
|
+
*
|
|
18
|
+
* @category Component Props
|
|
19
|
+
*/
|
|
20
|
+
export interface PageHeaderPrimaryActionProps extends BaseComponentProps {
|
|
21
|
+
/**
|
|
22
|
+
* Must be a <code>PageHeader.PageLink</code> component (for internal app pages) or a <code>PageHeader.Link</code> component (for URLs outside your app). No other component types are supported.
|
|
23
|
+
*/
|
|
24
|
+
children: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The props type for [PageHeader.SecondaryActions](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-header#pageheader-secondaryactions).
|
|
28
|
+
*
|
|
29
|
+
* @category Component Props
|
|
30
|
+
*/
|
|
31
|
+
export interface PageHeaderSecondaryActionsProps extends BaseComponentProps {
|
|
32
|
+
/**
|
|
33
|
+
* Must contain one or more <code>PageHeader.PageLink</code> component (for internal app pages) or a <code>PageHeader.Link</code> component (for URLs outside your app). No other component types are supported.
|
|
34
|
+
*/
|
|
35
|
+
children: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The props type for [PageHeader.Link](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-header#pageheader-link).
|
|
39
|
+
*
|
|
40
|
+
* @category Component Props
|
|
41
|
+
*/
|
|
42
|
+
export interface PageHeaderLinkProps extends BaseComponentProps {
|
|
43
|
+
/**
|
|
44
|
+
* A URL that will be opened when the button is clicked. If the value is a URL external to `hubspot.com` it will be opened in a new tab.
|
|
45
|
+
*/
|
|
46
|
+
href?: string;
|
|
47
|
+
/**
|
|
48
|
+
* A function that will be invoked when the button is clicked. Do not use this function for submitting a form; use Form's `onSubmit` function instead.
|
|
49
|
+
*
|
|
50
|
+
* @event
|
|
51
|
+
*/
|
|
52
|
+
onClick?: ReactionsHandler<ExtensionEvent>;
|
|
53
|
+
/**
|
|
54
|
+
* Determines whether or not the button should be disabled.
|
|
55
|
+
*/
|
|
56
|
+
disabled?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* The visible label of the link button. This prop is passed implicitly by providing sub-components.
|
|
59
|
+
*/
|
|
60
|
+
children: ReactNode;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The props type for [PageHeader.PageLink](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-header#pageheader-pagelink).
|
|
64
|
+
*
|
|
65
|
+
* @category Component Props
|
|
66
|
+
*/
|
|
67
|
+
export interface PageHeaderPageLinkProps extends BaseComponentProps {
|
|
68
|
+
/**
|
|
69
|
+
* The path to navigate to when the link is clicked. Supports path parameters (e.g. `/view-contact/:contactId`).
|
|
70
|
+
*/
|
|
71
|
+
to: string;
|
|
72
|
+
/**
|
|
73
|
+
* Values for path parameters and query string entries.
|
|
74
|
+
* Parameters matching `:paramName` tokens in `to` are substituted into the path.
|
|
75
|
+
* Any remaining parameters are appended as query string entries.
|
|
76
|
+
*/
|
|
77
|
+
params?: Record<string, string>;
|
|
78
|
+
/**
|
|
79
|
+
* The visible label of the link button. This prop is passed implicitly by providing sub-components.
|
|
80
|
+
*/
|
|
81
|
+
children: ReactNode;
|
|
82
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -7,7 +7,9 @@ import type { BaseComponentProps } from '../../shared.ts';
|
|
|
7
7
|
*/
|
|
8
8
|
export interface PageLinkProps extends BaseComponentProps {
|
|
9
9
|
/**
|
|
10
|
-
* The path to navigate to
|
|
10
|
+
* The path to navigate to. Can be a simple path (e.g., <code>"/docs"</code>) or a path with parameters
|
|
11
|
+
* (e.g., <code>"/view-contact/:contactId"</code>). When using path parameters, provide the parameter
|
|
12
|
+
* values in the <code>params</code> prop.
|
|
11
13
|
*/
|
|
12
14
|
to: string;
|
|
13
15
|
/**
|
|
@@ -17,7 +19,7 @@ export interface PageLinkProps extends BaseComponentProps {
|
|
|
17
19
|
*/
|
|
18
20
|
params?: Record<string, string>;
|
|
19
21
|
/**
|
|
20
|
-
* The content to
|
|
22
|
+
* The content to display inside the link.
|
|
21
23
|
*/
|
|
22
24
|
children: ReactNode;
|
|
23
25
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BaseComponentProps } from '../../shared.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The props type for [PageTitle](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/ui-components/app-page-components/page-title).
|
|
4
|
+
*
|
|
5
|
+
* @category Component Props
|
|
6
|
+
*/
|
|
7
|
+
export interface PageTitleProps extends BaseComponentProps {
|
|
8
|
+
/**
|
|
9
|
+
* The text to display as the page heading. Also sets the browser tab title.
|
|
10
|
+
*/
|
|
11
|
+
children: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import type { ComponentType, ReactElement, ReactNode } from 'react';
|
|
2
2
|
export type UnknownComponentProps = Record<string, any>;
|
|
3
|
+
/**
|
|
4
|
+
* Represents a JSON-serializable value. Functions, Symbols, BigInts, and
|
|
5
|
+
* circular references are not JSON-serializable and will cause issues with
|
|
6
|
+
* utilities that rely on `JSON.stringify` for comparison.
|
|
7
|
+
*/
|
|
8
|
+
export type JsonSerializable = string | number | boolean | null | undefined | JsonSerializable[] | {
|
|
9
|
+
[key: string]: JsonSerializable;
|
|
10
|
+
};
|
|
3
11
|
/**
|
|
4
12
|
* Represents a HubSpot-provided React component.
|
|
5
13
|
*/
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { HubspotExtendFunction } from './extend.ts';
|
|
2
2
|
import type { ExtensionPointApiActions, ExtensionPointApiContext, ExtensionPointFullApi, ExtensionPoints } from './extension-points.ts';
|
|
3
3
|
import type { Logger } from './logger.ts';
|
|
4
|
-
import type {
|
|
5
|
-
import type { PageRoutesComponent } from './pages/components/page-routes.ts';
|
|
4
|
+
import type { AppPageLocation } from './shared.ts';
|
|
6
5
|
export interface WorkersApi {
|
|
7
6
|
/**
|
|
8
7
|
* Hook added to worker globals so customer code can access extension context at runtime.
|
|
@@ -17,17 +16,9 @@ export interface WorkersApi {
|
|
|
17
16
|
*/
|
|
18
17
|
useExtensionApi: <ExtensionPoint extends keyof ExtensionPoints>() => ExtensionPointFullApi<ExtensionPoint>;
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Hook added to worker globals so page router can access the current app page location at runtime.
|
|
21
20
|
*/
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Hook added to worker globals so customer code can access the current page route at runtime.
|
|
25
|
-
*/
|
|
26
|
-
usePageRoute: UsePageRouteHook;
|
|
27
|
-
/**
|
|
28
|
-
* Function added to worker globals so customer code can create a page router at runtime.
|
|
29
|
-
*/
|
|
30
|
-
createPageRouter: CreatePageRouterFunction;
|
|
21
|
+
useAppPageLocation: () => AppPageLocation;
|
|
31
22
|
}
|
|
32
23
|
export interface WorkerGlobalsInternal {
|
|
33
24
|
/**
|
|
@@ -18,6 +18,6 @@ describe('createRenderer', () => {
|
|
|
18
18
|
catch (error) {
|
|
19
19
|
errorMessage = String(error);
|
|
20
20
|
}
|
|
21
|
-
expect(errorMessage).toBe(`InvalidExtensionPointLocationError: Invalid extension point location of "INVALID_LOCATION". Allowed locations are: "crm.preview", "crm.record.sidebar", "crm.record.tab", "helpdesk.sidebar", "uie.playground.middle", "settings", "home"`);
|
|
21
|
+
expect(errorMessage).toBe(`InvalidExtensionPointLocationError: Invalid extension point location of "INVALID_LOCATION". Allowed locations are: "crm.preview", "crm.record.sidebar", "crm.record.tab", "helpdesk.sidebar", "uie.playground.middle", "settings", "home", "pages"`);
|
|
22
22
|
});
|
|
23
23
|
});
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
+
import { type useAppPageLocation } from '../../../pages/internal/useAppPageLocation.tsx';
|
|
1
2
|
import type { ExtensionPointLocation } from '../../../shared/types/extension-points.ts';
|
|
2
|
-
import type {
|
|
3
|
-
import type
|
|
3
|
+
import type { RendererMocks } from '../../types.ts';
|
|
4
|
+
import { type MocksAppPageLocationStore } from './mock-app-page-location.tsx';
|
|
4
5
|
/**
|
|
5
6
|
* An object containing the mocks and spies.
|
|
6
7
|
*/
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
export interface RendererMocksContext {
|
|
9
|
+
/**
|
|
10
|
+
* The mocks object provided by the testing utilities.
|
|
11
|
+
*/
|
|
12
|
+
mocks: RendererMocks<ExtensionPointLocation>;
|
|
13
|
+
/**
|
|
14
|
+
* The app page location store.
|
|
15
|
+
*/
|
|
16
|
+
appPageLocationStore: MocksAppPageLocationStore;
|
|
17
|
+
useAppPageLocation: typeof useAppPageLocation;
|
|
10
18
|
}
|
|
11
19
|
/**
|
|
12
20
|
* Creates a set of mocks and spies for the various functions such as the `useCrmProperties` and `useAssociations`
|
|
@@ -14,4 +22,4 @@ export interface MocksWithSpies<TExtensionPointLocation extends ExtensionPointLo
|
|
|
14
22
|
*
|
|
15
23
|
* @returns An object containing the mocks and spies.
|
|
16
24
|
*/
|
|
17
|
-
export declare const
|
|
25
|
+
export declare const createRendererMocksContext: <TExtensionPointLocation extends ExtensionPointLocation>(extensionPointLocation: TExtensionPointLocation) => RendererMocksContext;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { act as reactAct, useSyncExternalStore } from 'react';
|
|
1
2
|
import { spyOn } from 'tinyspy';
|
|
3
|
+
import { createMocksAppPageLocationStore, } from "./mock-app-page-location.js";
|
|
2
4
|
import { createMockExtensionPointApi } from "./mock-extension-point-api.js";
|
|
3
5
|
import { createMockHooks } from "./mock-hooks.js";
|
|
4
6
|
/**
|
|
@@ -29,7 +31,7 @@ const spyOnAllFunctionsRecursive = (object) => {
|
|
|
29
31
|
*
|
|
30
32
|
* @returns An object containing the mocks and spies.
|
|
31
33
|
*/
|
|
32
|
-
export const
|
|
34
|
+
export const createRendererMocksContext = (extensionPointLocation) => {
|
|
33
35
|
const mockExtensionPointApi = createMockExtensionPointApi(extensionPointLocation);
|
|
34
36
|
// Build a mocks object with the default mock functions.
|
|
35
37
|
// NOTE: The mock implementations can be overridden by tests using the spies that are provided in the returned `spies` object.
|
|
@@ -37,10 +39,22 @@ export const createMocksWithSpies = (extensionPointLocation) => {
|
|
|
37
39
|
...createMockHooks(),
|
|
38
40
|
...mockExtensionPointApi,
|
|
39
41
|
};
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
+
// Recursively create spies for all functions in the mocks object.
|
|
43
|
+
const mocksWithSpies = spyOnAllFunctionsRecursive(mocks);
|
|
44
|
+
const appPageLocationStore = createMocksAppPageLocationStore();
|
|
42
45
|
return {
|
|
43
|
-
mocks
|
|
44
|
-
|
|
46
|
+
mocks: {
|
|
47
|
+
// NOTE: These mocks are exposed as part of our public API.
|
|
48
|
+
...mocksWithSpies,
|
|
49
|
+
setPageLocation: (location) => {
|
|
50
|
+
reactAct(() => {
|
|
51
|
+
appPageLocationStore.setPageLocation(location);
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
appPageLocationStore,
|
|
56
|
+
useAppPageLocation: () => {
|
|
57
|
+
return useSyncExternalStore(appPageLocationStore.subscribe, appPageLocationStore.getSnapshot);
|
|
58
|
+
},
|
|
45
59
|
};
|
|
46
60
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AppPageLocation } from '../../../shared/types/shared.ts';
|
|
2
|
+
export interface MocksAppPageLocationStore {
|
|
3
|
+
getSnapshot: () => AppPageLocation;
|
|
4
|
+
setPageLocation: (location: AppPageLocation) => void;
|
|
5
|
+
subscribe: (listener: () => void) => () => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const createMocksAppPageLocationStore: (initialLocation?: AppPageLocation) => MocksAppPageLocationStore;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const DEFAULT_PAGE_LOCATION = { path: '/', params: {} };
|
|
2
|
+
const areParamsEqual = (a, b) => {
|
|
3
|
+
const keysA = Object.keys(a);
|
|
4
|
+
const keysB = Object.keys(b);
|
|
5
|
+
if (keysA.length !== keysB.length)
|
|
6
|
+
return false;
|
|
7
|
+
for (const key of keysA) {
|
|
8
|
+
if (a[key] !== b[key])
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
};
|
|
13
|
+
const areAppPageLocationsEqual = (a, b) => a.path === b.path && areParamsEqual(a.params, b.params);
|
|
14
|
+
export const createMocksAppPageLocationStore = (initialLocation = DEFAULT_PAGE_LOCATION) => {
|
|
15
|
+
let currentLocation = initialLocation;
|
|
16
|
+
let listeners = [];
|
|
17
|
+
return {
|
|
18
|
+
getSnapshot: () => currentLocation,
|
|
19
|
+
setPageLocation: (location) => {
|
|
20
|
+
if (areAppPageLocationsEqual(location, currentLocation)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
currentLocation = location;
|
|
24
|
+
for (const listener of listeners) {
|
|
25
|
+
listener();
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
subscribe: (listener) => {
|
|
29
|
+
listeners = [...listeners, listener];
|
|
30
|
+
return () => {
|
|
31
|
+
listeners = listeners.filter((l) => l !== listener);
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -22,8 +22,9 @@ const createMockUiePlatformActions = () => {
|
|
|
22
22
|
return {
|
|
23
23
|
copyTextToClipboard: async () => { },
|
|
24
24
|
closeOverlay: () => { },
|
|
25
|
-
|
|
25
|
+
navigateToPage: () => { },
|
|
26
26
|
openIframeModal: () => { },
|
|
27
|
+
reloadPage: () => { },
|
|
27
28
|
};
|
|
28
29
|
};
|
|
29
30
|
const createMockSettingsActions = () => {
|
|
@@ -36,6 +37,11 @@ const createMockAppHomeActions = () => {
|
|
|
36
37
|
addAlert: () => { },
|
|
37
38
|
};
|
|
38
39
|
};
|
|
40
|
+
const createMockPagesActions = () => {
|
|
41
|
+
return {
|
|
42
|
+
addAlert: () => { },
|
|
43
|
+
};
|
|
44
|
+
};
|
|
39
45
|
const createFakeEmail = () => `${fakePrefix}email@example.com`;
|
|
40
46
|
const createFakeFirstName = () => `${fakePrefix}firstName`;
|
|
41
47
|
const createFakeLastName = () => `${fakePrefix}lastName`;
|
|
@@ -156,6 +162,20 @@ const mockCreators = {
|
|
|
156
162
|
},
|
|
157
163
|
};
|
|
158
164
|
},
|
|
165
|
+
pages: () => {
|
|
166
|
+
return {
|
|
167
|
+
runServerlessFunction: createMockServerlessFuncRunner(),
|
|
168
|
+
actions: {
|
|
169
|
+
...createMockPagesActions(),
|
|
170
|
+
...createMockUiePlatformActions(),
|
|
171
|
+
},
|
|
172
|
+
context: {
|
|
173
|
+
location: 'pages',
|
|
174
|
+
user: createFakeUser(),
|
|
175
|
+
portal: createFakePortalContext(),
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
},
|
|
159
179
|
};
|
|
160
180
|
export const createMockExtensionPointApi = (extensionPointLocation) => {
|
|
161
181
|
const mockCreator = mockCreators[extensionPointLocation];
|
|
@@ -15,6 +15,13 @@ const createFakeProperties = (propertyNames) => {
|
|
|
15
15
|
}, {});
|
|
16
16
|
};
|
|
17
17
|
export const createMockHooks = () => {
|
|
18
|
+
const useRequiredMocksContext = () => {
|
|
19
|
+
const mocksContext = useMocksContext();
|
|
20
|
+
if (!mocksContext) {
|
|
21
|
+
throw new Error('Illegal State: Mocks context not found.');
|
|
22
|
+
}
|
|
23
|
+
return mocksContext;
|
|
24
|
+
};
|
|
18
25
|
// Build a mocks object with the default mock functions.
|
|
19
26
|
// NOTE: The mock implementations can be overridden by tests using the spies that are provided in the returned `spies` object.
|
|
20
27
|
return {
|
|
@@ -61,20 +68,18 @@ export const createMockHooks = () => {
|
|
|
61
68
|
};
|
|
62
69
|
},
|
|
63
70
|
useExtensionContext: () => {
|
|
64
|
-
const mocks =
|
|
71
|
+
const { mocks } = useRequiredMocksContext();
|
|
65
72
|
return mocks.context;
|
|
66
73
|
},
|
|
67
74
|
useExtensionActions: () => {
|
|
68
|
-
const mocks =
|
|
69
|
-
return mocks
|
|
70
|
-
.actions;
|
|
75
|
+
const { mocks } = useRequiredMocksContext();
|
|
76
|
+
return mocks.actions;
|
|
71
77
|
},
|
|
72
78
|
useExtensionApi: () => {
|
|
73
|
-
const mocks =
|
|
79
|
+
const { mocks } = useRequiredMocksContext();
|
|
74
80
|
return {
|
|
75
81
|
context: mocks.context,
|
|
76
|
-
actions: mocks
|
|
77
|
-
.actions,
|
|
82
|
+
actions: mocks.actions,
|
|
78
83
|
runServerlessFunction: mocks.runServerlessFunction,
|
|
79
84
|
};
|
|
80
85
|
},
|
package/dist/testing/render.js
CHANGED
|
@@ -6,7 +6,7 @@ import { EXTENSION_POINT_LOCATIONS } from "../shared/types/extension-points.js";
|
|
|
6
6
|
import { convertRemoteRoot } from "./internal/convert.js";
|
|
7
7
|
import { createDocument } from "./internal/document.js";
|
|
8
8
|
import { InvalidComponentsError, InvalidExtensionPointLocationError, WaitForTimeoutError, } from "./internal/errors.js";
|
|
9
|
-
import {
|
|
9
|
+
import { createRendererMocksContext, } from "./internal/mocks/index.js";
|
|
10
10
|
import { find, findAll, findAllChildren, findByTestId, findChild, maybeFind, maybeFindByTestId, maybeFindChild, } from "./internal/query.js";
|
|
11
11
|
import { createRootNode } from "./internal/root.js";
|
|
12
12
|
import { asRenderedRootNode } from "./internal/type-utils-internal.js";
|
|
@@ -21,7 +21,7 @@ const DEFAULT_WAIT_FOR_OPTIONS = {
|
|
|
21
21
|
* @param node The React node to render.
|
|
22
22
|
* @returns A render result object
|
|
23
23
|
*/
|
|
24
|
-
const render = (node,
|
|
24
|
+
const render = (node, mocksContext) => {
|
|
25
25
|
let dirty = true;
|
|
26
26
|
let waitForChecksQueued = false;
|
|
27
27
|
let waitForList = [];
|
|
@@ -74,7 +74,7 @@ const render = (node, mocksWithSpies) => {
|
|
|
74
74
|
};
|
|
75
75
|
const remoteRoot = createRemoteRoot(remoteChannel);
|
|
76
76
|
const reactRoot = createReactRoot(remoteRoot);
|
|
77
|
-
reactRoot.render(_jsx(MocksContextProvider, { value:
|
|
77
|
+
reactRoot.render(_jsx(MocksContextProvider, { value: mocksContext, children: node }));
|
|
78
78
|
remoteRoot.mount();
|
|
79
79
|
let maybeRenderedRootNode;
|
|
80
80
|
/**
|
|
@@ -174,13 +174,14 @@ export const createRenderer = (extensionPointLocation) => {
|
|
|
174
174
|
return renderResult;
|
|
175
175
|
};
|
|
176
176
|
const getLatestRootNode = () => getRenderResult().getLatestRootNode();
|
|
177
|
-
const
|
|
177
|
+
const mocksContext = createRendererMocksContext(extensionPointLocation);
|
|
178
|
+
const { mocks } = mocksContext;
|
|
178
179
|
return {
|
|
179
180
|
render: (node) => {
|
|
180
|
-
renderResult = render(node,
|
|
181
|
+
renderResult = render(node, mocksContext);
|
|
181
182
|
return asRenderedRootNode(renderResult.getLatestRootNode());
|
|
182
183
|
},
|
|
183
|
-
mocks:
|
|
184
|
+
mocks: mocks,
|
|
184
185
|
find: (component, matcher) => {
|
|
185
186
|
return find(getLatestRootNode(), component, matcher);
|
|
186
187
|
},
|
package/dist/testing/types.d.ts
CHANGED
|
@@ -3,9 +3,10 @@ import type { useAssociations, useCrmProperties } from '../crm/index.ts';
|
|
|
3
3
|
import type { useExtensionActions } from '../hooks/useExtensionActions.tsx';
|
|
4
4
|
import type { useExtensionApi } from '../hooks/useExtensionApi.tsx';
|
|
5
5
|
import type { useExtensionContext } from '../hooks/useExtensionContext.tsx';
|
|
6
|
+
import type { useAppPageLocation } from '../pages/internal/useAppPageLocation.tsx';
|
|
6
7
|
import type { ExtensionPointApi, ExtensionPointApiActions, ExtensionPointLocation } from '../shared/types/extension-points.ts';
|
|
7
8
|
import type { ServerlessFuncRunner } from '../shared/types/http-requests.ts';
|
|
8
|
-
import type { HubSpotReactComponent, HubSpotReactFragmentProp, UnknownComponentProps } from '../shared/types/shared.ts';
|
|
9
|
+
import type { AppPageLocation, HubSpotReactComponent, HubSpotReactFragmentProp, UnknownComponentProps } from '../shared/types/shared.ts';
|
|
9
10
|
import type { AnyFunction } from './internal/types-internal.ts';
|
|
10
11
|
/**
|
|
11
12
|
* The type of a rendered node.
|
|
@@ -261,7 +262,7 @@ export type ExtensionPointApiActionSpies<TExtensionPointLocation extends Extensi
|
|
|
261
262
|
/**
|
|
262
263
|
* An object containing the spies for the various functions.
|
|
263
264
|
*/
|
|
264
|
-
export interface
|
|
265
|
+
export interface RendererMocks<TExtensionPointLocation extends ExtensionPointLocation = ExtensionPointLocation> {
|
|
265
266
|
/**
|
|
266
267
|
* A spy for the `useCrmProperties` hook function.
|
|
267
268
|
* The spy can be used to track the calls to the hook function and also control the
|
|
@@ -307,6 +308,15 @@ export interface RendererSpies<TExtensionPointLocation extends ExtensionPointLoc
|
|
|
307
308
|
* information about spies and the supported API.
|
|
308
309
|
*/
|
|
309
310
|
useExtensionApi: FunctionSpy<typeof useExtensionApi>;
|
|
311
|
+
/**
|
|
312
|
+
* A spy for the `useAppPageLocation` hook function.
|
|
313
|
+
* The spy can be used to track the calls to the hook function and also control the
|
|
314
|
+
* return result of the hook function.
|
|
315
|
+
*
|
|
316
|
+
* See the [tinyspy](https://github.com/tiny-spy/tinyspy) library for more
|
|
317
|
+
* information about spies and the supported API.
|
|
318
|
+
*/
|
|
319
|
+
useAppPageLocation: FunctionSpy<typeof useAppPageLocation>;
|
|
310
320
|
/**
|
|
311
321
|
* Mock context object that contains fake context data for the extension point API.
|
|
312
322
|
*/
|
|
@@ -329,6 +339,12 @@ export interface RendererSpies<TExtensionPointLocation extends ExtensionPointLoc
|
|
|
329
339
|
* information about spies and the supported API.
|
|
330
340
|
*/
|
|
331
341
|
runServerlessFunction: FunctionSpy<ServerlessFuncRunner>;
|
|
342
|
+
/**
|
|
343
|
+
* Sets the current app page location for testing page routing.
|
|
344
|
+
*
|
|
345
|
+
* @param location The app page location to set.
|
|
346
|
+
*/
|
|
347
|
+
setPageLocation: (location: AppPageLocation) => void;
|
|
332
348
|
}
|
|
333
349
|
/**
|
|
334
350
|
* A result of a render operation.
|
|
@@ -363,7 +379,7 @@ export interface Renderer<TExtensionPointLocation extends ExtensionPointLocation
|
|
|
363
379
|
/**
|
|
364
380
|
* The mocks that can be used to control the behavior of the various functions.
|
|
365
381
|
*/
|
|
366
|
-
mocks:
|
|
382
|
+
mocks: RendererMocks<TExtensionPointLocation>;
|
|
367
383
|
/**
|
|
368
384
|
* Finds the first descendant element node that matches the given component and test ID.
|
|
369
385
|
*
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface PaginationResult {
|
|
2
|
+
hasNextPage: boolean;
|
|
3
|
+
hasPreviousPage: boolean;
|
|
4
|
+
currentPage: number;
|
|
5
|
+
pageSize: number;
|
|
6
|
+
nextPage: () => void;
|
|
7
|
+
previousPage: () => void;
|
|
8
|
+
reset: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare const DEFAULT_PAGE_SIZE = 10;
|
|
11
|
+
/**
|
|
12
|
+
* Calculate pagination flags based on current page and API hasMore flag
|
|
13
|
+
*/
|
|
14
|
+
export declare function calculatePaginationFlags(currentPage: number, hasMore: boolean): {
|
|
15
|
+
hasNextPage: boolean;
|
|
16
|
+
hasPreviousPage: boolean;
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const DEFAULT_PAGE_SIZE = 10;
|
|
2
|
+
/**
|
|
3
|
+
* Calculate pagination flags based on current page and API hasMore flag
|
|
4
|
+
*/
|
|
5
|
+
export function calculatePaginationFlags(currentPage, hasMore) {
|
|
6
|
+
return {
|
|
7
|
+
hasNextPage: hasMore,
|
|
8
|
+
hasPreviousPage: currentPage > 1,
|
|
9
|
+
};
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./dist/index.js",
|
|
17
17
|
"./crm": "./dist/crm/index.js",
|
|
18
|
-
"./
|
|
18
|
+
"./pages": "./dist/pages/index.js",
|
|
19
19
|
"./pages/home": "./dist/pages/home/index.js",
|
|
20
20
|
"./experimental": "./dist/experimental/index.js",
|
|
21
21
|
"./testing": "./dist/testing/index.js",
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
export type * from '../../shared/types/pages/components/index.ts';
|
|
2
|
-
export type * from '../../shared/types/pages/app-pages-types.ts';
|
|
3
|
-
export declare const PageRoutes: import("../../shared/types/pages/components/page-routes.ts").PageRoutesComponent, usePageRoute: import("../../shared/types/pages/app-pages-types.ts").UsePageRouteHook, createPageRouter: import("../../shared/types/pages/app-pages-types.ts").CreatePageRouterFunction;
|
|
4
|
-
export { PageLink } from '../../shared/remoteComponents.tsx';
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import type { ComponentType, ReactElement } from 'react';
|
|
2
|
-
import type { EmptyProps, ReactFragmentProps } from '../shared.ts';
|
|
3
|
-
import type { PageRoutesProps } from './components/page-routes.ts';
|
|
4
|
-
/**
|
|
5
|
-
* The props type for a PageRouter component.
|
|
6
|
-
*/
|
|
7
|
-
export type PageRouterProps = EmptyProps;
|
|
8
|
-
/**
|
|
9
|
-
* Represents the current page route that has been matched by the page router.
|
|
10
|
-
*/
|
|
11
|
-
export interface MatchedPageRoute {
|
|
12
|
-
/**
|
|
13
|
-
* The id of the route that has been matched.
|
|
14
|
-
*/
|
|
15
|
-
routeId?: string;
|
|
16
|
-
/**
|
|
17
|
-
* The path of the route that has been matched.
|
|
18
|
-
*/
|
|
19
|
-
path: string;
|
|
20
|
-
/**
|
|
21
|
-
* The parameters of the route that has been matched.
|
|
22
|
-
*/
|
|
23
|
-
params: Record<string, string>;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* The component type for the page router that handles rendering matched routes.
|
|
27
|
-
*
|
|
28
|
-
* @experimental This component is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
29
|
-
*/
|
|
30
|
-
export type PageRouterComponent = ComponentType<PageRouterProps>;
|
|
31
|
-
/**
|
|
32
|
-
* The function type for [createPageRouter](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#createpagerouter).
|
|
33
|
-
*
|
|
34
|
-
* Example usage:
|
|
35
|
-
*
|
|
36
|
-
* ```tsx
|
|
37
|
-
* const PageRouter = createPageRouter(
|
|
38
|
-
* <PageRoutes layoutComponent={AppLayout}>
|
|
39
|
-
* <PageRoutes.IndexRoute component={HomePage} id="home" />
|
|
40
|
-
* <PageRoutes.Route path="/docs" component={DocsPage} />
|
|
41
|
-
* <PageRoutes path="/customers" layoutComponent={CustomersLayout}>
|
|
42
|
-
* <PageRoutes.IndexRoute component={ListCustomersPage} />
|
|
43
|
-
* <PageRoutes.Route path="/:customerId" component={ViewCustomerPage} />
|
|
44
|
-
* </PageRoutes>
|
|
45
|
-
* <PageRoutes.AnyRoute component={NotFoundPage} />
|
|
46
|
-
* </PageRoutes>
|
|
47
|
-
* );
|
|
48
|
-
*
|
|
49
|
-
* function AppPages() {
|
|
50
|
-
* return <PageRouter />;
|
|
51
|
-
* }
|
|
52
|
-
*
|
|
53
|
-
* hubspot.extend<"pages">(() => <AppPages />);
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* @param routes The routes to render.
|
|
57
|
-
* @returns The page router component.
|
|
58
|
-
*
|
|
59
|
-
* @experimental This function is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
60
|
-
*/
|
|
61
|
-
export type CreatePageRouterFunction = (reactPageRoutesElement: ReactElement<PageRoutesProps> | ReactElement<ReactFragmentProps>) => PageRouterComponent;
|
|
62
|
-
/**
|
|
63
|
-
* The function type for [usePageRoute](https://developers.hubspot.com/docs/apps/developer-platform/add-features/ui-extensibility/app-pages/reference#usepageroute).
|
|
64
|
-
*
|
|
65
|
-
* Example usage:
|
|
66
|
-
*
|
|
67
|
-
* ```tsx
|
|
68
|
-
* const pageRoute = usePageRoute();
|
|
69
|
-
* ```
|
|
70
|
-
*
|
|
71
|
-
* @returns The current page route.
|
|
72
|
-
*
|
|
73
|
-
* @experimental This hook is experimental. Avoid using it in production due to potential breaking changes. Your feedback is valuable for improvements. Stay tuned for updates.
|
|
74
|
-
*/
|
|
75
|
-
export type UsePageRouteHook = () => MatchedPageRoute;
|
/package/dist/{shared/types/pages/app-pages-types.js → __tests__/hooks/useDebounce.spec.d.ts}
RENAMED
|
File without changes
|
|
File without changes
|