@hubspot/ui-extensions 0.11.0 → 0.11.2
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/__synced__/experimental/types.synced.d.ts +3 -4
- package/dist/__synced__/remoteComponents.synced.d.ts +168 -355
- package/dist/__synced__/remoteComponents.synced.js +186 -83
- package/dist/__synced__/types/components/button.synced.d.ts +6 -0
- package/dist/__synced__/types/components/index.synced.d.ts +38 -38
- package/dist/__synced__/types/index.synced.d.ts +7 -7
- package/dist/__synced__/types/index.synced.js +1 -9
- package/dist/__synced__/types/shared.synced.d.ts +2 -3
- package/dist/__synced__/utils/remote-component-registry.synced.d.ts +80 -0
- package/dist/__synced__/utils/remote-component-registry.synced.js +64 -0
- package/dist/__tests__/crm/hooks/useAssociations.spec.js +33 -29
- package/dist/__tests__/crm/hooks/useCrmProperties.spec.js +19 -18
- package/dist/__tests__/crm/utils/fetchAssociations.spec.js +8 -7
- package/dist/__tests__/crm/utils/fetchCrmProperties.spec.js +34 -33
- package/dist/crm/index.d.ts +1 -1
- package/dist/crm/index.js +1 -1
- package/dist/experimental/index.d.ts +1 -1
- package/dist/experimental/index.js +1 -1
- package/dist/experimental/testing/__tests__/debug.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/debug.spec.js +43 -0
- package/dist/experimental/testing/__tests__/find.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/find.spec.js +33 -0
- package/dist/experimental/testing/__tests__/findAll.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/findAll.spec.js +12 -0
- package/dist/experimental/testing/__tests__/findAllChildren.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/findAllChildren.spec.js +48 -0
- package/dist/experimental/testing/__tests__/findChild.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/findChild.spec.js +29 -0
- package/dist/experimental/testing/__tests__/fragments.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/fragments.spec.js +59 -0
- package/dist/experimental/testing/__tests__/invalid-components.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/invalid-components.spec.js +88 -0
- package/dist/experimental/testing/__tests__/isMatch.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/isMatch.spec.js +60 -0
- package/dist/experimental/testing/__tests__/maybeFind.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/maybeFind.spec.js +58 -0
- package/dist/experimental/testing/__tests__/maybeFindChild.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/maybeFindChild.spec.js +65 -0
- package/dist/experimental/testing/__tests__/trigger.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/trigger.spec.js +40 -0
- package/dist/experimental/testing/__tests__/type-utils.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/type-utils.spec.js +163 -0
- package/dist/experimental/testing/__tests__/waitFor.spec.d.ts +1 -0
- package/dist/experimental/testing/__tests__/waitFor.spec.js +55 -0
- package/dist/experimental/testing/index.d.ts +3 -0
- package/dist/experimental/testing/index.js +3 -0
- package/dist/experimental/testing/internal/constants.d.ts +2 -0
- package/dist/experimental/testing/internal/constants.js +1 -0
- package/dist/experimental/testing/internal/convert.d.ts +10 -0
- package/dist/experimental/testing/internal/convert.js +131 -0
- package/dist/experimental/testing/internal/debug.d.ts +8 -0
- package/dist/experimental/testing/internal/debug.js +19 -0
- package/dist/experimental/testing/internal/document.d.ts +14 -0
- package/dist/experimental/testing/internal/document.js +37 -0
- package/dist/experimental/testing/internal/element.d.ts +11 -0
- package/dist/experimental/testing/internal/element.js +67 -0
- package/dist/experimental/testing/internal/errors.d.ts +56 -0
- package/dist/experimental/testing/internal/errors.js +70 -0
- package/dist/experimental/testing/internal/fragment.d.ts +8 -0
- package/dist/experimental/testing/internal/fragment.js +44 -0
- package/dist/experimental/testing/internal/match.d.ts +19 -0
- package/dist/experimental/testing/internal/match.js +42 -0
- package/dist/experimental/testing/internal/print.d.ts +6 -0
- package/dist/experimental/testing/internal/print.js +114 -0
- package/dist/experimental/testing/internal/query.d.ts +57 -0
- package/dist/experimental/testing/internal/query.js +213 -0
- package/dist/experimental/testing/internal/root.d.ts +8 -0
- package/dist/experimental/testing/internal/root.js +44 -0
- package/dist/experimental/testing/internal/text.d.ts +9 -0
- package/dist/experimental/testing/internal/text.js +16 -0
- package/dist/experimental/testing/internal/utils/promise-utils.d.ts +14 -0
- package/dist/experimental/testing/internal/utils/promise-utils.js +14 -0
- package/dist/experimental/testing/render.d.ts +9 -0
- package/dist/experimental/testing/render.js +155 -0
- package/dist/experimental/testing/types.d.ts +2 -1
- package/dist/hubspot.d.ts +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/pages/home/index.d.ts +1 -1
- package/dist/pages/home/index.js +1 -1
- package/package.json +19 -16
- package/dist/__synced__/appHomeRemoteComponents.synced.d.ts +0 -28
- package/dist/__synced__/appHomeRemoteComponents.synced.js +0 -21
- package/dist/__synced__/crmRemoteComponents.synced.d.ts +0 -66
- package/dist/__synced__/crmRemoteComponents.synced.js +0 -15
- package/dist/__synced__/experimentalRemoteComponents.synced.d.ts +0 -94
- package/dist/__synced__/experimentalRemoteComponents.synced.js +0 -56
- package/dist/coreComponents.d.ts +0 -848
- package/dist/coreComponents.js +0 -582
- package/dist/experimental/types.d.ts +0 -240
- package/dist/experimental/types.js +0 -5
- package/dist/types.d.ts +0 -3214
- package/dist/types.js +0 -244
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { __hubSpotComponentRegistry } from '../../../__synced__/remoteComponents.synced';
|
|
2
|
+
import { isRenderedElementNode, isRenderedFragmentNode } from '../type-utils';
|
|
3
|
+
import { ComponentNotFoundError, FindInvalidComponentError, InvalidComponentsError, } from './errors';
|
|
4
|
+
import { checkElementMatches } from './match';
|
|
5
|
+
const addMatchToFindResult = (findResult, match, options) => {
|
|
6
|
+
if (options.findFirstOnly) {
|
|
7
|
+
findResult.match = match;
|
|
8
|
+
}
|
|
9
|
+
else {
|
|
10
|
+
findResult.allMatches.push(match);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Recursive helper for finding elements in the rendered tree.
|
|
15
|
+
* NOTE: The input `findResult` is mutated in place.
|
|
16
|
+
*
|
|
17
|
+
* @param parentNode The parent node to search in.
|
|
18
|
+
* @param options The options for the find operation.
|
|
19
|
+
* @param findResult The find result to mutate.
|
|
20
|
+
*/
|
|
21
|
+
const findInternalHelper = (parentNode, options, findResult) => {
|
|
22
|
+
const { children } = parentNode;
|
|
23
|
+
const { targetComponent, matcher, findFirstOnly, findDirectChildrenOnly } = options;
|
|
24
|
+
const targetComponentName = __hubSpotComponentRegistry.getComponentName(targetComponent);
|
|
25
|
+
for (const child of children) {
|
|
26
|
+
if (!isRenderedElementNode(child)) {
|
|
27
|
+
// Skip over non-element child nodes (just text nodes)
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (child.name === targetComponentName &&
|
|
31
|
+
checkElementMatches(child, matcher)) {
|
|
32
|
+
// We found a match, so add it to the find result
|
|
33
|
+
addMatchToFindResult(findResult, child, options);
|
|
34
|
+
if (findFirstOnly) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// We only search in nested children and fragment props if we're not only looking for children
|
|
39
|
+
if (!findDirectChildrenOnly) {
|
|
40
|
+
// Search for the component in the fragment props
|
|
41
|
+
const { props } = child;
|
|
42
|
+
const fragmentProps = __hubSpotComponentRegistry.getComponentFragmentPropNames(child.name);
|
|
43
|
+
for (const fragmentPropName of fragmentProps) {
|
|
44
|
+
const maybeFragment = props[fragmentPropName];
|
|
45
|
+
// NOTE: As part of the conversion process of converting remote nodes to rendered nodes, we
|
|
46
|
+
// create a RenderedFragmentNode for each fragment prop and put that into the props object.
|
|
47
|
+
if (isRenderedFragmentNode(maybeFragment)) {
|
|
48
|
+
findInternalHelper(maybeFragment, options, findResult);
|
|
49
|
+
if (findFirstOnly && findResult.match) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Search for the component in the child
|
|
55
|
+
findInternalHelper(child, options, findResult);
|
|
56
|
+
if (findFirstOnly && findResult.match) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Internal utility that centralizes finding elements in the rendered tree (including error handling).
|
|
65
|
+
*
|
|
66
|
+
* @param parentNode The parent node to search in.
|
|
67
|
+
* @param options The options for the find operation.
|
|
68
|
+
* @returns The find result.
|
|
69
|
+
*/
|
|
70
|
+
const findInternal = (parentNode, options) => {
|
|
71
|
+
const { document } = parentNode;
|
|
72
|
+
const { targetComponent, findMethodName, shouldThrowErrorIfNotFound } = options;
|
|
73
|
+
const targetComponentName = __hubSpotComponentRegistry.getComponentName(targetComponent);
|
|
74
|
+
if (!targetComponentName) {
|
|
75
|
+
throw new FindInvalidComponentError({
|
|
76
|
+
findMethodName,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// We always validate the document before running the find operation since the document may
|
|
80
|
+
// have been updated asynchronously in the background.
|
|
81
|
+
if (document.hasInvalidComponentNames()) {
|
|
82
|
+
throw new InvalidComponentsError(document.rootNode);
|
|
83
|
+
}
|
|
84
|
+
// Initialize the find result that will be mutated in place
|
|
85
|
+
const findResult = {
|
|
86
|
+
match: null,
|
|
87
|
+
allMatches: [],
|
|
88
|
+
};
|
|
89
|
+
// Run the internal find helper to recursively search the rendered tree
|
|
90
|
+
findInternalHelper(parentNode, options, findResult);
|
|
91
|
+
if (findResult.match === null && shouldThrowErrorIfNotFound) {
|
|
92
|
+
throw new ComponentNotFoundError({
|
|
93
|
+
findMethodName: options.findMethodName,
|
|
94
|
+
parentNode,
|
|
95
|
+
componentName: targetComponentName,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return findResult;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Finds the first descendant element node that matches the given component.
|
|
102
|
+
*
|
|
103
|
+
* @param parentNode The parent node to search in.
|
|
104
|
+
* @param targetComponent The component to find.
|
|
105
|
+
* @param matcher An optional matcher to filter the elements.
|
|
106
|
+
* @returns The first element node that matches the given component or `null` if no match is found.
|
|
107
|
+
*/
|
|
108
|
+
export const maybeFind = (parentNode, targetComponent, matcher) => {
|
|
109
|
+
const { match } = findInternal(parentNode, {
|
|
110
|
+
targetComponent,
|
|
111
|
+
matcher,
|
|
112
|
+
findFirstOnly: true,
|
|
113
|
+
findDirectChildrenOnly: false,
|
|
114
|
+
findMethodName: 'maybeFind',
|
|
115
|
+
shouldThrowErrorIfNotFound: false,
|
|
116
|
+
});
|
|
117
|
+
return match;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* Finds the first descendant element node that matches the given component.
|
|
121
|
+
*
|
|
122
|
+
* @param parentNode The parent node to search in.
|
|
123
|
+
* @param targetComponent The component to find.
|
|
124
|
+
* @param matcher An optional matcher to filter the elements.
|
|
125
|
+
* @returns The first element node that matches the given component.
|
|
126
|
+
*/
|
|
127
|
+
export const find = (parentNode, targetComponent, matcher) => {
|
|
128
|
+
const { match } = findInternal(parentNode, {
|
|
129
|
+
targetComponent,
|
|
130
|
+
matcher,
|
|
131
|
+
findFirstOnly: true,
|
|
132
|
+
findDirectChildrenOnly: false,
|
|
133
|
+
findMethodName: 'find',
|
|
134
|
+
shouldThrowErrorIfNotFound: true,
|
|
135
|
+
});
|
|
136
|
+
return match;
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Finds all descendant element nodes that match the given component.
|
|
140
|
+
*
|
|
141
|
+
* @param parentNode The parent node to search in.
|
|
142
|
+
* @param targetComponent The component to find.
|
|
143
|
+
* @param matcher An optional matcher to filter the elements.
|
|
144
|
+
* @returns An array of element nodes that match the given component.
|
|
145
|
+
*/
|
|
146
|
+
export const findAll = (parentNode, targetComponent, matcher) => {
|
|
147
|
+
const { allMatches } = findInternal(parentNode, {
|
|
148
|
+
targetComponent,
|
|
149
|
+
matcher,
|
|
150
|
+
findFirstOnly: false,
|
|
151
|
+
findDirectChildrenOnly: false,
|
|
152
|
+
findMethodName: 'findAll',
|
|
153
|
+
shouldThrowErrorIfNotFound: false,
|
|
154
|
+
});
|
|
155
|
+
return allMatches;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Finds the first direct child element node that matches the given component.
|
|
159
|
+
*
|
|
160
|
+
* @param parentNode The parent node to search in.
|
|
161
|
+
* @param targetComponent The component to find.
|
|
162
|
+
* @param matcher An optional matcher to filter the elements.
|
|
163
|
+
* @returns The first child element node that matches the given component.
|
|
164
|
+
*/
|
|
165
|
+
export const findChild = (parentNode, targetComponent, matcher) => {
|
|
166
|
+
const { match } = findInternal(parentNode, {
|
|
167
|
+
targetComponent,
|
|
168
|
+
matcher,
|
|
169
|
+
findFirstOnly: true,
|
|
170
|
+
findDirectChildrenOnly: true,
|
|
171
|
+
findMethodName: 'findChild',
|
|
172
|
+
shouldThrowErrorIfNotFound: true,
|
|
173
|
+
});
|
|
174
|
+
return match;
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Finds the first direct child element node that matches the given component.
|
|
178
|
+
*
|
|
179
|
+
* @param parentNode The parent node to search in.
|
|
180
|
+
* @param targetComponent The component to find.
|
|
181
|
+
* @param matcher An optional matcher to filter the elements.
|
|
182
|
+
* @returns The first child element node that matches the given component.
|
|
183
|
+
*/
|
|
184
|
+
export const maybeFindChild = (parentNode, targetComponent, matcher) => {
|
|
185
|
+
const { match } = findInternal(parentNode, {
|
|
186
|
+
targetComponent,
|
|
187
|
+
matcher,
|
|
188
|
+
findFirstOnly: true,
|
|
189
|
+
findDirectChildrenOnly: true,
|
|
190
|
+
findMethodName: 'maybeFindChild',
|
|
191
|
+
shouldThrowErrorIfNotFound: false,
|
|
192
|
+
});
|
|
193
|
+
return match;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* Finds all direct child element nodes that match the given component.
|
|
197
|
+
*
|
|
198
|
+
* @param parentNode The parent node to search in.
|
|
199
|
+
* @param targetComponent The component to find.
|
|
200
|
+
* @param matcher An optional matcher to filter the elements.
|
|
201
|
+
* @returns An array of child element nodes that match the given component.
|
|
202
|
+
*/
|
|
203
|
+
export const findAllChildren = (parentNode, targetComponent, matcher) => {
|
|
204
|
+
const { allMatches } = findInternal(parentNode, {
|
|
205
|
+
targetComponent,
|
|
206
|
+
matcher,
|
|
207
|
+
findFirstOnly: false,
|
|
208
|
+
findDirectChildrenOnly: true,
|
|
209
|
+
findMethodName: 'findAllChildren',
|
|
210
|
+
shouldThrowErrorIfNotFound: false,
|
|
211
|
+
});
|
|
212
|
+
return allMatches;
|
|
213
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RenderedDocumentInternal, RenderedRootNodeInternal } from './types-internal';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a rendered root node.
|
|
4
|
+
*
|
|
5
|
+
* @param document The document that the root belongs to.
|
|
6
|
+
* @returns A rendered root node.
|
|
7
|
+
*/
|
|
8
|
+
export declare const createRootNode: (document: RenderedDocumentInternal) => RenderedRootNodeInternal;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { RenderedNodeType } from '../types';
|
|
2
|
+
import { EMPTY_CHILDREN } from './constants';
|
|
3
|
+
import { debugLog } from './debug';
|
|
4
|
+
import { printNode } from './print';
|
|
5
|
+
import { find, findAll, findAllChildren, findChild, maybeFind, maybeFindChild, } from './query';
|
|
6
|
+
/**
|
|
7
|
+
* Creates a rendered root node.
|
|
8
|
+
*
|
|
9
|
+
* @param document The document that the root belongs to.
|
|
10
|
+
* @returns A rendered root node.
|
|
11
|
+
*/
|
|
12
|
+
export const createRootNode = (document) => {
|
|
13
|
+
const rootNode = {
|
|
14
|
+
nodeType: RenderedNodeType.Root,
|
|
15
|
+
text: null,
|
|
16
|
+
children: EMPTY_CHILDREN,
|
|
17
|
+
document,
|
|
18
|
+
debugLog: (label) => {
|
|
19
|
+
debugLog(rootNode, label);
|
|
20
|
+
},
|
|
21
|
+
find: (targetComponent, matcher) => {
|
|
22
|
+
return find(rootNode, targetComponent, matcher);
|
|
23
|
+
},
|
|
24
|
+
findAll: (targetComponent, matcher) => {
|
|
25
|
+
return findAll(rootNode, targetComponent, matcher);
|
|
26
|
+
},
|
|
27
|
+
findChild: (targetComponent, matcher) => {
|
|
28
|
+
return findChild(rootNode, targetComponent, matcher);
|
|
29
|
+
},
|
|
30
|
+
findAllChildren: (targetComponent, matcher) => {
|
|
31
|
+
return findAllChildren(rootNode, targetComponent, matcher);
|
|
32
|
+
},
|
|
33
|
+
maybeFind: (targetComponent, matcher) => {
|
|
34
|
+
return maybeFind(rootNode, targetComponent, matcher);
|
|
35
|
+
},
|
|
36
|
+
maybeFindChild: (targetComponent, matcher) => {
|
|
37
|
+
return maybeFindChild(rootNode, targetComponent, matcher);
|
|
38
|
+
},
|
|
39
|
+
toString: () => {
|
|
40
|
+
return printNode(rootNode);
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
return rootNode;
|
|
44
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RemoteRoot, type RemoteText } from '@remote-ui/core';
|
|
2
|
+
import { type RenderedTextNode } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a rendered text node.
|
|
5
|
+
*
|
|
6
|
+
* @param remoteText The remote text node or string to create a text node from.
|
|
7
|
+
* @returns A rendered text node.
|
|
8
|
+
*/
|
|
9
|
+
export declare const createTextNode: (remoteText: RemoteText<RemoteRoot> | string) => RenderedTextNode;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { RenderedNodeType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a rendered text node.
|
|
4
|
+
*
|
|
5
|
+
* @param remoteText The remote text node or string to create a text node from.
|
|
6
|
+
* @returns A rendered text node.
|
|
7
|
+
*/
|
|
8
|
+
export const createTextNode = (remoteText) => {
|
|
9
|
+
const text = typeof remoteText === 'string' ? remoteText : remoteText.text;
|
|
10
|
+
const textNode = {
|
|
11
|
+
nodeType: RenderedNodeType.Text,
|
|
12
|
+
text,
|
|
13
|
+
toString: () => text,
|
|
14
|
+
};
|
|
15
|
+
return textNode;
|
|
16
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A deferred promise that can be resolved or rejected externally.
|
|
3
|
+
*/
|
|
4
|
+
export interface Deferred<T = unknown> {
|
|
5
|
+
promise: Promise<T>;
|
|
6
|
+
resolve: (value: T) => void;
|
|
7
|
+
reject: (error: unknown) => void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Creates a deferred promise that can be resolved or rejected externally.
|
|
11
|
+
*
|
|
12
|
+
* @returns A deferred promise object.
|
|
13
|
+
*/
|
|
14
|
+
export declare const createDeferred: <T = unknown>() => Deferred<T>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a deferred promise that can be resolved or rejected externally.
|
|
3
|
+
*
|
|
4
|
+
* @returns A deferred promise object.
|
|
5
|
+
*/
|
|
6
|
+
export const createDeferred = () => {
|
|
7
|
+
let resolve;
|
|
8
|
+
let reject;
|
|
9
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
10
|
+
resolve = _resolve;
|
|
11
|
+
reject = _reject;
|
|
12
|
+
});
|
|
13
|
+
return { promise, resolve: resolve, reject: reject };
|
|
14
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type RenderResult } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Renders a UI extension component using React so that assertions can be made against the rendered output.
|
|
5
|
+
*
|
|
6
|
+
* @param node The React node to render.
|
|
7
|
+
* @returns A render result object that can be used to query the rendered DOM.
|
|
8
|
+
*/
|
|
9
|
+
export declare const render: (node: ReactNode) => RenderResult;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { createRemoteRoot } from '@remote-ui/core';
|
|
2
|
+
import { createRoot as createReactRoot } from '@remote-ui/react';
|
|
3
|
+
import { convertRemoteRoot } from './internal/convert';
|
|
4
|
+
import { createDocument } from './internal/document';
|
|
5
|
+
import { InvalidComponentsError, WaitForTimeoutError } from './internal/errors';
|
|
6
|
+
import { isMatch } from './internal/match';
|
|
7
|
+
import { find, findAll, findAllChildren, findChild, maybeFind, maybeFindChild, } from './internal/query';
|
|
8
|
+
import { createRootNode } from './internal/root';
|
|
9
|
+
import { createDeferred } from './internal/utils/promise-utils';
|
|
10
|
+
const DEFAULT_WAIT_FOR_TIMEOUT_IN_MS = 1000;
|
|
11
|
+
const DEFAULT_WAIT_FOR_OPTIONS = {
|
|
12
|
+
timeoutInMs: DEFAULT_WAIT_FOR_TIMEOUT_IN_MS,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Renders a UI extension component using React so that assertions can be made against the rendered output.
|
|
16
|
+
*
|
|
17
|
+
* @param node The React node to render.
|
|
18
|
+
* @returns A render result object that can be used to query the rendered DOM.
|
|
19
|
+
*/
|
|
20
|
+
export const render = (node) => {
|
|
21
|
+
let dirty = true;
|
|
22
|
+
let waitForChecksQueued = false;
|
|
23
|
+
let waitForList = [];
|
|
24
|
+
const runWaitForChecks = () => {
|
|
25
|
+
waitForChecksQueued = false;
|
|
26
|
+
waitForList = waitForList.filter((waitFor) => {
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
28
|
+
const renderedRootNode = getLatestRootNode();
|
|
29
|
+
if (renderedRootNode.document.hasInvalidComponentNames()) {
|
|
30
|
+
// Reject the waitFor promise if we detect invalid components in the rendered output.
|
|
31
|
+
waitFor.deferred.reject(new InvalidComponentsError(renderedRootNode));
|
|
32
|
+
if (waitFor.setTimeoutId) {
|
|
33
|
+
// Clear the timeout so that we don't reject the promise if the check passes before the timeout expires.
|
|
34
|
+
// Technically, calling reject on a promise that has already been resolved is a no-op.
|
|
35
|
+
clearTimeout(waitFor.setTimeoutId);
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
waitFor.check(); // Run the user provided check function.
|
|
41
|
+
if (waitFor.setTimeoutId) {
|
|
42
|
+
// Clear the timeout so that we don't reject the promise if the check passes before the timeout expires.
|
|
43
|
+
// Technically, calling reject on a promise that has already been resolved is a no-op.
|
|
44
|
+
clearTimeout(waitFor.setTimeoutId);
|
|
45
|
+
}
|
|
46
|
+
waitFor.deferred.resolve(); // Resolve the promise that was originally returned by the waitFor function.
|
|
47
|
+
return false; // Remove the waitFor from the list since the check passed and we resolved the promise
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return true; // Keep the waitFor in the list since the check failed and we didn't resolve the promise.
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* This function will be invoked any time there are changes to the remote DOM.
|
|
56
|
+
* We don't really care what the changes were because we are not actually sending them
|
|
57
|
+
* to a host page. We are only interested in the fact that the DOM has been updated
|
|
58
|
+
* so that we can know that we need rebuild our own tree of rendered nodes.
|
|
59
|
+
*/
|
|
60
|
+
const remoteChannel = () => {
|
|
61
|
+
dirty = true;
|
|
62
|
+
if (waitForList.length > 0) {
|
|
63
|
+
// Even though @remote-ui/core is notifying us that the DOM has been updated, it hasn't actually
|
|
64
|
+
// applied the DOMs to the remote DOM representation. Therefore, we need to queue a microtask to
|
|
65
|
+
// run the waitFor checks at the end of the event loop to give the DOM a chance to be updated...
|
|
66
|
+
if (!waitForChecksQueued) {
|
|
67
|
+
waitForChecksQueued = true;
|
|
68
|
+
queueMicrotask(runWaitForChecks);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const remoteRoot = createRemoteRoot(remoteChannel);
|
|
73
|
+
const reactRoot = createReactRoot(remoteRoot);
|
|
74
|
+
reactRoot.render(node);
|
|
75
|
+
remoteRoot.mount();
|
|
76
|
+
let maybeRenderedRootNode;
|
|
77
|
+
/**
|
|
78
|
+
* Returns the latest rendered root node. If the root node has not been rendered yet, it will be rendered.
|
|
79
|
+
* If there were no changes to the remote DOM then the previously rendered root node will be returned.
|
|
80
|
+
*/
|
|
81
|
+
const getLatestRootNode = () => {
|
|
82
|
+
if (maybeRenderedRootNode && !dirty) {
|
|
83
|
+
// Return the previously rendered root node if there were no changes to the remote DOM.
|
|
84
|
+
return maybeRenderedRootNode;
|
|
85
|
+
}
|
|
86
|
+
// Create a new document for the next rendered DOM tree
|
|
87
|
+
const document = createDocument({
|
|
88
|
+
getLatestRootNode,
|
|
89
|
+
});
|
|
90
|
+
const nextRootNode = createRootNode(document);
|
|
91
|
+
document.rootNode = nextRootNode;
|
|
92
|
+
// Convert the tree of remote nodes to a tree of rendered nodes.
|
|
93
|
+
convertRemoteRoot(nextRootNode, remoteRoot);
|
|
94
|
+
maybeRenderedRootNode = nextRootNode;
|
|
95
|
+
dirty = false;
|
|
96
|
+
return maybeRenderedRootNode;
|
|
97
|
+
};
|
|
98
|
+
const initialRenderedRootNode = getLatestRootNode();
|
|
99
|
+
if (initialRenderedRootNode.document.hasInvalidComponentNames()) {
|
|
100
|
+
throw new InvalidComponentsError(initialRenderedRootNode);
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
find: (component, matcher) => {
|
|
104
|
+
return find(getLatestRootNode(), component, matcher);
|
|
105
|
+
},
|
|
106
|
+
findAll: (component, matcher) => {
|
|
107
|
+
return findAll(getLatestRootNode(), component, matcher);
|
|
108
|
+
},
|
|
109
|
+
findChild: (component, matcher) => {
|
|
110
|
+
return findChild(getLatestRootNode(), component, matcher);
|
|
111
|
+
},
|
|
112
|
+
findAllChildren: (component, matcher) => {
|
|
113
|
+
return findAllChildren(getLatestRootNode(), component, matcher);
|
|
114
|
+
},
|
|
115
|
+
maybeFindChild: (component, matcher) => {
|
|
116
|
+
return maybeFindChild(getLatestRootNode(), component, matcher);
|
|
117
|
+
},
|
|
118
|
+
maybeFind: (component, matcher) => {
|
|
119
|
+
return maybeFind(getLatestRootNode(), component, matcher);
|
|
120
|
+
},
|
|
121
|
+
waitFor: (check, options = DEFAULT_WAIT_FOR_OPTIONS) => {
|
|
122
|
+
const { timeoutInMs = DEFAULT_WAIT_FOR_TIMEOUT_IN_MS } = options;
|
|
123
|
+
try {
|
|
124
|
+
check();
|
|
125
|
+
// Nothing to wait for since the check passed! Resolve the promise immediately.
|
|
126
|
+
return Promise.resolve();
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// If the check failed then we need to put it in the waitForList so that we
|
|
130
|
+
// can check it again later. We return a promise that will be resolved when
|
|
131
|
+
// the provided check passes.
|
|
132
|
+
}
|
|
133
|
+
const deferred = createDeferred();
|
|
134
|
+
const waitFor = { check, deferred, setTimeoutId: null };
|
|
135
|
+
waitForList.push(waitFor);
|
|
136
|
+
if (timeoutInMs > 0) {
|
|
137
|
+
waitFor.setTimeoutId = setTimeout(() => {
|
|
138
|
+
// Remove the wait for from the list since the timeout expired and we rejected the promise.
|
|
139
|
+
waitForList = waitForList.filter((currentWaitFor) => currentWaitFor !== waitFor);
|
|
140
|
+
deferred.reject(new WaitForTimeoutError(timeoutInMs));
|
|
141
|
+
}, timeoutInMs);
|
|
142
|
+
}
|
|
143
|
+
return deferred.promise;
|
|
144
|
+
},
|
|
145
|
+
debugLog: (label) => {
|
|
146
|
+
return getLatestRootNode().debugLog(label);
|
|
147
|
+
},
|
|
148
|
+
getRootNode: () => {
|
|
149
|
+
return getLatestRootNode();
|
|
150
|
+
},
|
|
151
|
+
isMatch: (targetNode, component, matcher) => {
|
|
152
|
+
return isMatch(targetNode, component, matcher);
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HubSpotReactComponent, HubSpotReactFragmentProp, UnknownComponentProps } from '../../types';
|
|
1
|
+
import { HubSpotReactComponent, HubSpotReactFragmentProp, UnknownComponentProps } from '../../__synced__/types/shared.synced';
|
|
2
2
|
/**
|
|
3
3
|
* The type of a rendered node.
|
|
4
4
|
*/
|
|
@@ -235,6 +235,7 @@ export interface RenderResult extends CommonQueryMethods {
|
|
|
235
235
|
* @returns The root node.
|
|
236
236
|
*/
|
|
237
237
|
getRootNode: () => RenderedRootNode;
|
|
238
|
+
isMatch: <TComponentProps extends UnknownComponentProps>(node: RenderedNode | null | undefined, component: HubSpotReactComponent<TComponentProps>, matcher?: ElementMatcher<TComponentProps>) => node is RenderedElementNode<TComponentProps>;
|
|
238
239
|
}
|
|
239
240
|
export type RenderedNode = RenderedElementNode<any> | RenderedRootNode | RenderedFragmentNode | RenderedTextNode;
|
|
240
241
|
export {};
|
package/dist/hubspot.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement } from 'react';
|
|
2
|
-
import type { ExtensionPoints, ExtensionPointApi, ServerlessRunnerParams, HubSpotFetchRequestURI, HubSpotFetchOptions } from './types';
|
|
2
|
+
import type { ExtensionPoints, ExtensionPointApi, ServerlessRunnerParams, HubSpotFetchRequestURI, HubSpotFetchOptions } from './__synced__/types/index.synced';
|
|
3
3
|
export declare function extend_V2<ExtensionPointName extends keyof ExtensionPoints>(renderExtensionCallback: (api: ExtensionPointApi<ExtensionPointName>) => ReactElement<any>): any;
|
|
4
4
|
export declare function serverless<T = any>(name: ServerlessRunnerParams['name'], options?: Omit<ServerlessRunnerParams, 'name'>): Promise<T>;
|
|
5
5
|
export declare function fetch(url: HubSpotFetchRequestURI, options?: HubSpotFetchOptions): Promise<Response>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import './clientTypes';
|
|
2
2
|
export { hubspot } from './hubspot';
|
|
3
3
|
export { logger } from './logger';
|
|
4
|
-
export * from './
|
|
5
|
-
export
|
|
4
|
+
export * from './__synced__/types/index.synced';
|
|
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 './__synced__/remoteComponents.synced';
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
import './clientTypes';
|
|
3
3
|
export { hubspot } from './hubspot';
|
|
4
4
|
export { logger } from './logger';
|
|
5
|
-
export * from './
|
|
6
|
-
export
|
|
5
|
+
export * from './__synced__/types/index.synced';
|
|
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 './__synced__/remoteComponents.synced';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { HeaderActions, PrimaryHeaderActionButton, SecondaryHeaderActionButton, } from '../../__synced__/remoteComponents.synced';
|
package/dist/pages/home/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {};
|
|
1
|
+
export { HeaderActions, PrimaryHeaderActionButton, SecondaryHeaderActionButton, } from '../../__synced__/remoteComponents.synced';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubspot/ui-extensions",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
"scripts": {
|
|
9
9
|
"clean": "rm -rf dist/",
|
|
10
10
|
"build": "npm run clean && tsc",
|
|
11
|
+
"check:tsc": "tsc",
|
|
11
12
|
"watch": "npm run clean && tsc --watch",
|
|
12
13
|
"prepare": "npm run build",
|
|
13
14
|
"lint": "echo 'No linter configured for @hubspot/ui-extensions'",
|
|
14
|
-
"test": "
|
|
15
|
-
"
|
|
15
|
+
"test": "vitest run && tsd",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
17
|
+
"test:watch": "vitest",
|
|
16
18
|
"test:types": "tsd"
|
|
17
19
|
},
|
|
18
20
|
"files": [
|
|
@@ -25,19 +27,21 @@
|
|
|
25
27
|
".": "./dist/index.js",
|
|
26
28
|
"./crm": "./dist/crm/index.js",
|
|
27
29
|
"./pages/home": "./dist/pages/home/index.js",
|
|
28
|
-
"./experimental": "./dist/experimental/index.js"
|
|
30
|
+
"./experimental": "./dist/experimental/index.js",
|
|
31
|
+
"./experimental/testing": "./dist/experimental/testing/index.js"
|
|
29
32
|
},
|
|
30
33
|
"license": "MIT",
|
|
31
34
|
"dependencies": {
|
|
35
|
+
"@remote-ui/core": "2.2.7",
|
|
32
36
|
"@remote-ui/react": "5.0.2",
|
|
33
|
-
"react": "18.
|
|
37
|
+
"react": "18.3.1"
|
|
34
38
|
},
|
|
35
39
|
"engines": {
|
|
36
40
|
"node": ">=16"
|
|
37
41
|
},
|
|
38
42
|
"peerDependencies": {
|
|
39
43
|
"@remote-ui/react": "^5.0.0",
|
|
40
|
-
"react": "
|
|
44
|
+
"react": "18.3.1",
|
|
41
45
|
"typescript": "^5.0.4"
|
|
42
46
|
},
|
|
43
47
|
"peerDependenciesMeta": {
|
|
@@ -49,25 +53,24 @@
|
|
|
49
53
|
},
|
|
50
54
|
"typescript": {
|
|
51
55
|
"optional": true
|
|
52
|
-
},
|
|
53
|
-
"jest": {
|
|
54
|
-
"optional": true
|
|
55
56
|
}
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@testing-library/dom": "^10.4.0",
|
|
59
60
|
"@testing-library/react": "^14.1.2",
|
|
60
|
-
"@types/
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
61
|
+
"@types/node": "^20.11.0",
|
|
62
|
+
"@types/react": "^18.3.1",
|
|
63
|
+
"@types/react-dom": "^18.3.1",
|
|
64
|
+
"@vitest/coverage-v8": "2.1.8",
|
|
65
|
+
"jsdom": "26.1.0",
|
|
66
|
+
"react-dom": "18.3.1",
|
|
64
67
|
"react-reconciler": "^0.29.0",
|
|
65
|
-
"ts-jest": "^29.1.1",
|
|
66
68
|
"tsd": "^0.33.0",
|
|
67
|
-
"typescript": "5.0.4"
|
|
69
|
+
"typescript": "5.0.4",
|
|
70
|
+
"vitest": "2.1.9"
|
|
68
71
|
},
|
|
69
72
|
"tsd": {
|
|
70
73
|
"directory": "src/__tests__/test-d"
|
|
71
74
|
},
|
|
72
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "6545e14f68b8927c84d75ae45f287a43f19a0bb2"
|
|
73
76
|
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import type * as componentTypes from './types/components/index.synced';
|
|
2
|
-
/**
|
|
3
|
-
* The `HeaderActions` component renders a container for action buttons in the app home header. It accepts `PrimaryHeaderActionButton` and `SecondaryHeaderActionButton` as children.
|
|
4
|
-
*
|
|
5
|
-
*/
|
|
6
|
-
export declare const HeaderActions: "HeaderActions" & {
|
|
7
|
-
readonly type?: "HeaderActions" | undefined;
|
|
8
|
-
readonly props?: componentTypes.HeaderActionsProps | undefined;
|
|
9
|
-
readonly children?: true | undefined;
|
|
10
|
-
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"HeaderActions", componentTypes.HeaderActionsProps, true>>;
|
|
11
|
-
/**
|
|
12
|
-
* The `PrimaryHeaderActionButton` component renders a primary action button in the app home header. This button is styled as the main call-to-action and only one should be used per `HeaderActions` container.
|
|
13
|
-
*
|
|
14
|
-
*/
|
|
15
|
-
export declare const PrimaryHeaderActionButton: "PrimaryHeaderActionButton" & {
|
|
16
|
-
readonly type?: "PrimaryHeaderActionButton" | undefined;
|
|
17
|
-
readonly props?: componentTypes.HeaderActionButtonProps | undefined;
|
|
18
|
-
readonly children?: true | undefined;
|
|
19
|
-
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"PrimaryHeaderActionButton", componentTypes.HeaderActionButtonProps, true>>;
|
|
20
|
-
/**
|
|
21
|
-
* The `SecondaryHeaderActionButton` component renders a secondary action button in the app home header. Multiple secondary actions can be used and they will be grouped appropriately in the header.
|
|
22
|
-
*
|
|
23
|
-
*/
|
|
24
|
-
export declare const SecondaryHeaderActionButton: "SecondaryHeaderActionButton" & {
|
|
25
|
-
readonly type?: "SecondaryHeaderActionButton" | undefined;
|
|
26
|
-
readonly props?: componentTypes.HeaderActionButtonProps | undefined;
|
|
27
|
-
readonly children?: true | undefined;
|
|
28
|
-
} & import("@remote-ui/react").ReactComponentTypeFromRemoteComponentType<import("@remote-ui/types").RemoteComponentType<"SecondaryHeaderActionButton", componentTypes.HeaderActionButtonProps, true>>;
|