@hubspot/ui-extensions 0.8.51 → 0.8.53
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/fetchCrmProperties.spec.d.ts +1 -0
- package/dist/__tests__/experimental/crm/fetchCrmProperties.spec.js +58 -0
- package/dist/__tests__/experimental/hooks/useCrmProperties.spec.d.ts +1 -0
- package/dist/__tests__/experimental/hooks/useCrmProperties.spec.js +71 -0
- package/dist/experimental/crm/fetchCrmProperties.d.ts +4 -0
- package/dist/experimental/crm/fetchCrmProperties.js +32 -0
- package/dist/experimental/hooks/useCrmProperties.d.ts +6 -4
- package/dist/experimental/hooks/useCrmProperties.js +33 -9
- package/dist/experimental/index.d.ts +13 -1
- package/dist/experimental/index.js +9 -1
- package/dist/experimental/types.d.ts +11 -0
- package/dist/types.d.ts +1756 -1723
- package/dist/types.js +27 -24
- package/package.json +9 -3
- package/dist/experimental/crm/propertyManager.d.ts +0 -8
- package/dist/experimental/crm/propertyManager.js +0 -36
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Set up the mock before importing the module
|
|
2
|
+
const mockFetchCrmProperties = jest.fn();
|
|
3
|
+
const mockSelf = {
|
|
4
|
+
fetchCrmProperties: mockFetchCrmProperties,
|
|
5
|
+
};
|
|
6
|
+
// Mock the global self object
|
|
7
|
+
Object.defineProperty(global, 'self', {
|
|
8
|
+
value: mockSelf,
|
|
9
|
+
writable: true,
|
|
10
|
+
});
|
|
11
|
+
import { fetchCrmProperties, } from '../../../experimental/crm/fetchCrmProperties';
|
|
12
|
+
describe('fetchCrmProperties', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
});
|
|
16
|
+
it('successfully fetches CRM properties', async () => {
|
|
17
|
+
const mockApiResponse = {
|
|
18
|
+
firstname: 'Test value for firstname',
|
|
19
|
+
lastname: 'Test value for lastname',
|
|
20
|
+
};
|
|
21
|
+
const mockResponse = {
|
|
22
|
+
ok: true,
|
|
23
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
24
|
+
};
|
|
25
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
26
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
27
|
+
const result = await fetchCrmProperties(propertyNames);
|
|
28
|
+
expect(mockFetchCrmProperties).toHaveBeenCalledWith(propertyNames);
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
firstname: 'Test value for firstname',
|
|
31
|
+
lastname: 'Test value for lastname',
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
it('throws an error when response is not OK', async () => {
|
|
35
|
+
const mockResponse = {
|
|
36
|
+
ok: false,
|
|
37
|
+
statusText: 'Not Found',
|
|
38
|
+
};
|
|
39
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
40
|
+
const propertyNames = ['firstname'];
|
|
41
|
+
await expect(fetchCrmProperties(propertyNames)).rejects.toThrow('Failed to fetch CRM properties: Not Found');
|
|
42
|
+
});
|
|
43
|
+
it('throws an error when fetch fails', async () => {
|
|
44
|
+
mockFetchCrmProperties.mockRejectedValue(new Error('Network error'));
|
|
45
|
+
const propertyNames = ['firstname'];
|
|
46
|
+
await expect(fetchCrmProperties(propertyNames)).rejects.toThrow('Network error');
|
|
47
|
+
});
|
|
48
|
+
it('throws an error if the response is not an object', async () => {
|
|
49
|
+
const mockApiResponse = 'Invalid response';
|
|
50
|
+
const mockResponse = {
|
|
51
|
+
ok: true,
|
|
52
|
+
json: jest.fn().mockResolvedValue(mockApiResponse),
|
|
53
|
+
};
|
|
54
|
+
mockFetchCrmProperties.mockResolvedValue(mockResponse);
|
|
55
|
+
const propertyNames = ['firstname'];
|
|
56
|
+
await expect(fetchCrmProperties(propertyNames)).rejects.toThrow('Invalid response format');
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { renderHook, waitFor } from '@testing-library/react';
|
|
2
|
+
import { useCrmProperties } from '../../../experimental/hooks/useCrmProperties';
|
|
3
|
+
// Mock the logger module
|
|
4
|
+
jest.mock('../../../logger', () => ({
|
|
5
|
+
logger: {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
info: jest.fn(),
|
|
8
|
+
warn: jest.fn(),
|
|
9
|
+
error: jest.fn(),
|
|
10
|
+
},
|
|
11
|
+
}));
|
|
12
|
+
// Mock the fetchCrmProperties module
|
|
13
|
+
jest.mock('../../../experimental/crm/fetchCrmProperties');
|
|
14
|
+
const mockFetchCrmProperties = jest.fn();
|
|
15
|
+
import * as fetchCrmPropertiesModule from '../../../experimental/crm/fetchCrmProperties';
|
|
16
|
+
fetchCrmPropertiesModule.fetchCrmProperties = mockFetchCrmProperties;
|
|
17
|
+
describe('useCrmProperties', () => {
|
|
18
|
+
let originalError;
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
// Suppress React act() warning coming from @testing-library/react
|
|
21
|
+
originalError = console.error;
|
|
22
|
+
console.error = (...args) => {
|
|
23
|
+
if (args[0]?.includes('ReactDOMTestUtils.act'))
|
|
24
|
+
return;
|
|
25
|
+
originalError.call(console, ...args);
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
// Reset the mock before each test
|
|
30
|
+
mockFetchCrmProperties.mockReset();
|
|
31
|
+
});
|
|
32
|
+
afterAll(() => {
|
|
33
|
+
console.error = originalError;
|
|
34
|
+
});
|
|
35
|
+
it('should return the initial values for properties, error, and isLoading', async () => {
|
|
36
|
+
const { result } = renderHook(() => useCrmProperties(['firstname', 'lastname']));
|
|
37
|
+
await waitFor(() => {
|
|
38
|
+
expect(result.current.properties).toEqual({});
|
|
39
|
+
expect(result.current.error).toBeNull();
|
|
40
|
+
expect(result.current.isLoading).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
it('should successfully fetch and return CRM properties', async () => {
|
|
44
|
+
mockFetchCrmProperties.mockResolvedValue({
|
|
45
|
+
firstname: 'Test value for firstname',
|
|
46
|
+
lastname: 'Test value for lastname',
|
|
47
|
+
});
|
|
48
|
+
const propertyNames = ['firstname', 'lastname'];
|
|
49
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
50
|
+
await waitFor(() => {
|
|
51
|
+
expect(result.current.properties).toEqual({
|
|
52
|
+
firstname: 'Test value for firstname',
|
|
53
|
+
lastname: 'Test value for lastname',
|
|
54
|
+
});
|
|
55
|
+
expect(result.current.error).toBeNull();
|
|
56
|
+
expect(result.current.isLoading).toBe(false);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
it('should handle fetch errors correctly', async () => {
|
|
60
|
+
const errorMessage = 'Failed to fetch CRM properties';
|
|
61
|
+
mockFetchCrmProperties.mockRejectedValue(new Error(errorMessage));
|
|
62
|
+
const propertyNames = ['firstname'];
|
|
63
|
+
const { result } = renderHook(() => useCrmProperties(propertyNames));
|
|
64
|
+
await waitFor(() => {
|
|
65
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
66
|
+
expect(result.current.error?.message).toBe(errorMessage);
|
|
67
|
+
expect(result.current.properties).toEqual({});
|
|
68
|
+
expect(result.current.isLoading).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Type guard for CrmPropertiesResponse
|
|
2
|
+
function isCrmPropertiesResponse(data) {
|
|
3
|
+
if (
|
|
4
|
+
// Confirm the data is a defined object
|
|
5
|
+
data === null ||
|
|
6
|
+
typeof data !== 'object' ||
|
|
7
|
+
// Confirm all keys and values are strings
|
|
8
|
+
!Object.keys(data).every((key) => typeof key === 'string' && typeof data[key] === 'string')) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
export const fetchCrmProperties = async (propertyNames) => {
|
|
14
|
+
try {
|
|
15
|
+
// eslint-disable-next-line hubspot-dev/no-confusing-browser-globals
|
|
16
|
+
const response = await self.fetchCrmProperties(propertyNames);
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`Failed to fetch CRM properties: ${response.statusText}`);
|
|
19
|
+
}
|
|
20
|
+
const data = await response.json();
|
|
21
|
+
if (!isCrmPropertiesResponse(data)) {
|
|
22
|
+
throw new Error('Invalid response format');
|
|
23
|
+
}
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
throw new Error('Failed to fetch CRM properties: Unknown error');
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
export interface CrmPropertiesState {
|
|
2
|
+
properties: Record<string, string>;
|
|
3
|
+
error: Error | null;
|
|
4
|
+
isLoading: boolean;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
7
|
* A hook for using and managing CRM properties.
|
|
4
8
|
*
|
|
5
9
|
* @experimental This hook is experimental and might change or be removed in future versions.
|
|
6
10
|
*/
|
|
7
|
-
export declare function useCrmProperties(
|
|
8
|
-
onPropertyChanged: import("types").refreshObjectPropertiesAction;
|
|
9
|
-
};
|
|
11
|
+
export declare function useCrmProperties(propertyNames: string[]): CrmPropertiesState;
|
|
@@ -1,22 +1,46 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import { createPropertyManager } from '../crm/propertyManager';
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
3
2
|
import { logger } from '../../logger';
|
|
3
|
+
import { fetchCrmProperties } from '../crm/fetchCrmProperties';
|
|
4
4
|
/**
|
|
5
5
|
* A hook for using and managing CRM properties.
|
|
6
6
|
*
|
|
7
7
|
* @experimental This hook is experimental and might change or be removed in future versions.
|
|
8
8
|
*/
|
|
9
|
-
export function useCrmProperties(
|
|
9
|
+
export function useCrmProperties(propertyNames) {
|
|
10
|
+
const [properties, setProperties] = useState({});
|
|
11
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
12
|
+
const [error, setError] = useState(null);
|
|
13
|
+
// Log experimental warning once on mount
|
|
10
14
|
useEffect(() => {
|
|
11
15
|
logger.warn('useCrmProperties is an experimental hook and might change or be removed in the future.');
|
|
12
16
|
}, []);
|
|
17
|
+
// Fetch the properties
|
|
13
18
|
useEffect(() => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
(async () => {
|
|
20
|
+
try {
|
|
21
|
+
const propertyData = await fetchCrmProperties(propertyNames);
|
|
22
|
+
setProperties(propertyData);
|
|
23
|
+
setError(null);
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
const errorData = err instanceof Error
|
|
27
|
+
? err
|
|
28
|
+
: new Error('Failed to fetch CRM properties');
|
|
29
|
+
setError(errorData);
|
|
30
|
+
setProperties({});
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
setIsLoading(false);
|
|
34
|
+
}
|
|
35
|
+
})().catch((err) => {
|
|
36
|
+
setError(err);
|
|
37
|
+
setProperties({});
|
|
38
|
+
setIsLoading(false);
|
|
39
|
+
});
|
|
40
|
+
}, [propertyNames]);
|
|
19
41
|
return {
|
|
20
|
-
|
|
42
|
+
properties,
|
|
43
|
+
error,
|
|
44
|
+
isLoading,
|
|
21
45
|
};
|
|
22
46
|
}
|
|
@@ -54,4 +54,16 @@ declare const SettingsView: "SettingsView" & {
|
|
|
54
54
|
readonly props?: experimentalTypes.SettingsViewProps | undefined;
|
|
55
55
|
readonly children?: true | undefined;
|
|
56
56
|
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"SettingsView", experimentalTypes.SettingsViewProps, true>>;
|
|
57
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The `ExpandableText` component renders a text that can be expanded or collapsed based on a maximum height.
|
|
59
|
+
*
|
|
60
|
+
* **Links:**
|
|
61
|
+
*
|
|
62
|
+
* - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/expandable-text ExpandableText Docs}
|
|
63
|
+
*/
|
|
64
|
+
declare const ExpandableText: "ExpandableText" & {
|
|
65
|
+
readonly type?: "ExpandableText" | undefined;
|
|
66
|
+
readonly props?: experimentalTypes.ExpandableTextProps | undefined;
|
|
67
|
+
readonly children?: true | undefined;
|
|
68
|
+
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"ExpandableText", experimentalTypes.ExpandableTextProps, true>>;
|
|
69
|
+
export { Iframe, MediaObject, Inline, Stack2, Center, SimpleGrid, GridItem, Grid, SettingsView, ExpandableText, };
|
|
@@ -19,4 +19,12 @@ const Grid = createRemoteReactComponent('Grid');
|
|
|
19
19
|
/** @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. */
|
|
20
20
|
const GridItem = createRemoteReactComponent('GridItem');
|
|
21
21
|
const SettingsView = createRemoteReactComponent('SettingsView');
|
|
22
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The `ExpandableText` component renders a text that can be expanded or collapsed based on a maximum height.
|
|
24
|
+
*
|
|
25
|
+
* **Links:**
|
|
26
|
+
*
|
|
27
|
+
* - {@link https://developers.hubspot.com/docs/reference/ui-components/standard-components/expandable-text ExpandableText Docs}
|
|
28
|
+
*/
|
|
29
|
+
const ExpandableText = createRemoteReactComponent('ExpandableText');
|
|
30
|
+
export { Iframe, MediaObject, Inline, Stack2, Center, SimpleGrid, GridItem, Grid, SettingsView, ExpandableText, };
|
|
@@ -111,4 +111,15 @@ export interface SettingsViewProps {
|
|
|
111
111
|
*/
|
|
112
112
|
onCancel?: ReactionsHandler<ExtensionEvent>;
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* @ignore
|
|
116
|
+
* @experimental do not use in production
|
|
117
|
+
*/
|
|
118
|
+
export interface ExpandableTextProps {
|
|
119
|
+
children: ReactNode;
|
|
120
|
+
maxHeight?: number;
|
|
121
|
+
expandButtonText?: string;
|
|
122
|
+
collapseButtonText?: string;
|
|
123
|
+
expanded?: boolean;
|
|
124
|
+
}
|
|
114
125
|
export {};
|