@servicetitan/web-components 36.3.0 → 36.4.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/css-injector/index.d.ts +3 -0
- package/dist/css-injector/index.d.ts.map +1 -0
- package/dist/css-injector/index.js +4 -0
- package/dist/css-injector/index.js.map +1 -0
- package/dist/css-injector/mfe-style-registry.d.ts +14 -0
- package/dist/css-injector/mfe-style-registry.d.ts.map +1 -0
- package/dist/css-injector/mfe-style-registry.js +63 -0
- package/dist/css-injector/mfe-style-registry.js.map +1 -0
- package/dist/css-injector/with-css-injector.d.ts +6 -0
- package/dist/css-injector/with-css-injector.d.ts.map +1 -0
- package/dist/css-injector/with-css-injector.js +38 -0
- package/dist/css-injector/with-css-injector.js.map +1 -0
- package/dist/globals.d.ts +3 -3
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +19 -4
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/register-headless.d.ts.map +1 -1
- package/dist/register-headless.js +1 -0
- package/dist/register-headless.js.map +1 -1
- package/dist/register.d.ts.map +1 -1
- package/dist/register.js +43 -37
- package/dist/register.js.map +1 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +2 -2
- package/dist/render.js.map +1 -1
- package/dist/utils/get-bundle-info.d.ts.map +1 -1
- package/dist/utils/get-bundle-info.js +11 -4
- package/dist/utils/get-bundle-info.js.map +1 -1
- package/dist/utils/get-entry-points.d.ts +1 -0
- package/dist/utils/get-entry-points.d.ts.map +1 -1
- package/dist/utils/get-entry-points.js.map +1 -1
- package/package.json +23 -12
- package/src/__mocks__/register-anvil2-harness.tsx +49 -0
- package/src/__mocks__/test-component.tsx +9 -2
- package/src/__tests__/loader.test.tsx +32 -0
- package/src/__tests__/register-anvil2.test.tsx +21 -56
- package/src/css-injector/__tests__/mfe-style-registry.test.ts +163 -0
- package/src/css-injector/__tests__/with-css-injector.test.tsx +128 -0
- package/src/css-injector/index.ts +2 -0
- package/src/css-injector/mfe-style-registry.ts +48 -0
- package/src/css-injector/with-css-injector.tsx +40 -0
- package/src/globals.ts +30 -10
- package/src/index.ts +1 -0
- package/src/register-headless.ts +1 -0
- package/src/register.tsx +54 -43
- package/src/render.ts +2 -3
- package/src/utils/get-bundle-info.ts +12 -5
- package/src/utils/get-entry-points.ts +1 -0
|
@@ -43,12 +43,12 @@ export async function getBundleInfo({ cache, mainPackageUrl, fallbackPackageUrl,
|
|
|
43
43
|
const embed = !mismatch && supportLightBundles;
|
|
44
44
|
resolvedBundleType = embed ? 'light' : 'full';
|
|
45
45
|
}
|
|
46
|
-
const
|
|
46
|
+
const bundleBaseUrl = `${exactPackageUrl}/dist/bundle/${resolvedBundleType}`;
|
|
47
47
|
let entrypoints;
|
|
48
48
|
if ((_metadata_entrypoints = metadata.entrypoints) === null || _metadata_entrypoints === void 0 ? void 0 : _metadata_entrypoints[resolvedBundleType]) {
|
|
49
49
|
entrypoints = metadata.entrypoints[resolvedBundleType];
|
|
50
50
|
} else {
|
|
51
|
-
entrypoints = await getEntrypoints(
|
|
51
|
+
entrypoints = await getEntrypoints(`${bundleBaseUrl}/entrypoints.json`, requestCache);
|
|
52
52
|
}
|
|
53
53
|
const disposer = ()=>{
|
|
54
54
|
if (resolvedBundleType === 'light') {
|
|
@@ -58,9 +58,16 @@ export async function getBundleInfo({ cache, mainPackageUrl, fallbackPackageUrl,
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
};
|
|
61
|
+
let baseUrl = bundleBaseUrl;
|
|
62
|
+
// Per-MFE dev-server port only happens during local development
|
|
63
|
+
if (process.env.NODE_ENV !== 'production' && entrypoints.port) {
|
|
64
|
+
const url = new URL(baseUrl);
|
|
65
|
+
url.port = String(entrypoints.port);
|
|
66
|
+
baseUrl = url.toString();
|
|
67
|
+
}
|
|
61
68
|
const urls = {
|
|
62
|
-
css: entrypoints.css.map(
|
|
63
|
-
js: entrypoints.js.map(
|
|
69
|
+
css: entrypoints.css.map((path)=>`${baseUrl}/${path}`),
|
|
70
|
+
js: entrypoints.js.map((path)=>`${baseUrl}/${path}`)
|
|
64
71
|
};
|
|
65
72
|
const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;
|
|
66
73
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/get-bundle-info.ts"],"sourcesContent":["import { ExposedDependencies, getVersionMismatches } from '@servicetitan/startup-utils';\nimport get from 'lodash.get';\nimport semver from 'semver';\nimport { ExposedInstanceDependencies, getJson, GetJsonError } from '../common';\nimport type { Metadata } from '../types';\nimport { getCacheSetting } from './get-cache-setting';\nimport { getCachedMetadata } from './get-cached-metadata';\nimport { EntryPoints, getEntrypoints } from './get-entry-points';\nimport { getMFEHasCompatibleLaunchDarklyVersion } from './get-mfe-has-compatible-launchdarkly-version';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { getMFESupportsRerendering } from './get-mfe-supports-rerendering';\n\nexport type BundleInfo = Awaited<ReturnType<typeof getBundleInfo>>;\n\ninterface GetBundleInfoProps {\n cache: boolean | number;\n exposedDependencies?: ExposedDependencies;\n exposedInstanceDependencies?: ExposedInstanceDependencies;\n fallbackPackageUrl?: string;\n isHeadless?: boolean;\n mainPackageUrl: string;\n onRequestError?: (error: GetJsonError) => void;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport async function getBundleInfo({\n cache,\n mainPackageUrl,\n fallbackPackageUrl,\n exposedDependencies = {},\n exposedInstanceDependencies = {},\n isHeadless,\n signal,\n retries,\n onRequestError,\n}: GetBundleInfoProps) {\n const {\n cacheHit,\n data: metadata,\n url: packageUrl,\n } = await getCachedMetadata({\n cache,\n fallbackPackageUrl,\n mainPackageUrl,\n onRequestError,\n retries,\n signal,\n });\n\n const { bundledWith, name, sharedDependencies } = metadata;\n const exactPackageUrl = packageUrl.replace('/dist/metadata.json', '');\n const requestCache = getCacheSetting(exactPackageUrl);\n\n let dependencies: NonNullable<Metadata['dependencies']>;\n if (metadata.dependencies) {\n dependencies = metadata.dependencies;\n } else {\n const { data: packageJson } = await getJson(`${exactPackageUrl}/package.json`, {\n cache: requestCache,\n retries,\n signal,\n });\n\n dependencies = packageJson.dependencies;\n }\n\n let mismatch: ReturnType<typeof getVersionMismatches>;\n let resolvedBundleType: 'headless' | 'light' | 'full';\n\n if (isHeadless) {\n resolvedBundleType = 'headless';\n } else {\n mismatch = getVersionMismatches(exposedDependencies, dependencies, sharedDependencies);\n const startupVersion = bundledWith?.['@servicetitan/startup'];\n const supportLightBundles = !!startupVersion && semver.gte(startupVersion, '17.0.0');\n const embed = !mismatch && supportLightBundles;\n resolvedBundleType = embed ? 'light' : 'full';\n }\n\n const
|
|
1
|
+
{"version":3,"sources":["../../src/utils/get-bundle-info.ts"],"sourcesContent":["import { ExposedDependencies, getVersionMismatches } from '@servicetitan/startup-utils';\nimport get from 'lodash.get';\nimport semver from 'semver';\nimport { ExposedInstanceDependencies, getJson, GetJsonError } from '../common';\nimport type { Metadata } from '../types';\nimport { getCacheSetting } from './get-cache-setting';\nimport { getCachedMetadata } from './get-cached-metadata';\nimport { EntryPoints, getEntrypoints } from './get-entry-points';\nimport { getMFEHasCompatibleLaunchDarklyVersion } from './get-mfe-has-compatible-launchdarkly-version';\nimport { getMFEHasUniqueChunkNamespace } from './get-mfe-has-unique-chunk-namespace';\nimport { getMFESupportsRerendering } from './get-mfe-supports-rerendering';\n\nexport type BundleInfo = Awaited<ReturnType<typeof getBundleInfo>>;\n\ninterface GetBundleInfoProps {\n cache: boolean | number;\n exposedDependencies?: ExposedDependencies;\n exposedInstanceDependencies?: ExposedInstanceDependencies;\n fallbackPackageUrl?: string;\n isHeadless?: boolean;\n mainPackageUrl: string;\n onRequestError?: (error: GetJsonError) => void;\n retries?: number;\n signal?: AbortSignal;\n}\n\nexport async function getBundleInfo({\n cache,\n mainPackageUrl,\n fallbackPackageUrl,\n exposedDependencies = {},\n exposedInstanceDependencies = {},\n isHeadless,\n signal,\n retries,\n onRequestError,\n}: GetBundleInfoProps) {\n const {\n cacheHit,\n data: metadata,\n url: packageUrl,\n } = await getCachedMetadata({\n cache,\n fallbackPackageUrl,\n mainPackageUrl,\n onRequestError,\n retries,\n signal,\n });\n\n const { bundledWith, name, sharedDependencies } = metadata;\n const exactPackageUrl = packageUrl.replace('/dist/metadata.json', '');\n const requestCache = getCacheSetting(exactPackageUrl);\n\n let dependencies: NonNullable<Metadata['dependencies']>;\n if (metadata.dependencies) {\n dependencies = metadata.dependencies;\n } else {\n const { data: packageJson } = await getJson(`${exactPackageUrl}/package.json`, {\n cache: requestCache,\n retries,\n signal,\n });\n\n dependencies = packageJson.dependencies;\n }\n\n let mismatch: ReturnType<typeof getVersionMismatches>;\n let resolvedBundleType: 'headless' | 'light' | 'full';\n\n if (isHeadless) {\n resolvedBundleType = 'headless';\n } else {\n mismatch = getVersionMismatches(exposedDependencies, dependencies, sharedDependencies);\n const startupVersion = bundledWith?.['@servicetitan/startup'];\n const supportLightBundles = !!startupVersion && semver.gte(startupVersion, '17.0.0');\n const embed = !mismatch && supportLightBundles;\n resolvedBundleType = embed ? 'light' : 'full';\n }\n\n const bundleBaseUrl = `${exactPackageUrl}/dist/bundle/${resolvedBundleType}`;\n\n let entrypoints: EntryPoints;\n if (metadata.entrypoints?.[resolvedBundleType]) {\n entrypoints = metadata.entrypoints[resolvedBundleType];\n } else {\n entrypoints = await getEntrypoints(`${bundleBaseUrl}/entrypoints.json`, requestCache);\n }\n\n const disposer = () => {\n if (resolvedBundleType === 'light') {\n const path = Object.values<string>(sharedDependencies).shift();\n if (path) {\n get(window, path).getStorage().clear(name);\n }\n }\n };\n\n let baseUrl = bundleBaseUrl;\n // Per-MFE dev-server port only happens during local development\n if (process.env.NODE_ENV !== 'production' && entrypoints.port) {\n const url = new URL(baseUrl);\n url.port = String(entrypoints.port);\n baseUrl = url.toString();\n }\n\n const urls: EntryPoints = {\n css: entrypoints.css.map(path => `${baseUrl}/${path}`),\n js: entrypoints.js.map(path => `${baseUrl}/${path}`),\n };\n\n const elementName = resolvedBundleType === 'headless' ? `headless-${name}` : name;\n\n return {\n bundledWith,\n cacheHit,\n disposer,\n mfeSupportsRerendering: getMFESupportsRerendering(bundledWith, dependencies),\n mfeHasUniqueChunkNamespace: getMFEHasUniqueChunkNamespace(bundledWith),\n mfeHasCompatibleLaunchDarklyVersion: getMFEHasCompatibleLaunchDarklyVersion(\n bundledWith,\n exposedInstanceDependencies\n ),\n mismatch,\n resolvedBundleType,\n urls,\n WebComponent: elementName,\n version: metadata.version,\n module: entrypoints.module,\n };\n}\n"],"names":["getVersionMismatches","get","semver","getJson","getCacheSetting","getCachedMetadata","getEntrypoints","getMFEHasCompatibleLaunchDarklyVersion","getMFEHasUniqueChunkNamespace","getMFESupportsRerendering","getBundleInfo","cache","mainPackageUrl","fallbackPackageUrl","exposedDependencies","exposedInstanceDependencies","isHeadless","signal","retries","onRequestError","metadata","cacheHit","data","url","packageUrl","bundledWith","name","sharedDependencies","exactPackageUrl","replace","requestCache","dependencies","packageJson","mismatch","resolvedBundleType","startupVersion","supportLightBundles","gte","embed","bundleBaseUrl","entrypoints","disposer","path","Object","values","shift","window","getStorage","clear","baseUrl","process","env","NODE_ENV","port","URL","String","toString","urls","css","map","js","elementName","mfeSupportsRerendering","mfeHasUniqueChunkNamespace","mfeHasCompatibleLaunchDarklyVersion","WebComponent","version","module"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ export interface EntryPoints {
|
|
|
2
2
|
css: string[];
|
|
3
3
|
js: string[];
|
|
4
4
|
module?: true;
|
|
5
|
+
port?: number;
|
|
5
6
|
}
|
|
6
7
|
export declare function getEntrypoints(url: string, cache: 'no-store' | undefined, signal?: AbortSignal): Promise<EntryPoints>;
|
|
7
8
|
//# sourceMappingURL=get-entry-points.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-entry-points.d.ts","sourceRoot":"","sources":["../../src/utils/get-entry-points.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;CACjB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,WAAW,CAAC,CAWtB"}
|
|
1
|
+
{"version":3,"file":"get-entry-points.d.ts","sourceRoot":"","sources":["../../src/utils/get-entry-points.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,EAAE,EAAE,MAAM,EAAE,CAAC;IACb,MAAM,CAAC,EAAE,IAAI,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,cAAc,CAChC,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,UAAU,GAAG,SAAS,EAC7B,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,WAAW,CAAC,CAWtB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/get-entry-points.ts"],"sourcesContent":["import { getJson } from '../common';\n\nexport interface EntryPoints {\n css: string[];\n js: string[];\n module?: true;\n}\n\nexport async function getEntrypoints(\n url: string,\n cache: 'no-store' | undefined,\n signal?: AbortSignal\n): Promise<EntryPoints> {\n try {\n return (\n await getJson(url, {\n cache,\n signal,\n })\n ).data;\n } catch {\n return { css: ['index.css'], js: ['index.js'] };\n }\n}\n"],"names":["getJson","getEntrypoints","url","cache","signal","data","css","js"],"mappings":"AAAA,SAASA,OAAO,QAAQ,YAAY;
|
|
1
|
+
{"version":3,"sources":["../../src/utils/get-entry-points.ts"],"sourcesContent":["import { getJson } from '../common';\n\nexport interface EntryPoints {\n css: string[];\n js: string[];\n module?: true;\n port?: number;\n}\n\nexport async function getEntrypoints(\n url: string,\n cache: 'no-store' | undefined,\n signal?: AbortSignal\n): Promise<EntryPoints> {\n try {\n return (\n await getJson(url, {\n cache,\n signal,\n })\n ).data;\n } catch {\n return { css: ['index.css'], js: ['index.js'] };\n }\n}\n"],"names":["getJson","getEntrypoints","url","cache","signal","data","css","js"],"mappings":"AAAA,SAASA,OAAO,QAAQ,YAAY;AASpC,OAAO,eAAeC,eAClBC,GAAW,EACXC,KAA6B,EAC7BC,MAAoB;IAEpB,IAAI;QACA,OAAO,AACH,CAAA,MAAMJ,QAAQE,KAAK;YACfC;YACAC;QACJ,EAAC,EACHC,IAAI;IACV,EAAE,eAAM;QACJ,OAAO;YAAEC,KAAK;gBAAC;aAAY;YAAEC,IAAI;gBAAC;aAAW;QAAC;IAClD;AACJ"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@servicetitan/web-components",
|
|
3
|
-
"version": "36.
|
|
3
|
+
"version": "36.4.0",
|
|
4
4
|
"description": "Framework to build, register, and load Microfrontends into host applications",
|
|
5
5
|
"homepage": "https://docs.st.dev/docs/frontend/uikit/web-components",
|
|
6
6
|
"repository": {
|
|
@@ -10,24 +10,35 @@
|
|
|
10
10
|
},
|
|
11
11
|
"main": "./dist/index.js",
|
|
12
12
|
"typings": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./register-headless": {
|
|
19
|
+
"types": "./dist/register-headless.d.ts",
|
|
20
|
+
"default": "./dist/register-headless.js"
|
|
21
|
+
},
|
|
22
|
+
"./*": "./dist/*.js"
|
|
23
|
+
},
|
|
13
24
|
"sideEffects": false,
|
|
14
25
|
"files": [
|
|
15
26
|
"dist",
|
|
16
27
|
"src"
|
|
17
28
|
],
|
|
18
29
|
"dependencies": {
|
|
19
|
-
"@servicetitan/error-boundary": "36.
|
|
20
|
-
"@servicetitan/startup-utils": "36.
|
|
30
|
+
"@servicetitan/error-boundary": "36.4.0",
|
|
31
|
+
"@servicetitan/startup-utils": "36.4.0",
|
|
21
32
|
"lodash.get": "~4.4.2",
|
|
22
33
|
"mitt": "~3.0.1",
|
|
23
|
-
"semver": "~7.
|
|
34
|
+
"semver": "~7.8.1"
|
|
24
35
|
},
|
|
25
36
|
"devDependencies": {
|
|
26
|
-
"@servicetitan/anvil2": "^3.0.
|
|
37
|
+
"@servicetitan/anvil2": "^3.0.8",
|
|
27
38
|
"@servicetitan/design-system": "~14.5.3",
|
|
28
|
-
"@servicetitan/launchdarkly-service": "36.
|
|
29
|
-
"@servicetitan/log-service": "36.
|
|
30
|
-
"@servicetitan/react-ioc": "36.
|
|
39
|
+
"@servicetitan/launchdarkly-service": "36.4.0",
|
|
40
|
+
"@servicetitan/log-service": "36.4.0",
|
|
41
|
+
"@servicetitan/react-ioc": "36.4.0",
|
|
31
42
|
"@testing-library/dom": "^10.4.1",
|
|
32
43
|
"@testing-library/jest-dom": "^6.9.1",
|
|
33
44
|
"@testing-library/react": "^16.3.2",
|
|
@@ -45,9 +56,9 @@
|
|
|
45
56
|
"peerDependencies": {
|
|
46
57
|
"@servicetitan/anvil2": ">=1.5.0",
|
|
47
58
|
"@servicetitan/design-system": ">=14.5.3",
|
|
48
|
-
"@servicetitan/launchdarkly-service": "36.
|
|
49
|
-
"@servicetitan/log-service": "36.
|
|
50
|
-
"@servicetitan/react-ioc": "36.
|
|
59
|
+
"@servicetitan/launchdarkly-service": "36.4.0",
|
|
60
|
+
"@servicetitan/log-service": "36.4.0",
|
|
61
|
+
"@servicetitan/react-ioc": "36.4.0",
|
|
51
62
|
"history": "~4.10.1",
|
|
52
63
|
"react": ">=17.0.2",
|
|
53
64
|
"react-dom": ">=17.0.2"
|
|
@@ -69,5 +80,5 @@
|
|
|
69
80
|
"cli": {
|
|
70
81
|
"webpack": false
|
|
71
82
|
},
|
|
72
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "8beab9ae3a70ce381e8cd53f2de8691640e96c6e"
|
|
73
84
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import { MockComponent, registerComponent } from './index';
|
|
3
|
+
|
|
4
|
+
/* Wrapper components injected by each optional library. */
|
|
5
|
+
export const optionalLibraryWrappers: Record<string, string[]> = {
|
|
6
|
+
'@servicetitan/anvil2': ['PortalProvider'],
|
|
7
|
+
'@servicetitan/design-system': [
|
|
8
|
+
'DefaultPortalContext.Provider',
|
|
9
|
+
'TablePopupPropsContext.Provider',
|
|
10
|
+
],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export interface RegisterHarness {
|
|
14
|
+
Page: {
|
|
15
|
+
readonly componentShadowRoot: ShadowRoot;
|
|
16
|
+
readonly portalShadowRoot: ShadowRoot;
|
|
17
|
+
};
|
|
18
|
+
subject: () => ReturnType<typeof render>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createRegisterHarness(
|
|
22
|
+
componentName: string,
|
|
23
|
+
options?: Parameters<typeof registerComponent>[2]
|
|
24
|
+
): RegisterHarness {
|
|
25
|
+
const Page = {
|
|
26
|
+
get componentShadowRoot() {
|
|
27
|
+
return document.querySelector(componentName)!.shadowRoot!;
|
|
28
|
+
},
|
|
29
|
+
get portalShadowRoot() {
|
|
30
|
+
return document.querySelector('body')!.lastElementChild!.shadowRoot!;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
registerComponent(MockComponent, componentName, {
|
|
36
|
+
sharedDependenciesNames: ['@servicetitan/anvil2'],
|
|
37
|
+
...options,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const subject = () => {
|
|
42
|
+
jest.spyOn(console, 'error').mockImplementation(jest.fn()); // suppress "ReactDOM.render deprecated" error
|
|
43
|
+
const Component: any = componentName;
|
|
44
|
+
const handleRef = (wc: any) => wc?.provide({ foo: 'bar' }); // triggers rendering
|
|
45
|
+
return render(<Component ref={handleRef} />);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return { Page, subject };
|
|
49
|
+
}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { TablePopupPropsContext } from '@servicetitan/design-system';
|
|
2
|
-
import { FC, useContext } from 'react';
|
|
2
|
+
import { createContext, FC, useContext } from 'react';
|
|
3
3
|
import { useMFEDataContext, useMFEMetadataContext } from '../contexts';
|
|
4
4
|
|
|
5
5
|
jest.mock('@progress/kendo-licensing', () => ({ validatePackage: jest.fn() }));
|
|
6
6
|
|
|
7
|
+
const FallbackContext = createContext(undefined);
|
|
8
|
+
|
|
7
9
|
export const MockComponent: FC<Record<string, any>> = props => {
|
|
8
10
|
const data = useMFEDataContext();
|
|
9
11
|
const metadata = useMFEMetadataContext();
|
|
10
|
-
|
|
12
|
+
/*
|
|
13
|
+
* TablePopupPropsContext is undefined when a test mocks
|
|
14
|
+
* @servicetitan/design-system as an empty object. Fall back to
|
|
15
|
+
* a no-op context so the component renders without it.
|
|
16
|
+
*/
|
|
17
|
+
const popupProps = useContext(TablePopupPropsContext ?? FallbackContext);
|
|
11
18
|
const appendTo = popupProps?.({}).appendTo;
|
|
12
19
|
|
|
13
20
|
return (
|
|
@@ -403,6 +403,38 @@ describe('[web-components] Loader', () => {
|
|
|
403
403
|
() => `${baseUrl}/dist/bundle/light/${metadata.entrypoints!.light.css[0]}`
|
|
404
404
|
);
|
|
405
405
|
|
|
406
|
+
describe('when the entrypoint specifies a dev-server port', () => {
|
|
407
|
+
const port = 3000;
|
|
408
|
+
|
|
409
|
+
beforeEach(() => {
|
|
410
|
+
Object.assign(metadata.entrypoints!.light, { port });
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
function withPort(url: string) {
|
|
414
|
+
const result = new URL(url);
|
|
415
|
+
result.port = String(port);
|
|
416
|
+
return String(result);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
itAddsJsEntrypointToWebComponent(
|
|
420
|
+
'entrypoint with dev-server port',
|
|
421
|
+
() => `${withPort(baseUrl)}/dist/bundle/light/${metadata.entrypoints!.light.js[0]}`
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
describe('when NODE_ENV is production', () => {
|
|
425
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
426
|
+
|
|
427
|
+
beforeEach(() => (process.env.NODE_ENV = 'production'));
|
|
428
|
+
|
|
429
|
+
afterEach(() => (process.env.NODE_ENV = originalNodeEnv));
|
|
430
|
+
|
|
431
|
+
itAddsJsEntrypointToWebComponent(
|
|
432
|
+
'entrypoint without dev-server port',
|
|
433
|
+
() => `${baseUrl}/dist/bundle/light/${metadata.entrypoints!.light.js[0]}`
|
|
434
|
+
);
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
406
438
|
describe('when entrypoint has module: true', () => {
|
|
407
439
|
beforeEach(() => {
|
|
408
440
|
metadata.entrypoints!.light.module = true;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import '@testing-library/jest-dom';
|
|
2
|
-
import {
|
|
2
|
+
import { within } from '@testing-library/react';
|
|
3
3
|
import { Fragment } from 'react';
|
|
4
|
-
import { expectStylesInDom
|
|
4
|
+
import { expectStylesInDom } from '../__mocks__';
|
|
5
|
+
import {
|
|
6
|
+
createRegisterHarness,
|
|
7
|
+
optionalLibraryWrappers,
|
|
8
|
+
} from '../__mocks__/register-anvil2-harness';
|
|
5
9
|
|
|
6
10
|
function mockComponent(name: string) {
|
|
7
11
|
return {
|
|
@@ -37,30 +41,7 @@ jest.mock('@servicetitan/design-system', () => ({
|
|
|
37
41
|
}));
|
|
38
42
|
|
|
39
43
|
describe('[web-components] register', () => {
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
class Page {
|
|
43
|
-
static get componentShadowRoot() {
|
|
44
|
-
return document.querySelector(componentName)!.shadowRoot!;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static get portalShadowRoot() {
|
|
48
|
-
return document.querySelector('body')!.lastElementChild!.shadowRoot!;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
beforeAll(() => {
|
|
53
|
-
registerComponent(MockComponent, componentName, {
|
|
54
|
-
sharedDependenciesNames: ['@servicetitan/anvil2'],
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
const subject = () => {
|
|
59
|
-
jest.spyOn(console, 'error').mockImplementation(jest.fn()); // suppress "ReactDOM.render deprecated" error
|
|
60
|
-
const Component: any = componentName;
|
|
61
|
-
const handleRef = (webComponent: any) => webComponent?.provide({ foo: 'bar' }); // triggers rendering
|
|
62
|
-
return render(<Component ref={handleRef} />);
|
|
63
|
-
};
|
|
44
|
+
const { Page, subject } = createRegisterHarness('mock-anvil2-app');
|
|
64
45
|
|
|
65
46
|
describe('when Anvil2 bundle is present in the DOM', () => {
|
|
66
47
|
let cssLink: HTMLLinkElement;
|
|
@@ -90,44 +71,28 @@ describe('[web-components] register', () => {
|
|
|
90
71
|
});
|
|
91
72
|
});
|
|
92
73
|
|
|
93
|
-
|
|
94
|
-
test(
|
|
74
|
+
for (const [library, wrappers] of Object.entries(optionalLibraryWrappers)) {
|
|
75
|
+
test.each(wrappers)(`wraps component within %s`, name => {
|
|
95
76
|
subject();
|
|
96
77
|
|
|
97
78
|
const element = within(Page.componentShadowRoot as any).queryByText(`<${name}>`);
|
|
98
|
-
|
|
99
|
-
expect(element).toBeInTheDocument();
|
|
100
|
-
} else {
|
|
101
|
-
expect(element).not.toBeInTheDocument();
|
|
102
|
-
}
|
|
79
|
+
expect(element).toBeInTheDocument();
|
|
103
80
|
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
['PortalProvider', 'DefaultPortalContext.Provider', 'TablePopupPropsContext.Provider'].forEach(
|
|
107
|
-
name => itWrapsComponentWithin(name)
|
|
108
|
-
);
|
|
109
81
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
82
|
+
describe(`when ${library} is not present`, () => {
|
|
83
|
+
beforeAll(() => {
|
|
84
|
+
jest.resetModules();
|
|
85
|
+
jest.mock(library, () => {
|
|
86
|
+
throw new Error('Cannot find module');
|
|
87
|
+
});
|
|
115
88
|
});
|
|
116
|
-
});
|
|
117
89
|
|
|
118
|
-
|
|
119
|
-
|
|
90
|
+
test.each(wrappers)('does not wrap component within %s', name => {
|
|
91
|
+
subject();
|
|
120
92
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
jest.resetModules();
|
|
124
|
-
jest.mock('@servicetitan/design-system', () => {
|
|
125
|
-
throw new Error('Cannot find module');
|
|
93
|
+
const element = within(Page.componentShadowRoot as any).queryByText(`<${name}>`);
|
|
94
|
+
expect(element).not.toBeInTheDocument();
|
|
126
95
|
});
|
|
127
96
|
});
|
|
128
|
-
|
|
129
|
-
['DefaultPortalContext.Provider', 'TablePopupPropsContext.Provider'].forEach(name =>
|
|
130
|
-
itWrapsComponentWithin(name, false)
|
|
131
|
-
);
|
|
132
|
-
});
|
|
97
|
+
}
|
|
133
98
|
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { MfeStyleRegistry } from '../mfe-style-registry';
|
|
2
|
+
|
|
3
|
+
beforeAll(() => {
|
|
4
|
+
if (!CSSStyleSheet.prototype.replaceSync) {
|
|
5
|
+
CSSStyleSheet.prototype.replaceSync = jest.fn();
|
|
6
|
+
}
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe('[web-components] MfeStyleRegistry', () => {
|
|
10
|
+
const createRoot = () => {
|
|
11
|
+
const root = document.createElement('div').attachShadow({ mode: 'open' });
|
|
12
|
+
root.adoptedStyleSheets = [];
|
|
13
|
+
return root;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
delete globalThis.__mfeStyles__;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe(MfeStyleRegistry.for.name, () => {
|
|
21
|
+
test('initializes globalThis.__mfeStyles__', () => {
|
|
22
|
+
MfeStyleRegistry.for('test-mfe');
|
|
23
|
+
|
|
24
|
+
expect(globalThis.__mfeStyles__).toBeDefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('returns the same registry for the same component', () => {
|
|
28
|
+
const a = MfeStyleRegistry.for('test-mfe');
|
|
29
|
+
const b = MfeStyleRegistry.for('test-mfe');
|
|
30
|
+
|
|
31
|
+
expect(a).toBe(b);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('returns different registries for different components', () => {
|
|
35
|
+
const a = MfeStyleRegistry.for('mfe-a');
|
|
36
|
+
const b = MfeStyleRegistry.for('mfe-b');
|
|
37
|
+
|
|
38
|
+
expect(a).not.toBe(b);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('injectSheet', () => {
|
|
43
|
+
const defaultCss = '.foo { color: red; }';
|
|
44
|
+
const defaultSheetId = 'sheet-1';
|
|
45
|
+
|
|
46
|
+
let registry: MfeStyleRegistry;
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
registry = MfeStyleRegistry.for('test-mfe');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const subject = (sheetId = defaultSheetId, css = defaultCss) =>
|
|
53
|
+
registry.injectSheet(sheetId, css);
|
|
54
|
+
|
|
55
|
+
test('creates a new CSSStyleSheet and stores it', () => {
|
|
56
|
+
subject();
|
|
57
|
+
|
|
58
|
+
const sheet = registry.sheets.get('sheet-1');
|
|
59
|
+
expect(sheet).toBeInstanceOf(CSSStyleSheet);
|
|
60
|
+
expect(sheet!.replaceSync).toHaveBeenCalledWith(defaultCss);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('adopts new sheets into observed roots', () => {
|
|
64
|
+
const root = createRoot();
|
|
65
|
+
registry.observeRoot(root);
|
|
66
|
+
|
|
67
|
+
subject();
|
|
68
|
+
|
|
69
|
+
expect(root.adoptedStyleSheets).toContain(registry.sheets.get('sheet-1'));
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('when sheet with the same ID already exists', () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
subject();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('updates existing sheet in place via replaceSync', () => {
|
|
78
|
+
const sheet = registry.sheets.get(defaultSheetId)!;
|
|
79
|
+
|
|
80
|
+
const newCSS = '.foo { color: blue; }';
|
|
81
|
+
subject(defaultSheetId, newCSS);
|
|
82
|
+
|
|
83
|
+
expect(registry.sheets.get(defaultSheetId)).toBe(sheet);
|
|
84
|
+
expect(sheet.replaceSync).toHaveBeenLastCalledWith(newCSS);
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('observeRoot', () => {
|
|
90
|
+
let registry: MfeStyleRegistry;
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
registry = MfeStyleRegistry.for('test-mfe');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test('adds root to the roots set', () => {
|
|
97
|
+
const root = createRoot();
|
|
98
|
+
|
|
99
|
+
registry.observeRoot(root);
|
|
100
|
+
|
|
101
|
+
expect(registry.roots.has(root)).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('when root is already observed', () => {
|
|
105
|
+
let root: ShadowRoot;
|
|
106
|
+
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
registry.injectSheet('a', '.a {}');
|
|
109
|
+
root = createRoot();
|
|
110
|
+
registry.observeRoot(root);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('does not duplicate sheets', () => {
|
|
114
|
+
registry.observeRoot(root);
|
|
115
|
+
|
|
116
|
+
expect(root.adoptedStyleSheets).toHaveLength(1);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('adopts all existing sheets into the root', () => {
|
|
121
|
+
registry.injectSheet('a', '.a {}');
|
|
122
|
+
registry.injectSheet('b', '.b {}');
|
|
123
|
+
const root = createRoot();
|
|
124
|
+
|
|
125
|
+
registry.observeRoot(root);
|
|
126
|
+
|
|
127
|
+
expect(root.adoptedStyleSheets).toEqual([...registry.sheets.values()]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('unobserveRoot', () => {
|
|
132
|
+
let registry: MfeStyleRegistry;
|
|
133
|
+
let root: ShadowRoot;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
registry = MfeStyleRegistry.for('test-mfe');
|
|
137
|
+
root = createRoot();
|
|
138
|
+
const defaultSheetId = 'sheet-1';
|
|
139
|
+
registry.injectSheet(defaultSheetId, '.foo {}');
|
|
140
|
+
registry.observeRoot(root);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test('removes root from the roots set', () => {
|
|
144
|
+
registry.unobserveRoot(root);
|
|
145
|
+
|
|
146
|
+
expect(registry.roots.has(root)).toBe(false);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('removes adopted sheets from the root', () => {
|
|
150
|
+
registry.unobserveRoot(root);
|
|
151
|
+
|
|
152
|
+
expect(root.adoptedStyleSheets).toHaveLength(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('stops receiving newly adopted sheets', () => {
|
|
156
|
+
registry.unobserveRoot(root);
|
|
157
|
+
|
|
158
|
+
registry.injectSheet('sheet-2', '.bar {}');
|
|
159
|
+
|
|
160
|
+
expect(root.adoptedStyleSheets).toHaveLength(0);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|