@servicetitan/web-components 23.0.0 → 23.1.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.
Files changed (71) hide show
  1. package/dist/contexts/tests/mfe-data-context.test.js +1 -1
  2. package/dist/contexts/tests/mfe-data-context.test.js.map +1 -1
  3. package/dist/contexts/tests/mfe-metadata-context.test.js +1 -1
  4. package/dist/contexts/tests/mfe-metadata-context.test.js.map +1 -1
  5. package/dist/loader.d.ts +7 -17
  6. package/dist/loader.d.ts.map +1 -1
  7. package/dist/loader.js +74 -224
  8. package/dist/loader.js.map +1 -1
  9. package/dist/register.d.ts.map +1 -1
  10. package/dist/register.js +1 -0
  11. package/dist/register.js.map +1 -1
  12. package/dist/utils/get-bundle-info.d.ts +24 -0
  13. package/dist/utils/get-bundle-info.d.ts.map +1 -0
  14. package/dist/utils/get-bundle-info.js +98 -0
  15. package/dist/utils/get-bundle-info.js.map +1 -0
  16. package/dist/utils/get-entry-points.d.ts +6 -0
  17. package/dist/utils/get-entry-points.d.ts.map +1 -0
  18. package/dist/utils/get-entry-points.js +24 -0
  19. package/dist/utils/get-entry-points.js.map +1 -0
  20. package/dist/{get-mfe-supports-rerendering.d.ts → utils/get-mfe-supports-rerendering.d.ts} +1 -1
  21. package/dist/utils/get-mfe-supports-rerendering.d.ts.map +1 -0
  22. package/dist/utils/get-mfe-supports-rerendering.js.map +1 -0
  23. package/dist/{utils.d.ts → utils/get-version-mismatches.d.ts} +2 -3
  24. package/dist/utils/get-version-mismatches.d.ts.map +1 -0
  25. package/dist/{utils.js → utils/get-version-mismatches.js} +2 -10
  26. package/dist/utils/get-version-mismatches.js.map +1 -0
  27. package/dist/utils/index.d.ts +9 -0
  28. package/dist/utils/index.d.ts.map +1 -0
  29. package/dist/utils/index.js +9 -0
  30. package/dist/utils/index.js.map +1 -0
  31. package/dist/utils/is-versioned-url.d.ts +2 -0
  32. package/dist/utils/is-versioned-url.d.ts.map +1 -0
  33. package/dist/utils/is-versioned-url.js +9 -0
  34. package/dist/utils/is-versioned-url.js.map +1 -0
  35. package/dist/utils/load-scripts.d.ts +3 -0
  36. package/dist/utils/load-scripts.d.ts.map +1 -0
  37. package/dist/utils/load-scripts.js +31 -0
  38. package/dist/utils/load-scripts.js.map +1 -0
  39. package/dist/utils/validate-bundle-info.d.ts +7 -0
  40. package/dist/utils/validate-bundle-info.d.ts.map +1 -0
  41. package/dist/utils/validate-bundle-info.js +10 -0
  42. package/dist/utils/validate-bundle-info.js.map +1 -0
  43. package/dist/utils/web-component.d.ts +17 -0
  44. package/dist/utils/web-component.d.ts.map +1 -0
  45. package/dist/utils/web-component.js +54 -0
  46. package/dist/utils/web-component.js.map +1 -0
  47. package/package.json +16 -17
  48. package/src/__tests__/event-bus.test.ts +3 -2
  49. package/src/__tests__/loader.test.tsx +17 -11
  50. package/src/contexts/tests/mfe-data-context.test.tsx +4 -4
  51. package/src/contexts/tests/mfe-metadata-context.test.tsx +4 -4
  52. package/src/loader.tsx +129 -374
  53. package/src/register.tsx +1 -0
  54. package/src/{__tests__ → utils/__tests__}/get-mfe-supports-rerendering.test.ts +1 -1
  55. package/src/utils/__tests__/get-version-mismatches.test.ts +76 -0
  56. package/src/utils/__tests__/is-versioned-url.test.ts +18 -0
  57. package/src/utils/get-bundle-info.ts +121 -0
  58. package/src/utils/get-entry-points.ts +23 -0
  59. package/src/{get-mfe-supports-rerendering.ts → utils/get-mfe-supports-rerendering.ts} +1 -1
  60. package/src/{utils.ts → utils/get-version-mismatches.ts} +1 -12
  61. package/src/utils/index.ts +8 -0
  62. package/src/utils/is-versioned-url.ts +11 -0
  63. package/src/utils/load-scripts.ts +38 -0
  64. package/src/utils/validate-bundle-info.ts +15 -0
  65. package/src/utils/web-component.tsx +96 -0
  66. package/dist/get-mfe-supports-rerendering.d.ts.map +0 -1
  67. package/dist/get-mfe-supports-rerendering.js.map +0 -1
  68. package/dist/utils.d.ts.map +0 -1
  69. package/dist/utils.js.map +0 -1
  70. package/src/__tests__/utils.test.ts +0 -95
  71. /package/dist/{get-mfe-supports-rerendering.js → utils/get-mfe-supports-rerendering.js} +0 -0
package/src/loader.tsx CHANGED
@@ -1,425 +1,180 @@
1
1
  import {
2
- lazy,
2
+ ReactElement,
3
+ ReactNode,
3
4
  Suspense,
4
- useEffect,
5
- useMemo,
6
- useState,
7
5
  forwardRef,
6
+ lazy,
7
+ useEffect,
8
8
  useImperativeHandle,
9
- ReactElement,
10
- ReactNode,
11
- Ref,
9
+ useMemo,
12
10
  } from 'react';
13
-
14
- import semver from 'semver';
15
-
16
- import get from 'lodash.get';
17
-
18
11
  import { ErrorBoundary } from '@servicetitan/error-boundary';
19
12
  import { Log } from '@servicetitan/log-service';
20
13
  import { useOptionalDependencies } from '@servicetitan/react-ioc';
21
14
 
22
15
  import {
23
- ExposedDependencies,
24
16
  EXPOSED_DEPENDENCIES_TOKEN,
25
- useCancelation,
26
- getJson,
17
+ ExposedDependencies,
27
18
  GetJsonError,
28
- IWebComponent,
19
+ useCancelation,
29
20
  } from './common';
21
+ import { MFEContextData } from './contexts/mfe-data-context';
30
22
  import { EventBus, EVENT_BUS_TOKEN, useEventBus } from './event-bus';
31
- import { HistoryManager } from './history-manager';
32
23
  import { EXPOSED_DEPENDENCIES } from './globals';
33
- import { getVersionMismatches, isVersionedUrl } from './utils';
34
- import { MFEContextData } from './contexts/mfe-data-context';
35
- import { getMFESupportsRerendering } from './get-mfe-supports-rerendering';
36
-
37
- export interface Metadata {
38
- name: string;
39
- bundledWith?: Record<string, string | undefined>;
40
- sharedDependencies: Record<string, string>;
41
- dependencies?: Record<string, string>;
42
- entrypoints?: Record<string, { css: string[]; js: string[] }>;
43
- }
44
-
45
- interface EntryPoints {
46
- css: string[];
47
- js: string[];
48
- }
49
-
50
- async function getEntrypoints(
51
- url: string,
52
- cache: 'no-store' | undefined,
53
- signal?: AbortSignal
54
- ): Promise<EntryPoints> {
55
- try {
56
- return (
57
- await getJson(url, {
58
- cache,
59
- signal,
60
- })
61
- ).data;
62
- } catch {
63
- return { css: ['index.css'], js: ['index.js'] };
64
- }
65
- }
66
-
67
- async function getBundleInfo(
68
- mainPackageUrl: string,
69
- fallbackPackageUrl: string | undefined,
70
- exposedDependencies: ExposedDependencies = {},
71
- signal?: AbortSignal,
72
- retries?: number,
73
- onRequestError?: (error: GetJsonError) => void
74
- ) {
75
- const { data: metadata, url: packageUrl } = await getJson<Metadata>(
76
- `${mainPackageUrl}/dist/metadata.json`,
77
- {
78
- cache: isVersionedUrl(mainPackageUrl) ? undefined : 'no-store',
79
- retries: fallbackPackageUrl ? 0 : retries,
80
- onRequestError,
81
- signal,
82
- }
83
- ).catch(e => {
84
- if (!fallbackPackageUrl) {
85
- throw e;
86
- }
87
-
88
- return getJson(`${fallbackPackageUrl}/dist/metadata.json`, {
89
- retries,
90
- onRequestError,
91
- cache: isVersionedUrl(fallbackPackageUrl) ? undefined : 'no-store',
92
- signal,
93
- });
94
- });
95
-
96
- const exactPackageUrl = packageUrl.replace('/dist/metadata.json', '');
97
- const cache = isVersionedUrl(exactPackageUrl) ? undefined : 'no-store';
98
-
99
- let dependencies: NonNullable<Metadata['dependencies']>;
100
-
101
- if (metadata.dependencies) {
102
- dependencies = metadata.dependencies;
103
- } else {
104
- const { data: packageJson } = await getJson(`${exactPackageUrl}/package.json`, {
105
- retries,
106
- cache,
107
- signal,
108
- });
109
-
110
- dependencies = packageJson.dependencies;
111
- }
112
-
113
- const mismatch = getVersionMismatches(
114
- exposedDependencies,
115
- dependencies,
116
- metadata.sharedDependencies
117
- );
118
-
119
- const hasMismatch = !!Object.keys(mismatch).length;
120
- const startupVersion = metadata.bundledWith?.['@servicetitan/startup'];
121
- const supportLightBundles = startupVersion && semver.gte(startupVersion, '17.0.0');
122
- const embed = !hasMismatch && supportLightBundles;
123
- const bundleType = embed ? 'light' : 'full';
124
- const withBaseUrl = (path: string) => {
125
- return `${exactPackageUrl}/dist/bundle/${bundleType}/${path}`;
126
- };
127
-
128
- let entrypoints: EntryPoints;
129
-
130
- if (metadata.entrypoints?.[bundleType]) {
131
- entrypoints = metadata.entrypoints[bundleType];
132
- } else {
133
- entrypoints = await getEntrypoints(withBaseUrl('entrypoints.json'), cache);
134
- }
135
-
136
- return {
137
- urls: {
138
- css: entrypoints.css.map(withBaseUrl),
139
- js: entrypoints.js.map(withBaseUrl),
140
- },
141
- WebComponent: metadata.name,
142
- disposer: () => {
143
- if (embed) {
144
- const path = Object.values<string>(metadata.sharedDependencies).shift();
145
- if (path) {
146
- get(window, path).getStorage().clear(metadata.name);
147
- }
148
- }
149
- },
150
- mismatch: hasMismatch ? mismatch : undefined,
151
- mfeSupportsRerendering: getMFESupportsRerendering(metadata.bundledWith, dependencies),
152
- };
153
- }
24
+ import { BundleInfo, getBundleInfo, validateBundleInfo, loadScripts, webComponent } from './utils';
154
25
 
155
26
  export type LoaderRef = EventBus;
156
27
 
28
+ export type FallbackNode = NonNullable<ReactNode> | null;
29
+
157
30
  export interface LoaderProps<T> {
158
31
  src: string;
159
32
  fallbackSrc?: string;
160
33
  /** Values in the object will be stringified and then parsed before being delivered to the MFE via props or context. */
161
34
  data?: MFEContextData<T>;
162
35
  basename?: string;
163
- loadingFallback?: NonNullable<ReactNode> | null;
164
- errorFallback?: NonNullable<ReactNode> | null;
36
+ loadingFallback?: FallbackNode;
37
+ errorFallback?: FallbackNode;
165
38
  className?: string;
166
39
  retries?: number;
167
40
  onRequestError?(error: GetJsonError): boolean;
168
41
  }
169
42
 
170
- type DataProps = Pick<LoaderProps<{}>, 'data' | 'className'>;
43
+ export type LoaderDataProps = Pick<LoaderProps<{}>, 'data' | 'className'>;
171
44
 
172
- // Type complexity below is required to pass a generic type through forwardRef
173
- export const Loader: <T>(props: LoaderProps<T> & { ref?: Ref<LoaderRef> }) => ReactElement | null =
174
- forwardRef<LoaderRef, LoaderProps<{}>>(
175
- (
176
- {
177
- src,
178
- fallbackSrc,
179
- data = {},
180
- basename,
181
- loadingFallback,
182
- errorFallback,
183
- className,
184
- retries,
185
- onRequestError,
186
- },
187
- ref
188
- ) => {
189
- const [isLoading, setIsLoading] = useState(true);
190
-
191
- const bus = useEventBus();
192
- const [
193
- logService,
194
- historyManager,
195
- exposedDependencies = EXPOSED_DEPENDENCIES,
196
- eventBus = bus,
197
- ] = useOptionalDependencies(
198
- Log,
199
- HistoryManager,
200
- EXPOSED_DEPENDENCIES_TOKEN,
201
- EVENT_BUS_TOKEN
202
- );
203
-
204
- const Loader = useMemo(() => {
205
- const Component = forwardRef<LoaderRef, DataProps>((props: DataProps, ref) => {
206
- const cancelation = useCancelation();
207
-
208
- useImperativeHandle(ref, () => eventBus, []);
209
-
210
- useEffect(() => {
211
- setIsLoading(true);
212
-
213
- return () => {
214
- cancelation.abort();
215
- };
216
- }, []); // eslint-disable-line react-hooks/exhaustive-deps
217
-
218
- const Lazy = useMemo(
219
- () =>
220
- lazy<(props: DataProps) => JSX.Element | null>(async () => {
221
- let bundleInfo: Awaited<ReturnType<typeof getBundleInfo>>;
222
- try {
223
- bundleInfo = await getBundleInfo(
224
- src,
225
- fallbackSrc,
226
- exposedDependencies,
227
- cancelation.signal,
228
- retries,
229
- onRequestError
230
- );
231
- } catch (e) {
232
- if (cancelation.signal.aborted) {
233
- return {
234
- default: () => null,
235
- };
236
- }
237
-
238
- throw e;
239
- }
240
-
241
- const {
242
- urls,
243
- WebComponent,
244
- disposer,
245
- mismatch,
246
- mfeSupportsRerendering,
247
- } = bundleInfo;
45
+ export const Loader = forwardRef<LoaderRef, LoaderProps<{}>>((props, ref) => {
46
+ const { basename, fallbackSrc, onRequestError, retries, src } = props;
248
47
 
249
- if (mismatch) {
250
- logService?.warning({
251
- category: 'MicroFrontends.DependenciesMismatch',
252
- message:
253
- 'Some of the package dependencies have incompatible versions.',
254
- data: {
255
- package: src,
256
- dependencies: JSON.stringify(mismatch),
257
- },
258
- });
259
- }
48
+ const bus = useEventBus();
49
+ const [logService, exposedDependencies = EXPOSED_DEPENDENCIES, eventBus = bus] =
50
+ useOptionalDependencies(Log, EXPOSED_DEPENDENCIES_TOKEN, EVENT_BUS_TOKEN);
51
+ const cancelation = useCancelation();
260
52
 
261
- await Promise.all(
262
- urls.js.map(url => {
263
- const script = Array.from(document.scripts).find(
264
- ({ src }) => src === url
265
- );
266
- if (!script) {
267
- return new Promise<void>(resolve => {
268
- const script = document.createElement('script');
53
+ const LoadingFallback = useMemo(
54
+ () => getFallback(props.loadingFallback, 'Loading...'),
55
+ [props.loadingFallback]
56
+ );
269
57
 
270
- script.crossOrigin = 'anonymous';
271
- script.onload = () => {
272
- script.dataset.webComponent = WebComponent;
273
- script.onload = null;
274
- resolve();
275
- };
276
- script.async = true;
277
- script.src = url;
58
+ const ErrorFallback = useMemo(
59
+ () => getFallback(props.errorFallback, 'Something went wrong'),
60
+ [props.errorFallback]
61
+ );
278
62
 
279
- document.body.append(script);
280
- });
281
- } else if (!script.dataset.webComponent) {
282
- return new Promise<void>(resolve => {
283
- const originalOnload = script.onload;
284
- script.onload = (...args) => {
285
- originalOnload?.call(script, ...args);
286
- script.onload = null;
287
- resolve();
288
- };
289
- });
290
- }
63
+ const Loader = useMemo(
64
+ () =>
65
+ lazyLoader({
66
+ basename,
67
+ cancelation,
68
+ exposedDependencies,
69
+ eventBus,
70
+ fallbackSrc,
71
+ logService,
72
+ onRequestError,
73
+ retries,
74
+ src,
75
+ LoadingFallback,
76
+ }),
77
+ [
78
+ basename,
79
+ cancelation,
80
+ exposedDependencies,
81
+ eventBus,
82
+ logService,
83
+ onRequestError,
84
+ fallbackSrc,
85
+ retries,
86
+ src,
87
+ LoadingFallback,
88
+ ]
89
+ );
291
90
 
292
- return Promise.resolve();
293
- })
294
- );
91
+ return (
92
+ <ErrorBoundary moduleName={src} fallback={ErrorFallback}>
93
+ <Suspense fallback={<LoadingFallback />}>
94
+ <Loader ref={ref} data={props.data ?? {}} className={props.className} />
95
+ </Suspense>
96
+ </ErrorBoundary>
97
+ );
98
+ });
295
99
 
296
- const handleRef = (webComponent: IWebComponent | null) => {
297
- webComponent?.provide({
298
- logService,
299
- historyManager,
300
- exposedDependencies,
301
- eventBus,
302
- basename,
303
- onReady: () => {
304
- setIsLoading(false);
305
- },
306
- onDispose: () => {
307
- disposer();
308
- },
309
- });
310
- };
100
+ type LazyLoaderProps = Omit<LoaderProps<any>, 'loadingFallback'> & {
101
+ cancelation: AbortController;
102
+ exposedDependencies?: ExposedDependencies;
103
+ eventBus: EventBus;
104
+ logService?: Log;
105
+ LoadingFallback: () => JSX.Element;
106
+ };
311
107
 
312
- return {
313
- default: function LazyComponent(props: DataProps) {
314
- const stringifiedData = useMemo(
315
- () => JSON.stringify(props.data),
316
- [props.data]
317
- );
108
+ function lazyLoader(props: LazyLoaderProps) {
109
+ const { cancelation, eventBus } = props;
318
110
 
319
- /*
320
- * Set key for rendering the web component so that if the data from the Host change,
321
- * and the MFE's version of startup doesn't support watching for data changes, then
322
- * the web component will be re-mounted whenever the data changes.
323
- * This is only used as a fallback when the MFE's version is older.
324
- */
325
- const key = mfeSupportsRerendering
326
- ? undefined
327
- : stringifiedData;
111
+ const LazyWebComponent = lazyWebComponent(props);
328
112
 
329
- /*
330
- * Convert camelCase data props to kebab-case data attributes, prepended with data-
331
- * Ex: fooBar -> data-foo-bar
332
- */
333
- const dataAttrs = useMemo(
334
- () =>
335
- Object.fromEntries(
336
- Object.entries(props.data ?? {}).map(
337
- ([key, value]) => [
338
- 'data-' +
339
- key.replace(
340
- /[A-Z]/g,
341
- letter =>
342
- '-' + letter.toLowerCase()
343
- ),
344
- value,
345
- ]
346
- )
347
- ),
348
- [props.data]
349
- );
113
+ return forwardRef<LoaderRef, LoaderDataProps>((props: LoaderDataProps, ref) => {
114
+ useImperativeHandle(ref, () => eventBus, []);
350
115
 
351
- return (
352
- <WebComponent
353
- key={key}
354
- ref={handleRef}
355
- class={props.className}
356
- {...(urls.css.length
357
- ? {
358
- 'style-urls': encodeURIComponent(
359
- JSON.stringify(urls.css)
360
- ),
361
- }
362
- : {})}
363
- /*
364
- * Continue to spread data as separate data attributes for backwards compatibility,
365
- * in case the Host has updated web-components, but the MFE has not
366
- */
367
- {...dataAttrs}
368
- // Stringify data object so that it can be passed as a data-attribute
369
- data-mfe-data={stringifiedData}
370
- />
371
- );
372
- },
373
- };
374
- }),
375
- [] // eslint-disable-line react-hooks/exhaustive-deps
376
- );
116
+ useEffect(() => {
117
+ return () => {
118
+ cancelation.abort();
119
+ };
120
+ }, []);
377
121
 
378
- return <Lazy data={props.data} className={props.className} />;
379
- });
122
+ return <LazyWebComponent data={props.data} className={props.className} />;
123
+ });
124
+ }
380
125
 
381
- return Component;
382
- }, [
126
+ function lazyWebComponent(props: LazyLoaderProps) {
127
+ const {
128
+ basename,
129
+ cancelation,
130
+ exposedDependencies,
131
+ eventBus,
132
+ fallbackSrc,
133
+ logService,
134
+ onRequestError,
135
+ retries,
136
+ src,
137
+ LoadingFallback,
138
+ } = props;
139
+
140
+ return lazy<(props: LoaderDataProps) => JSX.Element | null>(async () => {
141
+ let bundleInfo: BundleInfo;
142
+ try {
143
+ bundleInfo = await getBundleInfo(
383
144
  src,
384
145
  fallbackSrc,
385
146
  exposedDependencies,
147
+ cancelation.signal,
386
148
  retries,
387
- onRequestError,
388
- logService,
389
- historyManager,
390
- basename,
391
- eventBus,
392
- ]);
149
+ onRequestError
150
+ );
151
+ } catch (e) {
152
+ if (cancelation.signal.aborted) {
153
+ return {
154
+ default: () => null,
155
+ };
156
+ }
393
157
 
394
- const LoadingFallback = useMemo(
395
- () => () => {
396
- if (loadingFallback !== undefined) {
397
- return loadingFallback as ReactElement;
398
- }
158
+ throw e;
159
+ }
399
160
 
400
- return <h2>Loading...</h2>;
401
- },
402
- [loadingFallback]
403
- );
161
+ validateBundleInfo(bundleInfo, { logService, packageName: src });
404
162
 
405
- const ErrorFallback = useMemo(
406
- () => () => {
407
- if (errorFallback !== undefined) {
408
- return errorFallback as ReactElement;
409
- }
163
+ await loadScripts(bundleInfo);
410
164
 
411
- return <h2>Something went wrong</h2>;
412
- },
413
- [errorFallback]
414
- );
165
+ return {
166
+ default: webComponent({
167
+ basename,
168
+ bundleInfo,
169
+ eventBus,
170
+ exposedDependencies,
171
+ logService,
172
+ LoadingFallback,
173
+ }),
174
+ };
175
+ });
176
+ }
415
177
 
416
- return (
417
- <ErrorBoundary moduleName={src} fallback={ErrorFallback}>
418
- {isLoading && <LoadingFallback />}
419
- <Suspense fallback={null}>
420
- <Loader ref={ref} data={data} className={className} />
421
- </Suspense>
422
- </ErrorBoundary>
423
- );
424
- }
425
- );
178
+ function getFallback(node: FallbackNode | undefined, message: string): () => JSX.Element {
179
+ return () => (node !== undefined ? (node as ReactElement) : <h2>{message}</h2>);
180
+ }
package/src/register.tsx CHANGED
@@ -1,4 +1,5 @@
1
1
  import { FC, ComponentType } from 'react';
2
+ // eslint-disable-next-line react/no-deprecated
2
3
  import { render, unmountComponentAtNode } from 'react-dom';
3
4
 
4
5
  import { Log } from '@servicetitan/log-service';
@@ -1,5 +1,5 @@
1
1
  import { getMFESupportsRerendering } from '../get-mfe-supports-rerendering';
2
- import { Metadata } from '../loader';
2
+ import { Metadata } from '../get-bundle-info';
3
3
 
4
4
  describe(`[web-components] ${getMFESupportsRerendering.name}`, () => {
5
5
  let metadata: Required<Pick<Metadata, 'bundledWith' | 'dependencies'>>;
@@ -0,0 +1,76 @@
1
+ import { ExposedDependencies } from '../../common';
2
+ import { getVersionMismatches } from '../get-version-mismatches';
3
+
4
+ describe(`[web-components] ${getVersionMismatches.name}`, () => {
5
+ let sharedDependencies: Record<string, string> = {};
6
+ let dependencies: Record<string, string> = {};
7
+ let exposedDependencies: ExposedDependencies = {};
8
+
9
+ beforeEach(() => {
10
+ sharedDependencies = {
11
+ '@servicetitan/design-system': 'SharedDependencies.ServiceTitan.DesignSystem',
12
+ };
13
+
14
+ dependencies = {
15
+ '@servicetitan/design-system': '~11.8.0',
16
+ };
17
+
18
+ exposedDependencies = {
19
+ '@servicetitan/design-system': {
20
+ version: '~11.8.0',
21
+ variable: 'SharedDependencies.ServiceTitan.DesignSystem',
22
+ },
23
+ };
24
+ });
25
+
26
+ test('returns no mismatches', () => {
27
+ expect(getVersionMismatches(exposedDependencies, dependencies, sharedDependencies)).toEqual(
28
+ {}
29
+ );
30
+ });
31
+
32
+ describe('when there is a versions mismatch', () => {
33
+ beforeEach(() => (dependencies['@servicetitan/design-system'] = '~12.0.0'));
34
+
35
+ test('returns one mismatch', () => {
36
+ expect(
37
+ getVersionMismatches(exposedDependencies, dependencies, sharedDependencies)
38
+ ).toEqual({
39
+ '@servicetitan/design-system': {
40
+ host: '~11.8.0',
41
+ package: '~12.0.0',
42
+ },
43
+ });
44
+ });
45
+ });
46
+
47
+ describe('when a dependency is missing from exposed dependencies', () => {
48
+ beforeEach(() => delete exposedDependencies['@servicetitan/design-system']);
49
+
50
+ test('returns an object citing the dependency is missing from the host', () => {
51
+ expect(
52
+ getVersionMismatches(exposedDependencies, dependencies, sharedDependencies)
53
+ ).toEqual({
54
+ '@servicetitan/design-system': {
55
+ host: 'missing',
56
+ package: '~11.8.0',
57
+ },
58
+ });
59
+ });
60
+ });
61
+
62
+ describe('when a variable does not match', () => {
63
+ beforeEach(() => (exposedDependencies['@servicetitan/design-system'].variable = 'foo'));
64
+
65
+ test('returns an object citing the variable is wrong', () => {
66
+ expect(
67
+ getVersionMismatches(exposedDependencies, dependencies, sharedDependencies)
68
+ ).toEqual({
69
+ '@servicetitan/design-system': {
70
+ host: 'wrong global variable',
71
+ package: '~11.8.0',
72
+ },
73
+ });
74
+ });
75
+ });
76
+ });
@@ -0,0 +1,18 @@
1
+ import { isVersionedUrl } from '../is-versioned-url';
2
+
3
+ describe(`${isVersionedUrl.name}`, () => {
4
+ const results: [string, boolean][] = [
5
+ ['localhost:9090', false],
6
+ ['localhost:9090@1.1.1', false],
7
+ ['localhost:9090@dev', false],
8
+ ['//unpkg.servicetitan.com/@servicetitan/test', false],
9
+ ['//unpkg.servicetitan.com/@servicetitan/test@dev', false],
10
+ ['//unpkg.servicetitan.com/@servicetitan/test@0.0.0', true],
11
+ ['//unpkg.servicetitan.com/@servicetitan/test@0.0.0-hash', true],
12
+ ['//unpkg.servicetitan.com/@servicetitan/test@^16', false],
13
+ ];
14
+
15
+ test.each(results)(`providing %s returns %s`, (url, result) => {
16
+ expect(isVersionedUrl(url)).toEqual(result);
17
+ });
18
+ });