@backstage/frontend-test-utils 0.4.5 → 0.5.0-next.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/CHANGELOG.md +105 -8
- package/dist/apis/AlertApi/MockAlertApi.esm.js +64 -0
- package/dist/apis/AlertApi/MockAlertApi.esm.js.map +1 -0
- package/dist/apis/ConfigApi/MockConfigApi.esm.js +76 -0
- package/dist/apis/ConfigApi/MockConfigApi.esm.js.map +1 -0
- package/dist/apis/ErrorApi/MockErrorApi.esm.js +44 -0
- package/dist/apis/ErrorApi/MockErrorApi.esm.js.map +1 -0
- package/dist/apis/FeatureFlagsApi/MockFeatureFlagsApi.esm.js +55 -0
- package/dist/apis/FeatureFlagsApi/MockFeatureFlagsApi.esm.js.map +1 -0
- package/dist/apis/FetchApi/MockFetchApi.esm.js +56 -0
- package/dist/apis/FetchApi/MockFetchApi.esm.js.map +1 -0
- package/dist/apis/MockWithApiFactory.esm.js +28 -0
- package/dist/apis/MockWithApiFactory.esm.js.map +1 -0
- package/dist/apis/PermissionApi/MockPermissionApi.esm.js +13 -0
- package/dist/apis/PermissionApi/MockPermissionApi.esm.js.map +1 -0
- package/dist/apis/StorageApi/MockStorageApi.esm.js +98 -0
- package/dist/apis/StorageApi/MockStorageApi.esm.js.map +1 -0
- package/dist/apis/TestApiProvider.esm.js +31 -0
- package/dist/apis/TestApiProvider.esm.js.map +1 -0
- package/dist/apis/TranslationApi/MockTranslationApi.esm.js +68 -0
- package/dist/apis/TranslationApi/MockTranslationApi.esm.js.map +1 -0
- package/dist/apis/createApiMock.esm.js +25 -0
- package/dist/apis/createApiMock.esm.js.map +1 -0
- package/dist/apis/mockApis.esm.js +195 -0
- package/dist/apis/mockApis.esm.js.map +1 -0
- package/dist/app/createExtensionTester.esm.js +42 -3
- package/dist/app/createExtensionTester.esm.js.map +1 -1
- package/dist/app/renderInTestApp.esm.js +24 -3
- package/dist/app/renderInTestApp.esm.js.map +1 -1
- package/dist/app/renderTestApp.esm.js +48 -5
- package/dist/app/renderTestApp.esm.js.map +1 -1
- package/dist/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.esm.js +67 -0
- package/dist/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.esm.js.map +1 -0
- package/dist/core-app-api/src/apis/system/ApiAggregator.esm.js +18 -0
- package/dist/core-app-api/src/apis/system/ApiAggregator.esm.js.map +1 -0
- package/dist/core-app-api/src/apis/system/ApiProvider.esm.js +26 -0
- package/dist/core-app-api/src/apis/system/ApiProvider.esm.js.map +1 -0
- package/dist/frontend-app-api/src/tree/instantiateAppNodeTree.esm.js +48 -17
- package/dist/frontend-app-api/src/tree/instantiateAppNodeTree.esm.js.map +1 -1
- package/dist/frontend-app-api/src/tree/resolveAppNodeSpecs.esm.js +11 -5
- package/dist/frontend-app-api/src/tree/resolveAppNodeSpecs.esm.js.map +1 -1
- package/dist/frontend-app-api/src/tree/resolveAppTree.esm.js +3 -0
- package/dist/frontend-app-api/src/tree/resolveAppTree.esm.js.map +1 -1
- package/dist/frontend-app-api/src/wiring/createErrorCollector.esm.js.map +1 -1
- package/dist/frontend-plugin-api/src/translation/TranslationRef.esm.js +13 -0
- package/dist/frontend-plugin-api/src/translation/TranslationRef.esm.js.map +1 -0
- package/dist/frontend-plugin-api/src/wiring/resolveExtensionDefinition.esm.js.map +1 -1
- package/dist/index.d.ts +785 -12
- package/dist/index.esm.js +5 -2
- package/dist/index.esm.js.map +1 -1
- package/package.json +25 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockStorageApi.esm.js","sources":["../../../src/apis/StorageApi/MockStorageApi.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StorageApi, StorageValueSnapshot } from '@backstage/core-plugin-api';\nimport { JsonObject, JsonValue, Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\n/**\n * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests\n *\n * @public\n */\nexport class MockStorageApi implements StorageApi {\n private readonly namespace: string;\n private readonly data: JsonObject;\n private readonly bucketStorageApis: Map<string, MockStorageApi>;\n\n private constructor(\n namespace: string,\n bucketStorageApis: Map<string, MockStorageApi>,\n data?: JsonObject,\n ) {\n this.namespace = namespace;\n this.bucketStorageApis = bucketStorageApis;\n this.data = { ...data };\n }\n\n static create(data?: JsonObject) {\n // Translate a nested data object structure into a flat object with keys\n // like `/a/b` with their corresponding leaf values\n const keyValues: { [key: string]: any } = {};\n function put(value: { [key: string]: any }, namespace: string) {\n for (const [key, val] of Object.entries(value)) {\n if (typeof val === 'object' && val !== null) {\n put(val, `${namespace}/${key}`);\n } else {\n const namespacedKey = `${namespace}/${key.replace(/^\\//, '')}`;\n keyValues[namespacedKey] = val;\n }\n }\n }\n put(data ?? {}, '');\n return new MockStorageApi('', new Map(), keyValues);\n }\n\n forBucket(name: string): StorageApi {\n if (!this.bucketStorageApis.has(name)) {\n this.bucketStorageApis.set(\n name,\n new MockStorageApi(\n `${this.namespace}/${name}`,\n this.bucketStorageApis,\n this.data,\n ),\n );\n }\n return this.bucketStorageApis.get(name)!;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n if (this.data.hasOwnProperty(this.getKeyName(key))) {\n const data = this.data[this.getKeyName(key)];\n return {\n key,\n presence: 'present',\n value: data as T,\n };\n }\n return {\n key,\n presence: 'absent',\n value: undefined,\n };\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n const serialized = JSON.parse(JSON.stringify(data), (_key, value) => {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n }\n return value;\n });\n this.data[this.getKeyName(key)] = serialized;\n this.notifyChanges({\n key,\n presence: 'present',\n value: serialized,\n });\n }\n\n async remove(key: string): Promise<void> {\n delete this.data[this.getKeyName(key)];\n this.notifyChanges({\n key,\n presence: 'absent',\n value: undefined,\n });\n }\n\n observe$<T extends JsonValue>(\n key: string,\n ): Observable<StorageValueSnapshot<T>> {\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges<T extends JsonValue>(message: StorageValueSnapshot<T>) {\n for (const subscription of this.subscribers) {\n subscription.next(message);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observable = new ObservableImpl<\n StorageValueSnapshot<JsonValue>\n >(subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { ApiProvider } from '../core-app-api/src/apis/system/ApiProvider.esm.js';
|
|
3
|
+
import { getMockApiFactory } from './MockWithApiFactory.esm.js';
|
|
4
|
+
|
|
5
|
+
function resolveTestApiEntries(apis) {
|
|
6
|
+
const apiMap = /* @__PURE__ */ new Map();
|
|
7
|
+
for (const entry of apis) {
|
|
8
|
+
const mockFactory = getMockApiFactory(entry);
|
|
9
|
+
if (mockFactory) {
|
|
10
|
+
apiMap.set(mockFactory.api.id, mockFactory.factory({}));
|
|
11
|
+
} else {
|
|
12
|
+
const [apiRef, impl] = entry;
|
|
13
|
+
apiMap.set(apiRef.id, impl);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
get: (ref) => apiMap.get(ref.id)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function TestApiProvider(props) {
|
|
21
|
+
return /* @__PURE__ */ jsx(
|
|
22
|
+
ApiProvider,
|
|
23
|
+
{
|
|
24
|
+
apis: resolveTestApiEntries(props.apis),
|
|
25
|
+
children: props.children
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export { TestApiProvider, resolveTestApiEntries };
|
|
31
|
+
//# sourceMappingURL=TestApiProvider.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestApiProvider.esm.js","sources":["../../src/apis/TestApiProvider.tsx"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactNode } from 'react';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { ApiProvider } from '../../../core-app-api/src/apis/system';\nimport { ApiHolder, ApiRef } from '@backstage/frontend-plugin-api';\nimport {\n getMockApiFactory,\n type MockWithApiFactory,\n} from './MockWithApiFactory';\n\n/**\n * Represents a single API implementation, either as a tuple of the reference and the implementation, or a mock with an embedded factory.\n * @public\n */\nexport type TestApiPair<TApi> =\n | readonly [ApiRef<TApi>, TApi extends infer TImpl ? Partial<TImpl> : never]\n | MockWithApiFactory<TApi>;\n\n/**\n * Represents an array of mock API implementation.\n * @public\n */\nexport type TestApiPairs<TApiPairs> = {\n [TIndex in keyof TApiPairs]: TestApiPair<TApiPairs[TIndex]>;\n};\n\n/** @internal */\nexport function resolveTestApiEntries<const TApiPairs extends any[]>(\n apis: readonly [...TestApiPairs<TApiPairs>],\n): ApiHolder {\n const apiMap = new Map<string, unknown>();\n\n for (const entry of apis) {\n const mockFactory = getMockApiFactory(entry);\n if (mockFactory) {\n apiMap.set(mockFactory.api.id, mockFactory.factory({}));\n } else {\n const [apiRef, impl] = entry as readonly [ApiRef<any>, any];\n apiMap.set(apiRef.id, impl);\n }\n }\n\n return {\n get: <T,>(ref: ApiRef<T>) => apiMap.get(ref.id) as T | undefined,\n };\n}\n\n/**\n * Properties for the {@link TestApiProvider} component.\n *\n * @public\n */\nexport type TestApiProviderProps<TApiPairs extends any[]> = {\n apis: readonly [...TestApiPairs<TApiPairs>];\n children: ReactNode;\n};\n\n/**\n * The `TestApiProvider` is a Utility API context provider for standalone rendering\n * scenarios where you're not using `renderInTestApp` or other test utilities.\n *\n * It lets you provide any number of API implementations, without necessarily\n * having to fully implement each of the APIs.\n *\n * @remarks\n *\n * For most test scenarios, prefer using the `apis` option in `renderInTestApp` or\n * `createExtensionTester` instead of wrapping components with `TestApiProvider`.\n *\n * @example\n * ```tsx\n * import { render } from '\\@testing-library/react';\n * import { myCustomApiRef } from '../apis';\n * import { TestApiProvider, mockApis } from '\\@backstage/frontend-test-utils';\n *\n * // Mock custom APIs with tuple syntax\n * const myCustomApiMock = { myMethod: jest.fn() };\n * render(\n * <TestApiProvider\n * apis={[\n * [myCustomApiRef, myCustomApiMock]\n * ]}\n * >\n * <MyComponent />\n * </TestApiProvider>\n * );\n *\n * // Use with built-in mock APIs (no tuples needed)\n * render(\n * <TestApiProvider\n * apis={[\n * mockApis.identity({ userEntityRef: 'user:default/guest' }),\n * mockApis.alert(),\n * ]}\n * >\n * <MyComponent />\n * </TestApiProvider>\n * );\n * ```\n *\n * @public\n */\nexport function TestApiProvider<const TApiPairs extends any[]>(\n props: TestApiProviderProps<TApiPairs>,\n): JSX.Element {\n return (\n <ApiProvider\n apis={resolveTestApiEntries(props.apis)}\n children={props.children}\n />\n );\n}\n"],"names":[],"mappings":";;;;AA0CO,SAAS,sBACd,IAAA,EACW;AACX,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAqB;AAExC,EAAA,KAAA,MAAW,SAAS,IAAA,EAAM;AACxB,IAAA,MAAM,WAAA,GAAc,kBAAkB,KAAK,CAAA;AAC3C,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,MAAA,CAAO,GAAA,CAAI,YAAY,GAAA,CAAI,EAAA,EAAI,YAAY,OAAA,CAAQ,EAAE,CAAC,CAAA;AAAA,IACxD,CAAA,MAAO;AACL,MAAA,MAAM,CAAC,MAAA,EAAQ,IAAI,CAAA,GAAI,KAAA;AACvB,MAAA,MAAA,CAAO,GAAA,CAAI,MAAA,CAAO,EAAA,EAAI,IAAI,CAAA;AAAA,IAC5B;AAAA,EACF;AAEA,EAAA,OAAO;AAAA,IACL,KAAK,CAAK,GAAA,KAAmB,MAAA,CAAO,GAAA,CAAI,IAAI,EAAE;AAAA,GAChD;AACF;AAyDO,SAAS,gBACd,KAAA,EACa;AACb,EAAA,uBACE,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,IAAA,EAAM,qBAAA,CAAsB,KAAA,CAAM,IAAI,CAAA;AAAA,MACtC,UAAU,KAAA,CAAM;AAAA;AAAA,GAClB;AAEJ;;;;"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { createInstance } from 'i18next';
|
|
2
|
+
import ObservableImpl from 'zen-observable';
|
|
3
|
+
import { toInternalTranslationRef } from '../../frontend-plugin-api/src/translation/TranslationRef.esm.js';
|
|
4
|
+
import { JsxInterpolator } from '../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.esm.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_LANGUAGE = "en";
|
|
7
|
+
class MockTranslationApi {
|
|
8
|
+
static create() {
|
|
9
|
+
const i18n = createInstance({
|
|
10
|
+
fallbackLng: DEFAULT_LANGUAGE,
|
|
11
|
+
supportedLngs: [DEFAULT_LANGUAGE],
|
|
12
|
+
interpolation: {
|
|
13
|
+
escapeValue: false,
|
|
14
|
+
// Used for the JsxInterpolator format hook
|
|
15
|
+
alwaysFormat: true
|
|
16
|
+
},
|
|
17
|
+
ns: [],
|
|
18
|
+
defaultNS: false,
|
|
19
|
+
fallbackNS: false,
|
|
20
|
+
// Disable resource loading on init, meaning i18n will be ready to use immediately
|
|
21
|
+
initImmediate: false
|
|
22
|
+
});
|
|
23
|
+
i18n.init();
|
|
24
|
+
if (!i18n.isInitialized) {
|
|
25
|
+
throw new Error("i18next was unexpectedly not initialized");
|
|
26
|
+
}
|
|
27
|
+
const interpolator = JsxInterpolator.fromI18n(i18n);
|
|
28
|
+
return new MockTranslationApi(i18n, interpolator);
|
|
29
|
+
}
|
|
30
|
+
#i18n;
|
|
31
|
+
#interpolator;
|
|
32
|
+
#registeredRefs = /* @__PURE__ */ new Set();
|
|
33
|
+
constructor(i18n, interpolator) {
|
|
34
|
+
this.#i18n = i18n;
|
|
35
|
+
this.#interpolator = interpolator;
|
|
36
|
+
}
|
|
37
|
+
getTranslation(translationRef) {
|
|
38
|
+
const internalRef = toInternalTranslationRef(translationRef);
|
|
39
|
+
if (!this.#registeredRefs.has(internalRef.id)) {
|
|
40
|
+
this.#registeredRefs.add(internalRef.id);
|
|
41
|
+
this.#i18n.addResourceBundle(
|
|
42
|
+
DEFAULT_LANGUAGE,
|
|
43
|
+
internalRef.id,
|
|
44
|
+
internalRef.getDefaultMessages(),
|
|
45
|
+
false,
|
|
46
|
+
// do not merge
|
|
47
|
+
true
|
|
48
|
+
// overwrite existing
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const t = this.#interpolator.wrapT(
|
|
52
|
+
this.#i18n.getFixedT(null, internalRef.id)
|
|
53
|
+
);
|
|
54
|
+
return {
|
|
55
|
+
ready: true,
|
|
56
|
+
t
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
translation$() {
|
|
60
|
+
return new ObservableImpl((_subscriber) => {
|
|
61
|
+
return () => {
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { MockTranslationApi };
|
|
68
|
+
//# sourceMappingURL=MockTranslationApi.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockTranslationApi.esm.js","sources":["../../../src/apis/TranslationApi/MockTranslationApi.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n TranslationApi,\n TranslationRef,\n TranslationSnapshot,\n} from '@backstage/core-plugin-api/alpha';\nimport { createInstance as createI18n, type i18n as I18n } from 'i18next';\nimport ObservableImpl from 'zen-observable';\n\nimport { Observable } from '@backstage/types';\n// Internal import to avoid code duplication, this will lead to duplication in build output\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalTranslationRef } from '../../../../frontend-plugin-api/src/translation/TranslationRef';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { JsxInterpolator } from '../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi';\n\nconst DEFAULT_LANGUAGE = 'en';\n\n/**\n * Mock implementation of {@link @backstage/core-plugin-api/alpha#TranslationApi}.\n *\n * @public\n */\nexport class MockTranslationApi implements TranslationApi {\n static create() {\n const i18n = createI18n({\n fallbackLng: DEFAULT_LANGUAGE,\n supportedLngs: [DEFAULT_LANGUAGE],\n interpolation: {\n escapeValue: false,\n // Used for the JsxInterpolator format hook\n alwaysFormat: true,\n },\n ns: [],\n defaultNS: false,\n fallbackNS: false,\n\n // Disable resource loading on init, meaning i18n will be ready to use immediately\n initImmediate: false,\n });\n\n i18n.init();\n if (!i18n.isInitialized) {\n throw new Error('i18next was unexpectedly not initialized');\n }\n\n const interpolator = JsxInterpolator.fromI18n(i18n);\n\n return new MockTranslationApi(i18n, interpolator);\n }\n\n readonly #i18n: I18n;\n readonly #interpolator: JsxInterpolator;\n readonly #registeredRefs = new Set<string>();\n\n private constructor(i18n: I18n, interpolator: JsxInterpolator) {\n this.#i18n = i18n;\n this.#interpolator = interpolator;\n }\n\n getTranslation<TMessages extends { [key in string]: string }>(\n translationRef: TranslationRef<string, TMessages>,\n ): TranslationSnapshot<TMessages> {\n const internalRef = toInternalTranslationRef(translationRef);\n\n if (!this.#registeredRefs.has(internalRef.id)) {\n this.#registeredRefs.add(internalRef.id);\n this.#i18n.addResourceBundle(\n DEFAULT_LANGUAGE,\n internalRef.id,\n internalRef.getDefaultMessages(),\n false, // do not merge\n true, // overwrite existing\n );\n }\n\n const t = this.#interpolator.wrapT<TMessages>(\n this.#i18n.getFixedT(null, internalRef.id),\n );\n\n return {\n ready: true,\n t,\n };\n }\n\n translation$<TMessages extends { [key in string]: string }>(): Observable<\n TranslationSnapshot<TMessages>\n > {\n // No need to implement, getTranslation will always return a ready snapshot\n return new ObservableImpl<TranslationSnapshot<TMessages>>(_subscriber => {\n return () => {};\n });\n }\n}\n"],"names":["createI18n"],"mappings":";;;;;AA+BA,MAAM,gBAAA,GAAmB,IAAA;AAOlB,MAAM,kBAAA,CAA6C;AAAA,EACxD,OAAO,MAAA,GAAS;AACd,IAAA,MAAM,OAAOA,cAAA,CAAW;AAAA,MACtB,WAAA,EAAa,gBAAA;AAAA,MACb,aAAA,EAAe,CAAC,gBAAgB,CAAA;AAAA,MAChC,aAAA,EAAe;AAAA,QACb,WAAA,EAAa,KAAA;AAAA;AAAA,QAEb,YAAA,EAAc;AAAA,OAChB;AAAA,MACA,IAAI,EAAC;AAAA,MACL,SAAA,EAAW,KAAA;AAAA,MACX,UAAA,EAAY,KAAA;AAAA;AAAA,MAGZ,aAAA,EAAe;AAAA,KAChB,CAAA;AAED,IAAA,IAAA,CAAK,IAAA,EAAK;AACV,IAAA,IAAI,CAAC,KAAK,aAAA,EAAe;AACvB,MAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,IAC5D;AAEA,IAAA,MAAM,YAAA,GAAe,eAAA,CAAgB,QAAA,CAAS,IAAI,CAAA;AAElD,IAAA,OAAO,IAAI,kBAAA,CAAmB,IAAA,EAAM,YAAY,CAAA;AAAA,EAClD;AAAA,EAES,KAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA,uBAAsB,GAAA,EAAY;AAAA,EAEnC,WAAA,CAAY,MAAY,YAAA,EAA+B;AAC7D,IAAA,IAAA,CAAK,KAAA,GAAQ,IAAA;AACb,IAAA,IAAA,CAAK,aAAA,GAAgB,YAAA;AAAA,EACvB;AAAA,EAEA,eACE,cAAA,EACgC;AAChC,IAAA,MAAM,WAAA,GAAc,yBAAyB,cAAc,CAAA;AAE3D,IAAA,IAAI,CAAC,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,EAAE,CAAA,EAAG;AAC7C,MAAA,IAAA,CAAK,eAAA,CAAgB,GAAA,CAAI,WAAA,CAAY,EAAE,CAAA;AACvC,MAAA,IAAA,CAAK,KAAA,CAAM,iBAAA;AAAA,QACT,gBAAA;AAAA,QACA,WAAA,CAAY,EAAA;AAAA,QACZ,YAAY,kBAAA,EAAmB;AAAA,QAC/B,KAAA;AAAA;AAAA,QACA;AAAA;AAAA,OACF;AAAA,IACF;AAEA,IAAA,MAAM,CAAA,GAAI,KAAK,aAAA,CAAc,KAAA;AAAA,MAC3B,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,IAAA,EAAM,YAAY,EAAE;AAAA,KAC3C;AAEA,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,IAAA;AAAA,MACP;AAAA,KACF;AAAA,EACF;AAAA,EAEA,YAAA,GAEE;AAEA,IAAA,OAAO,IAAI,eAA+C,CAAA,WAAA,KAAe;AACvE,MAAA,OAAO,MAAM;AAAA,MAAC,CAAA;AAAA,IAChB,CAAC,CAAA;AAAA,EACH;AACF;;;;"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mockApiFactorySymbol } from './MockWithApiFactory.esm.js';
|
|
2
|
+
|
|
3
|
+
function createApiMock(apiRef, mockFactory) {
|
|
4
|
+
return (partialImpl) => {
|
|
5
|
+
const mock = mockFactory();
|
|
6
|
+
if (partialImpl) {
|
|
7
|
+
for (const [key, impl] of Object.entries(partialImpl)) {
|
|
8
|
+
if (typeof impl === "function") {
|
|
9
|
+
mock[key].mockImplementation(impl);
|
|
10
|
+
} else {
|
|
11
|
+
mock[key] = impl;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
mock[mockApiFactorySymbol] = {
|
|
16
|
+
api: apiRef,
|
|
17
|
+
deps: {},
|
|
18
|
+
factory: () => mock
|
|
19
|
+
};
|
|
20
|
+
return mock;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { createApiMock };
|
|
25
|
+
//# sourceMappingURL=createApiMock.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createApiMock.esm.js","sources":["../../src/apis/createApiMock.ts"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ApiFactory, type ApiRef } from '@backstage/frontend-plugin-api';\nimport { mockApiFactorySymbol } from './MockWithApiFactory';\n\n/**\n * Represents a mocked version of an API, where you automatically have access to\n * the mocked versions of all of its methods along with a factory that returns\n * that same mock.\n *\n * @public\n */\nexport type ApiMock<TApi> = {\n [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;\n} & {\n [Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return\n ? TApi[Key] & jest.MockInstance<Return, Args>\n : TApi[Key];\n};\n\n/**\n * Creates a standardized Backstage Utility API mockfactory function for\n * producing mock API instances.\n *\n * @remarks\n *\n * Each method in the mock factory is a `jest.fn()`, and you can optionally pass\n * partial implementations when calling the returned function. No type\n * parameters should be provided to this function, they will be inferred from\n * the provided API reference.\n *\n * @public\n * @example\n * ```ts\n * import { createApiMock } from '@backstage/frontend-test-utils';\n * import { myApiRef } from '../apis';\n *\n * // Set up the mock factory\n * const mock = createApiMock(myApiRef, () => ({\n * greet: jest.fn(),\n * }));\n *\n * // Create a mock with default behavior\n * const api = mock();\n *\n * // Or with a partial implementation\n * const api = mock({ greet: async () => 'Hello!' });\n * expect(api.greet).toHaveBeenCalledTimes(1);\n * ```\n */\nexport function createApiMock<TApi>(\n apiRef: ApiRef<TApi>,\n mockFactory: () => jest.Mocked<TApi>,\n): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {\n return partialImpl => {\n const mock = mockFactory();\n if (partialImpl) {\n for (const [key, impl] of Object.entries(partialImpl)) {\n if (typeof impl === 'function') {\n (mock as any)[key].mockImplementation(impl);\n } else {\n (mock as any)[key] = impl;\n }\n }\n }\n (mock as any)[mockApiFactorySymbol] = {\n api: apiRef,\n deps: {},\n factory: () => mock,\n };\n return mock as unknown as ApiMock<TApi>;\n };\n}\n"],"names":[],"mappings":";;AAgEO,SAAS,aAAA,CACd,QACA,WAAA,EACgD;AAChD,EAAA,OAAO,CAAA,WAAA,KAAe;AACpB,IAAA,MAAM,OAAO,WAAA,EAAY;AACzB,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,KAAA,MAAW,CAAC,GAAA,EAAK,IAAI,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACrD,QAAA,IAAI,OAAO,SAAS,UAAA,EAAY;AAC9B,UAAC,IAAA,CAAa,GAAG,CAAA,CAAE,kBAAA,CAAmB,IAAI,CAAA;AAAA,QAC5C,CAAA,MAAO;AACL,UAAC,IAAA,CAAa,GAAG,CAAA,GAAI,IAAA;AAAA,QACvB;AAAA,MACF;AAAA,IACF;AACA,IAAC,IAAA,CAAa,oBAAoB,CAAA,GAAI;AAAA,MACpC,GAAA,EAAK,MAAA;AAAA,MACL,MAAM,EAAC;AAAA,MACP,SAAS,MAAM;AAAA,KACjB;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AACF;;;;"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { alertApiRef, featureFlagsApiRef, analyticsApiRef, translationApiRef, configApiRef, discoveryApiRef, identityApiRef, storageApiRef, errorApiRef, fetchApiRef } from '@backstage/frontend-plugin-api';
|
|
2
|
+
import { permissionApiRef } from '@backstage/plugin-permission-react';
|
|
3
|
+
import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
|
4
|
+
import { MockAlertApi } from './AlertApi/MockAlertApi.esm.js';
|
|
5
|
+
import { MockFeatureFlagsApi } from './FeatureFlagsApi/MockFeatureFlagsApi.esm.js';
|
|
6
|
+
import { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi.esm.js';
|
|
7
|
+
import { MockConfigApi } from './ConfigApi/MockConfigApi.esm.js';
|
|
8
|
+
import { MockErrorApi } from './ErrorApi/MockErrorApi.esm.js';
|
|
9
|
+
import { MockFetchApi } from './FetchApi/MockFetchApi.esm.js';
|
|
10
|
+
import { MockStorageApi } from './StorageApi/MockStorageApi.esm.js';
|
|
11
|
+
import { MockPermissionApi } from './PermissionApi/MockPermissionApi.esm.js';
|
|
12
|
+
import { MockTranslationApi } from './TranslationApi/MockTranslationApi.esm.js';
|
|
13
|
+
import { mockWithApiFactory } from './MockWithApiFactory.esm.js';
|
|
14
|
+
import { createApiMock } from './createApiMock.esm.js';
|
|
15
|
+
|
|
16
|
+
var mockApis;
|
|
17
|
+
((mockApis2) => {
|
|
18
|
+
function alert() {
|
|
19
|
+
const instance = new MockAlertApi();
|
|
20
|
+
return mockWithApiFactory(
|
|
21
|
+
alertApiRef,
|
|
22
|
+
instance
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
mockApis2.alert = alert;
|
|
26
|
+
((alert2) => {
|
|
27
|
+
alert2.mock = createApiMock(alertApiRef, () => ({
|
|
28
|
+
post: jest.fn(),
|
|
29
|
+
alert$: jest.fn()
|
|
30
|
+
}));
|
|
31
|
+
})(alert = mockApis2.alert || (mockApis2.alert = {}));
|
|
32
|
+
function featureFlags(options) {
|
|
33
|
+
const instance = new MockFeatureFlagsApi(options);
|
|
34
|
+
return mockWithApiFactory(
|
|
35
|
+
featureFlagsApiRef,
|
|
36
|
+
instance
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
mockApis2.featureFlags = featureFlags;
|
|
40
|
+
((featureFlags2) => {
|
|
41
|
+
featureFlags2.mock = createApiMock(featureFlagsApiRef, () => ({
|
|
42
|
+
registerFlag: jest.fn(),
|
|
43
|
+
getRegisteredFlags: jest.fn(),
|
|
44
|
+
isActive: jest.fn(),
|
|
45
|
+
save: jest.fn()
|
|
46
|
+
}));
|
|
47
|
+
})(featureFlags = mockApis2.featureFlags || (mockApis2.featureFlags = {}));
|
|
48
|
+
function analytics() {
|
|
49
|
+
const instance = new MockAnalyticsApi();
|
|
50
|
+
return mockWithApiFactory(analyticsApiRef, instance);
|
|
51
|
+
}
|
|
52
|
+
mockApis2.analytics = analytics;
|
|
53
|
+
((analytics2) => {
|
|
54
|
+
analytics2.mock = createApiMock(analyticsApiRef, () => ({
|
|
55
|
+
captureEvent: jest.fn()
|
|
56
|
+
}));
|
|
57
|
+
})(analytics = mockApis2.analytics || (mockApis2.analytics = {}));
|
|
58
|
+
function translation() {
|
|
59
|
+
const instance = MockTranslationApi.create();
|
|
60
|
+
return mockWithApiFactory(
|
|
61
|
+
translationApiRef,
|
|
62
|
+
instance
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
mockApis2.translation = translation;
|
|
66
|
+
((translation2) => {
|
|
67
|
+
translation2.mock = createApiMock(translationApiRef, () => ({
|
|
68
|
+
getTranslation: jest.fn(),
|
|
69
|
+
translation$: jest.fn()
|
|
70
|
+
}));
|
|
71
|
+
})(translation = mockApis2.translation || (mockApis2.translation = {}));
|
|
72
|
+
function config(options) {
|
|
73
|
+
const instance = new MockConfigApi({ data: options?.data ?? {} });
|
|
74
|
+
return mockWithApiFactory(configApiRef, instance);
|
|
75
|
+
}
|
|
76
|
+
mockApis2.config = config;
|
|
77
|
+
((config2) => {
|
|
78
|
+
config2.mock = createApiMock(configApiRef, () => ({
|
|
79
|
+
has: jest.fn(),
|
|
80
|
+
keys: jest.fn(),
|
|
81
|
+
get: jest.fn(),
|
|
82
|
+
getOptional: jest.fn(),
|
|
83
|
+
getConfig: jest.fn(),
|
|
84
|
+
getOptionalConfig: jest.fn(),
|
|
85
|
+
getConfigArray: jest.fn(),
|
|
86
|
+
getOptionalConfigArray: jest.fn(),
|
|
87
|
+
getNumber: jest.fn(),
|
|
88
|
+
getOptionalNumber: jest.fn(),
|
|
89
|
+
getBoolean: jest.fn(),
|
|
90
|
+
getOptionalBoolean: jest.fn(),
|
|
91
|
+
getString: jest.fn(),
|
|
92
|
+
getOptionalString: jest.fn(),
|
|
93
|
+
getStringArray: jest.fn(),
|
|
94
|
+
getOptionalStringArray: jest.fn()
|
|
95
|
+
}));
|
|
96
|
+
})(config = mockApis2.config || (mockApis2.config = {}));
|
|
97
|
+
function discovery(options) {
|
|
98
|
+
const baseUrl = options?.baseUrl ?? "http://example.com";
|
|
99
|
+
const instance = {
|
|
100
|
+
async getBaseUrl(pluginId) {
|
|
101
|
+
return `${baseUrl}/api/${pluginId}`;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return mockWithApiFactory(discoveryApiRef, instance);
|
|
105
|
+
}
|
|
106
|
+
mockApis2.discovery = discovery;
|
|
107
|
+
((discovery2) => {
|
|
108
|
+
discovery2.mock = createApiMock(discoveryApiRef, () => ({
|
|
109
|
+
getBaseUrl: jest.fn()
|
|
110
|
+
}));
|
|
111
|
+
})(discovery = mockApis2.discovery || (mockApis2.discovery = {}));
|
|
112
|
+
function identity(options) {
|
|
113
|
+
const {
|
|
114
|
+
userEntityRef = "user:default/test",
|
|
115
|
+
ownershipEntityRefs = ["user:default/test"],
|
|
116
|
+
token,
|
|
117
|
+
email,
|
|
118
|
+
displayName,
|
|
119
|
+
picture
|
|
120
|
+
} = options ?? {};
|
|
121
|
+
const instance = {
|
|
122
|
+
async getBackstageIdentity() {
|
|
123
|
+
return { type: "user", ownershipEntityRefs, userEntityRef };
|
|
124
|
+
},
|
|
125
|
+
async getCredentials() {
|
|
126
|
+
return { token };
|
|
127
|
+
},
|
|
128
|
+
async getProfileInfo() {
|
|
129
|
+
return { email, displayName, picture };
|
|
130
|
+
},
|
|
131
|
+
async signOut() {
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return mockWithApiFactory(identityApiRef, instance);
|
|
135
|
+
}
|
|
136
|
+
mockApis2.identity = identity;
|
|
137
|
+
((identity2) => {
|
|
138
|
+
identity2.mock = createApiMock(identityApiRef, () => ({
|
|
139
|
+
getBackstageIdentity: jest.fn(),
|
|
140
|
+
getCredentials: jest.fn(),
|
|
141
|
+
getProfileInfo: jest.fn(),
|
|
142
|
+
signOut: jest.fn()
|
|
143
|
+
}));
|
|
144
|
+
})(identity = mockApis2.identity || (mockApis2.identity = {}));
|
|
145
|
+
function permission(options) {
|
|
146
|
+
const authorizeInput = options?.authorize;
|
|
147
|
+
const handler = typeof authorizeInput === "function" ? authorizeInput : () => authorizeInput ?? AuthorizeResult.ALLOW;
|
|
148
|
+
const instance = new MockPermissionApi(handler);
|
|
149
|
+
return mockWithApiFactory(permissionApiRef, instance);
|
|
150
|
+
}
|
|
151
|
+
mockApis2.permission = permission;
|
|
152
|
+
((permission2) => {
|
|
153
|
+
permission2.mock = createApiMock(permissionApiRef, () => ({
|
|
154
|
+
authorize: jest.fn()
|
|
155
|
+
}));
|
|
156
|
+
})(permission = mockApis2.permission || (mockApis2.permission = {}));
|
|
157
|
+
function storage(options) {
|
|
158
|
+
const instance = MockStorageApi.create(options?.data);
|
|
159
|
+
return mockWithApiFactory(storageApiRef, instance);
|
|
160
|
+
}
|
|
161
|
+
mockApis2.storage = storage;
|
|
162
|
+
((storage2) => {
|
|
163
|
+
storage2.mock = createApiMock(storageApiRef, () => ({
|
|
164
|
+
forBucket: jest.fn(),
|
|
165
|
+
snapshot: jest.fn(),
|
|
166
|
+
set: jest.fn(),
|
|
167
|
+
remove: jest.fn(),
|
|
168
|
+
observe$: jest.fn()
|
|
169
|
+
}));
|
|
170
|
+
})(storage = mockApis2.storage || (mockApis2.storage = {}));
|
|
171
|
+
function error(options) {
|
|
172
|
+
const instance = new MockErrorApi(options);
|
|
173
|
+
return mockWithApiFactory(errorApiRef, instance);
|
|
174
|
+
}
|
|
175
|
+
mockApis2.error = error;
|
|
176
|
+
((error2) => {
|
|
177
|
+
error2.mock = createApiMock(errorApiRef, () => ({
|
|
178
|
+
post: jest.fn(),
|
|
179
|
+
error$: jest.fn()
|
|
180
|
+
}));
|
|
181
|
+
})(error = mockApis2.error || (mockApis2.error = {}));
|
|
182
|
+
function fetch(options) {
|
|
183
|
+
const instance = new MockFetchApi(options);
|
|
184
|
+
return mockWithApiFactory(fetchApiRef, instance);
|
|
185
|
+
}
|
|
186
|
+
mockApis2.fetch = fetch;
|
|
187
|
+
((fetch2) => {
|
|
188
|
+
fetch2.mock = createApiMock(fetchApiRef, () => ({
|
|
189
|
+
fetch: jest.fn()
|
|
190
|
+
}));
|
|
191
|
+
})(fetch = mockApis2.fetch || (mockApis2.fetch = {}));
|
|
192
|
+
})(mockApis || (mockApis = {}));
|
|
193
|
+
|
|
194
|
+
export { mockApis };
|
|
195
|
+
//# sourceMappingURL=mockApis.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mockApis.esm.js","sources":["../../src/apis/mockApis.ts"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n alertApiRef,\n analyticsApiRef,\n configApiRef,\n discoveryApiRef,\n errorApiRef,\n fetchApiRef,\n featureFlagsApiRef,\n identityApiRef,\n storageApiRef,\n translationApiRef,\n type AnalyticsApi,\n type ConfigApi,\n type DiscoveryApi,\n type ErrorApi,\n type FetchApi,\n type IdentityApi,\n type StorageApi,\n type TranslationApi,\n} from '@backstage/frontend-plugin-api';\nimport {\n permissionApiRef,\n type PermissionApi,\n} from '@backstage/plugin-permission-react';\nimport { JsonObject } from '@backstage/types';\nimport {\n AuthorizeResult,\n EvaluatePermissionRequest,\n} from '@backstage/plugin-permission-common';\nimport { MockAlertApi } from './AlertApi';\nimport {\n MockFeatureFlagsApi,\n MockFeatureFlagsApiOptions,\n} from './FeatureFlagsApi';\nimport { MockAnalyticsApi } from './AnalyticsApi';\nimport { MockConfigApi } from './ConfigApi';\nimport { MockErrorApi, MockErrorApiOptions } from './ErrorApi';\nimport { MockFetchApi, MockFetchApiOptions } from './FetchApi';\nimport { MockStorageApi } from './StorageApi';\nimport { MockPermissionApi } from './PermissionApi';\nimport { MockTranslationApi } from './TranslationApi';\nimport {\n mockWithApiFactory,\n type MockWithApiFactory,\n} from './MockWithApiFactory';\nimport { createApiMock } from './createApiMock';\n\n/**\n * Mock implementations of the core utility APIs, to be used in tests.\n *\n * @public\n * @remarks\n *\n * There are some variations among the APIs depending on what needs tests\n * might have, but overall there are two main usage patterns:\n *\n * 1: Creating an actual fake API instance, often with a simplified version\n * of functionality, by calling the mock API itself as a function.\n *\n * ```ts\n * // The function often accepts parameters that control its behavior\n * const foo = mockApis.foo();\n * ```\n *\n * 2: Creating a mock API, where all methods are replaced with jest mocks, by\n * calling the API's `mock` function.\n *\n * ```ts\n * // You can optionally supply a subset of its methods to implement\n * const foo = mockApis.foo.mock({\n * someMethod: () => 'mocked result',\n * });\n * // After exercising your test, you can make assertions on the mock:\n * expect(foo.someMethod).toHaveBeenCalledTimes(2);\n * expect(foo.otherMethod).toHaveBeenCalledWith(testData);\n * ```\n */\nexport namespace mockApis {\n /**\n * Fake implementation of {@link @backstage/frontend-plugin-api#AlertApi}.\n *\n * @public\n * @example\n *\n * ```tsx\n * const alertApi = mockApis.alert();\n * alertApi.post({ message: 'Test alert' });\n * expect(alertApi.getAlerts()).toHaveLength(1);\n * ```\n */\n export function alert(): MockWithApiFactory<MockAlertApi> {\n const instance = new MockAlertApi();\n return mockWithApiFactory(\n alertApiRef,\n instance,\n ) as MockWithApiFactory<MockAlertApi>;\n }\n /**\n * Mock helpers for {@link @backstage/frontend-plugin-api#AlertApi}.\n *\n * @see {@link @backstage/frontend-plugin-api#mockApis.alert}\n * @public\n */\n export namespace alert {\n /**\n * Creates a mock implementation of\n * {@link @backstage/frontend-plugin-api#AlertApi}. All methods are\n * replaced with jest mock functions, and you can optionally pass in a\n * subset of methods with an explicit implementation.\n *\n * @public\n */\n export const mock = createApiMock(alertApiRef, () => ({\n post: jest.fn(),\n alert$: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/frontend-plugin-api#FeatureFlagsApi}.\n *\n * @public\n * @example\n *\n * ```tsx\n * const featureFlagsApi = mockApis.featureFlags({\n * initialStates: { 'my-feature': FeatureFlagState.Active },\n * });\n * expect(featureFlagsApi.isActive('my-feature')).toBe(true);\n * ```\n */\n export function featureFlags(\n options?: MockFeatureFlagsApiOptions,\n ): MockWithApiFactory<MockFeatureFlagsApi> {\n const instance = new MockFeatureFlagsApi(options);\n return mockWithApiFactory(\n featureFlagsApiRef,\n instance,\n ) as MockWithApiFactory<MockFeatureFlagsApi>;\n }\n /**\n * Mock helpers for {@link @backstage/frontend-plugin-api#FeatureFlagsApi}.\n *\n * @see {@link @backstage/frontend-plugin-api#mockApis.featureFlags}\n * @public\n */\n export namespace featureFlags {\n /**\n * Creates a mock implementation of\n * {@link @backstage/frontend-plugin-api#FeatureFlagsApi}. All methods are\n * replaced with jest mock functions, and you can optionally pass in a\n * subset of methods with an explicit implementation.\n *\n * @public\n */\n export const mock = createApiMock(featureFlagsApiRef, () => ({\n registerFlag: jest.fn(),\n getRegisteredFlags: jest.fn(),\n isActive: jest.fn(),\n save: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#AnalyticsApi}.\n *\n * @public\n */\n export function analytics(): MockAnalyticsApi &\n MockWithApiFactory<AnalyticsApi> {\n const instance = new MockAnalyticsApi();\n return mockWithApiFactory(analyticsApiRef, instance) as MockAnalyticsApi &\n MockWithApiFactory<AnalyticsApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#AnalyticsApi}.\n *\n * @public\n */\n export namespace analytics {\n export const mock = createApiMock(analyticsApiRef, () => ({\n captureEvent: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api/alpha#TranslationApi}.\n * By default returns the default translation.\n *\n * @public\n */\n export function translation(): MockTranslationApi &\n MockWithApiFactory<TranslationApi> {\n const instance = MockTranslationApi.create();\n return mockWithApiFactory(\n translationApiRef,\n instance,\n ) as MockTranslationApi & MockWithApiFactory<TranslationApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api/alpha#TranslationApi}.\n *\n * @see {@link @backstage/frontend-plugin-api#mockApis.translation}\n * @public\n */\n export namespace translation {\n /**\n * Creates a mock of {@link @backstage/core-plugin-api/alpha#TranslationApi}.\n *\n * @public\n */\n export const mock = createApiMock(translationApiRef, () => ({\n getTranslation: jest.fn(),\n translation$: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#ConfigApi}.\n *\n * @public\n */\n export function config(options?: {\n data?: JsonObject;\n }): MockConfigApi & MockWithApiFactory<ConfigApi> {\n const instance = new MockConfigApi({ data: options?.data ?? {} });\n return mockWithApiFactory(configApiRef, instance) as MockConfigApi &\n MockWithApiFactory<ConfigApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#ConfigApi}.\n *\n * @public\n */\n export namespace config {\n export const mock = createApiMock(configApiRef, () => ({\n has: jest.fn(),\n keys: jest.fn(),\n get: jest.fn(),\n getOptional: jest.fn(),\n getConfig: jest.fn(),\n getOptionalConfig: jest.fn(),\n getConfigArray: jest.fn(),\n getOptionalConfigArray: jest.fn(),\n getNumber: jest.fn(),\n getOptionalNumber: jest.fn(),\n getBoolean: jest.fn(),\n getOptionalBoolean: jest.fn(),\n getString: jest.fn(),\n getOptionalString: jest.fn(),\n getStringArray: jest.fn(),\n getOptionalStringArray: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#DiscoveryApi}.\n *\n * @public\n */\n export function discovery(options?: {\n baseUrl?: string;\n }): DiscoveryApi & MockWithApiFactory<DiscoveryApi> {\n const baseUrl = options?.baseUrl ?? 'http://example.com';\n const instance: DiscoveryApi = {\n async getBaseUrl(pluginId: string) {\n return `${baseUrl}/api/${pluginId}`;\n },\n };\n return mockWithApiFactory(discoveryApiRef, instance) as DiscoveryApi &\n MockWithApiFactory<DiscoveryApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#DiscoveryApi}.\n *\n * @public\n */\n export namespace discovery {\n export const mock = createApiMock(discoveryApiRef, () => ({\n getBaseUrl: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#IdentityApi}.\n *\n * @public\n */\n export function identity(options?: {\n userEntityRef?: string;\n ownershipEntityRefs?: string[];\n token?: string;\n email?: string;\n displayName?: string;\n picture?: string;\n }): MockWithApiFactory<IdentityApi> {\n const {\n userEntityRef = 'user:default/test',\n ownershipEntityRefs = ['user:default/test'],\n token,\n email,\n displayName,\n picture,\n } = options ?? {};\n const instance: IdentityApi = {\n async getBackstageIdentity() {\n return { type: 'user', ownershipEntityRefs, userEntityRef };\n },\n async getCredentials() {\n return { token };\n },\n async getProfileInfo() {\n return { email, displayName, picture };\n },\n async signOut() {},\n };\n return mockWithApiFactory(identityApiRef, instance) as IdentityApi &\n MockWithApiFactory<IdentityApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#IdentityApi}.\n *\n * @public\n */\n export namespace identity {\n export const mock = createApiMock(identityApiRef, () => ({\n getBackstageIdentity: jest.fn(),\n getCredentials: jest.fn(),\n getProfileInfo: jest.fn(),\n signOut: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/plugin-permission-react#PermissionApi}.\n *\n * @public\n */\n export function permission(options?: {\n authorize?:\n | AuthorizeResult.ALLOW\n | AuthorizeResult.DENY\n | ((\n request: EvaluatePermissionRequest,\n ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY);\n }): MockPermissionApi & MockWithApiFactory<PermissionApi> {\n const authorizeInput = options?.authorize;\n const handler =\n typeof authorizeInput === 'function'\n ? authorizeInput\n : () => authorizeInput ?? AuthorizeResult.ALLOW;\n const instance = new MockPermissionApi(handler);\n return mockWithApiFactory(permissionApiRef, instance) as MockPermissionApi &\n MockWithApiFactory<PermissionApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/plugin-permission-react#PermissionApi}.\n *\n * @public\n */\n export namespace permission {\n export const mock = createApiMock(permissionApiRef, () => ({\n authorize: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#StorageApi}.\n *\n * @public\n */\n export function storage(options?: {\n data?: JsonObject;\n }): MockStorageApi & MockWithApiFactory<StorageApi> {\n const instance = MockStorageApi.create(options?.data);\n return mockWithApiFactory(storageApiRef, instance) as MockStorageApi &\n MockWithApiFactory<StorageApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#StorageApi}.\n *\n * @public\n */\n export namespace storage {\n export const mock = createApiMock(storageApiRef, () => ({\n forBucket: jest.fn(),\n snapshot: jest.fn(),\n set: jest.fn(),\n remove: jest.fn(),\n observe$: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#ErrorApi}.\n *\n * @public\n */\n export function error(\n options?: MockErrorApiOptions,\n ): MockErrorApi & MockWithApiFactory<ErrorApi> {\n const instance = new MockErrorApi(options);\n return mockWithApiFactory(errorApiRef, instance) as MockErrorApi &\n MockWithApiFactory<ErrorApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#ErrorApi}.\n *\n * @public\n */\n export namespace error {\n export const mock = createApiMock(errorApiRef, () => ({\n post: jest.fn(),\n error$: jest.fn(),\n }));\n }\n\n /**\n * Fake implementation of {@link @backstage/core-plugin-api#FetchApi}.\n *\n * @public\n */\n export function fetch(\n options?: MockFetchApiOptions,\n ): MockFetchApi & MockWithApiFactory<FetchApi> {\n const instance = new MockFetchApi(options);\n return mockWithApiFactory(fetchApiRef, instance) as MockFetchApi &\n MockWithApiFactory<FetchApi>;\n }\n\n /**\n * Mock helpers for {@link @backstage/core-plugin-api#FetchApi}.\n *\n * @public\n */\n export namespace fetch {\n export const mock = createApiMock(fetchApiRef, () => ({\n fetch: jest.fn(),\n }));\n }\n}\n"],"names":["mockApis","alert","featureFlags","analytics","translation","config","discovery","identity","permission","storage","error","fetch"],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { resolveAppNodeSpecs } from '../frontend-app-api/src/tree/resolveAppNode
|
|
|
6
6
|
import { instantiateAppNodeTree } from '../frontend-app-api/src/tree/instantiateAppNodeTree.esm.js';
|
|
7
7
|
import { readAppExtensionsConfig } from '../frontend-app-api/src/tree/readAppExtensionsConfig.esm.js';
|
|
8
8
|
import { createErrorCollector } from '../frontend-app-api/src/wiring/createErrorCollector.esm.js';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveTestApiEntries } from '../apis/TestApiProvider.esm.js';
|
|
10
10
|
import { OpaqueExtensionDefinition } from '../frontend-internal/src/wiring/InternalExtensionDefinition.esm.js';
|
|
11
11
|
|
|
12
12
|
class ExtensionQuery {
|
|
@@ -33,12 +33,16 @@ class ExtensionQuery {
|
|
|
33
33
|
class ExtensionTester {
|
|
34
34
|
/** @internal */
|
|
35
35
|
static forSubject(subject, options) {
|
|
36
|
-
const tester = new ExtensionTester();
|
|
36
|
+
const tester = new ExtensionTester(options?.apis);
|
|
37
37
|
tester.add(subject, options);
|
|
38
38
|
return tester;
|
|
39
39
|
}
|
|
40
40
|
#tree;
|
|
41
|
+
#apis;
|
|
41
42
|
#extensions = new Array();
|
|
43
|
+
constructor(apis) {
|
|
44
|
+
this.#apis = apis;
|
|
45
|
+
}
|
|
42
46
|
add(extension, options) {
|
|
43
47
|
if (this.#tree) {
|
|
44
48
|
throw new Error(
|
|
@@ -96,6 +100,40 @@ class ExtensionTester {
|
|
|
96
100
|
}
|
|
97
101
|
return element;
|
|
98
102
|
}
|
|
103
|
+
/**
|
|
104
|
+
* Returns a snapshot of the extension tree structure for testing and debugging.
|
|
105
|
+
* Convenient to use with Jest's inline snapshot testing.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```tsx
|
|
109
|
+
* const tester = createExtensionTester(myExtension);
|
|
110
|
+
* expect(tester.snapshot()).toMatchInlineSnapshot();
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
snapshot() {
|
|
114
|
+
const tree = this.#resolveTree();
|
|
115
|
+
const buildNode = (node) => {
|
|
116
|
+
const outputs = node.instance ? Array.from(node.instance.getDataRefs()).map((ref) => ref.id).sort() : [];
|
|
117
|
+
const children = {};
|
|
118
|
+
for (const [inputName, attachedNodes] of node.edges.attachments) {
|
|
119
|
+
children[inputName] = attachedNodes.map((n) => buildNode(n)).sort((a, b) => a.id.localeCompare(b.id));
|
|
120
|
+
}
|
|
121
|
+
const result = {
|
|
122
|
+
id: node.spec.id
|
|
123
|
+
};
|
|
124
|
+
if (outputs.length > 0) {
|
|
125
|
+
result.outputs = outputs;
|
|
126
|
+
}
|
|
127
|
+
if (Object.keys(children).length > 0) {
|
|
128
|
+
result.children = children;
|
|
129
|
+
}
|
|
130
|
+
if (node.spec.disabled) {
|
|
131
|
+
result.disabled = true;
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
};
|
|
135
|
+
return buildNode(tree.root);
|
|
136
|
+
}
|
|
99
137
|
#resolveTree() {
|
|
100
138
|
if (this.#tree) {
|
|
101
139
|
return this.#tree;
|
|
@@ -117,7 +155,8 @@ class ExtensionTester {
|
|
|
117
155
|
}),
|
|
118
156
|
collector
|
|
119
157
|
);
|
|
120
|
-
|
|
158
|
+
const apiHolder = resolveTestApiEntries(this.#apis ?? []);
|
|
159
|
+
instantiateAppNodeTree(tree.root, apiHolder, collector);
|
|
121
160
|
const errors = collector.collectErrors();
|
|
122
161
|
if (errors) {
|
|
123
162
|
throw new Error(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createExtensionTester.esm.js","sources":["../../src/app/createExtensionTester.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppNode,\n AppTree,\n Extension,\n ExtensionDataRef,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n coreExtensionData,\n} from '@backstage/frontend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonArray, JsonObject, JsonValue } from '@backstage/types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { createErrorCollector } from '../../../frontend-app-api/src/wiring/createErrorCollector';\nimport { TestApiRegistry } from '@backstage/test-utils';\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\n\n/** @public */\nexport class ExtensionQuery<UOutput extends ExtensionDataRef> {\n #node: AppNode;\n\n constructor(node: AppNode) {\n this.#node = node;\n }\n\n get node() {\n return this.#node;\n }\n\n get instance() {\n const instance = this.#node.instance;\n if (!instance) {\n throw new Error(\n `Unable to access the instance of extension with ID '${\n this.#node.spec.id\n }'`,\n );\n }\n return instance;\n }\n\n get<TId extends UOutput['id']>(\n ref: ExtensionDataRef<any, TId, any>,\n ): UOutput extends ExtensionDataRef<infer IData, TId, infer IConfig>\n ? IConfig['optional'] extends true\n ? IData | undefined\n : IData\n : never {\n return this.instance.getData(ref);\n }\n}\n\n/** @public */\nexport class ExtensionTester<UOutput extends ExtensionDataRef> {\n /** @internal */\n static forSubject<T extends ExtensionDefinitionParameters>(\n subject: ExtensionDefinition<T>,\n options?: { config?: T['configInput'] },\n ): ExtensionTester<NonNullable<T['output']>> {\n const tester = new ExtensionTester();\n tester.add(subject, options as T['configInput'] & {});\n return tester;\n }\n\n #tree?: AppTree;\n\n readonly #extensions = new Array<{\n id: string;\n extension: Extension<any>;\n definition: ExtensionDefinition;\n config?: JsonValue;\n }>();\n\n add<T extends ExtensionDefinitionParameters>(\n extension: ExtensionDefinition<T>,\n options?: { config?: T['configInput'] },\n ): ExtensionTester<UOutput> {\n if (this.#tree) {\n throw new Error(\n 'Cannot add more extensions accessing the extension tree',\n );\n }\n\n const { name, namespace } = OpaqueExtensionDefinition.toInternal(extension);\n\n const definition = {\n ...extension,\n // setting name \"test\" as fallback\n name: !namespace && !name ? 'test' : name,\n };\n\n const resolvedExtension = resolveExtensionDefinition(definition);\n\n this.#extensions.push({\n id: resolvedExtension.id,\n extension: resolvedExtension,\n definition,\n config: options?.config as JsonValue,\n });\n\n return this;\n }\n\n get<TId extends UOutput['id']>(\n ref: ExtensionDataRef<any, TId, any>,\n ): UOutput extends ExtensionDataRef<infer IData, TId, infer IConfig>\n ? IConfig['optional'] extends true\n ? IData | undefined\n : IData\n : never {\n const tree = this.#resolveTree();\n\n return new ExtensionQuery(tree.root).get(ref);\n }\n\n query<T extends ExtensionDefinitionParameters>(\n extension: ExtensionDefinition<T>,\n ): ExtensionQuery<NonNullable<T['output']>> {\n const tree = this.#resolveTree();\n\n // Same fallback logic as in .add\n const { name, namespace } = OpaqueExtensionDefinition.toInternal(extension);\n const definition = {\n ...extension,\n name: !namespace && !name ? 'test' : name,\n };\n const actualId = resolveExtensionDefinition(definition).id;\n\n const node = tree.nodes.get(actualId);\n\n if (!node) {\n throw new Error(\n `Extension with ID '${actualId}' not found, please make sure it's added to the tester.`,\n );\n } else if (!node.instance) {\n throw new Error(\n `Extension with ID '${actualId}' has not been instantiated, because it is not part of the test subject's extension tree.`,\n );\n }\n return new ExtensionQuery(node);\n }\n\n reactElement(): JSX.Element {\n const tree = this.#resolveTree();\n\n const element = new ExtensionQuery(tree.root).get(\n coreExtensionData.reactElement,\n );\n\n if (!element) {\n throw new Error(\n 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(...)` to access output data directly instead',\n );\n }\n\n return element;\n }\n\n #resolveTree() {\n if (this.#tree) {\n return this.#tree;\n }\n\n const [subject] = this.#extensions;\n if (!subject) {\n throw new Error(\n 'No subject found. At least one extension should be added to the tester.',\n );\n }\n\n const collector = createErrorCollector();\n\n const tree = resolveAppTree(\n subject.id,\n resolveAppNodeSpecs({\n features: [],\n builtinExtensions: this.#extensions.map(_ => _.extension),\n parameters: readAppExtensionsConfig(this.#getConfig()),\n collector,\n }),\n collector,\n );\n\n instantiateAppNodeTree(tree.root, TestApiRegistry.from(), collector);\n\n const errors = collector.collectErrors();\n if (errors) {\n throw new Error(\n `Failed to resolve the extension tree: ${errors\n .map(e => e.message)\n .join(', ')}`,\n );\n }\n\n this.#tree = tree;\n\n return tree;\n }\n\n #getConfig(additionalConfig?: JsonObject): Config {\n const [subject, ...rest] = this.#extensions;\n\n const extensionsConfig: JsonArray = [\n ...rest.flatMap(extension =>\n extension.config\n ? [\n {\n [extension.id]: {\n config: extension.config,\n },\n },\n ]\n : [],\n ),\n {\n [subject.id]: {\n config: subject.config,\n disabled: false,\n },\n },\n ];\n\n return ConfigReader.fromConfigs([\n { context: 'render-config', data: additionalConfig ?? {} },\n {\n context: 'test',\n data: {\n app: {\n extensions: extensionsConfig,\n },\n },\n },\n ]);\n }\n}\n\n/** @public */\nexport function createExtensionTester<T extends ExtensionDefinitionParameters>(\n subject: ExtensionDefinition<T>,\n options?: { config?: T['configInput'] },\n): ExtensionTester<NonNullable<T['output']>> {\n return ExtensionTester.forSubject(subject, options);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"createExtensionTester.esm.js","sources":["../../src/app/createExtensionTester.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AppNode,\n AppTree,\n Extension,\n ExtensionDataRef,\n ExtensionDefinition,\n ExtensionDefinitionParameters,\n coreExtensionData,\n} from '@backstage/frontend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonArray, JsonObject, JsonValue } from '@backstage/types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveAppTree } from '../../../frontend-app-api/src/tree/resolveAppTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveAppNodeSpecs } from '../../../frontend-app-api/src/tree/resolveAppNodeSpecs';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { instantiateAppNodeTree } from '../../../frontend-app-api/src/tree/instantiateAppNodeTree';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { readAppExtensionsConfig } from '../../../frontend-app-api/src/tree/readAppExtensionsConfig';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { createErrorCollector } from '../../../frontend-app-api/src/wiring/createErrorCollector';\nimport { OpaqueExtensionDefinition } from '@internal/frontend';\nimport { resolveTestApiEntries, TestApiPairs } from '../apis/TestApiProvider';\n\n/**\n * Represents a snapshot of an extension in the app tree.\n *\n * @public\n */\nexport interface ExtensionSnapshotNode {\n /** The ID of the extension */\n id: string;\n /** The IDs of output data refs produced by this extension */\n outputs?: string[];\n /** Child extensions organized by input name */\n children?: Record<string, ExtensionSnapshotNode[]>;\n /** Whether this extension is disabled */\n disabled?: true;\n}\n\n/** @public */\nexport class ExtensionQuery<UOutput extends ExtensionDataRef> {\n #node: AppNode;\n\n constructor(node: AppNode) {\n this.#node = node;\n }\n\n get node() {\n return this.#node;\n }\n\n get instance() {\n const instance = this.#node.instance;\n if (!instance) {\n throw new Error(\n `Unable to access the instance of extension with ID '${\n this.#node.spec.id\n }'`,\n );\n }\n return instance;\n }\n\n get<TId extends UOutput['id']>(\n ref: ExtensionDataRef<any, TId, any>,\n ): UOutput extends ExtensionDataRef<infer IData, TId, infer IConfig>\n ? IConfig['optional'] extends true\n ? IData | undefined\n : IData\n : never {\n return this.instance.getData(ref);\n }\n}\n\n/** @public */\nexport class ExtensionTester<UOutput extends ExtensionDataRef> {\n /** @internal */\n static forSubject<\n T extends ExtensionDefinitionParameters,\n const TApiPairs extends any[],\n >(\n subject: ExtensionDefinition<T>,\n options?: {\n config?: T['configInput'];\n apis?: readonly [...TestApiPairs<TApiPairs>];\n },\n ): ExtensionTester<NonNullable<T['output']>> {\n const tester = new ExtensionTester(options?.apis);\n tester.add(subject, options as T['configInput'] & {});\n return tester;\n }\n\n #tree?: AppTree;\n #apis?: readonly any[];\n\n readonly #extensions = new Array<{\n id: string;\n extension: Extension<any>;\n definition: ExtensionDefinition;\n config?: JsonValue;\n }>();\n\n private constructor(apis?: readonly any[]) {\n this.#apis = apis;\n }\n\n add<T extends ExtensionDefinitionParameters>(\n extension: ExtensionDefinition<T>,\n options?: { config?: T['configInput'] },\n ): ExtensionTester<UOutput> {\n if (this.#tree) {\n throw new Error(\n 'Cannot add more extensions accessing the extension tree',\n );\n }\n\n const { name, namespace } = OpaqueExtensionDefinition.toInternal(extension);\n\n const definition = {\n ...extension,\n // setting name \"test\" as fallback\n name: !namespace && !name ? 'test' : name,\n };\n\n const resolvedExtension = resolveExtensionDefinition(definition);\n\n this.#extensions.push({\n id: resolvedExtension.id,\n extension: resolvedExtension,\n definition,\n config: options?.config as JsonValue,\n });\n\n return this;\n }\n\n get<TId extends UOutput['id']>(\n ref: ExtensionDataRef<any, TId, any>,\n ): UOutput extends ExtensionDataRef<infer IData, TId, infer IConfig>\n ? IConfig['optional'] extends true\n ? IData | undefined\n : IData\n : never {\n const tree = this.#resolveTree();\n\n return new ExtensionQuery(tree.root).get(ref);\n }\n\n query<T extends ExtensionDefinitionParameters>(\n extension: ExtensionDefinition<T>,\n ): ExtensionQuery<NonNullable<T['output']>> {\n const tree = this.#resolveTree();\n\n // Same fallback logic as in .add\n const { name, namespace } = OpaqueExtensionDefinition.toInternal(extension);\n const definition = {\n ...extension,\n name: !namespace && !name ? 'test' : name,\n };\n const actualId = resolveExtensionDefinition(definition).id;\n\n const node = tree.nodes.get(actualId);\n\n if (!node) {\n throw new Error(\n `Extension with ID '${actualId}' not found, please make sure it's added to the tester.`,\n );\n } else if (!node.instance) {\n throw new Error(\n `Extension with ID '${actualId}' has not been instantiated, because it is not part of the test subject's extension tree.`,\n );\n }\n return new ExtensionQuery(node);\n }\n\n reactElement(): JSX.Element {\n const tree = this.#resolveTree();\n\n const element = new ExtensionQuery(tree.root).get(\n coreExtensionData.reactElement,\n );\n\n if (!element) {\n throw new Error(\n 'No element found. Make sure the extension has a `coreExtensionData.reactElement` output, or use the `.get(...)` to access output data directly instead',\n );\n }\n\n return element;\n }\n\n /**\n * Returns a snapshot of the extension tree structure for testing and debugging.\n * Convenient to use with Jest's inline snapshot testing.\n *\n * @example\n * ```tsx\n * const tester = createExtensionTester(myExtension);\n * expect(tester.snapshot()).toMatchInlineSnapshot();\n * ```\n */\n snapshot(): ExtensionSnapshotNode {\n const tree = this.#resolveTree();\n\n const buildNode = (node: AppNode): ExtensionSnapshotNode => {\n const outputs = node.instance\n ? Array.from(node.instance.getDataRefs())\n .map(ref => ref.id)\n .sort()\n : [];\n\n const children: Record<string, ExtensionSnapshotNode[]> = {};\n for (const [inputName, attachedNodes] of node.edges.attachments) {\n children[inputName] = attachedNodes\n .map(n => buildNode(n))\n .sort((a, b) => a.id.localeCompare(b.id));\n }\n\n const result: ExtensionSnapshotNode = {\n id: node.spec.id,\n };\n\n // Only include non-empty/non-default fields\n if (outputs.length > 0) {\n result.outputs = outputs;\n }\n if (Object.keys(children).length > 0) {\n result.children = children;\n }\n if (node.spec.disabled) {\n result.disabled = true;\n }\n\n return result;\n };\n\n return buildNode(tree.root);\n }\n\n #resolveTree() {\n if (this.#tree) {\n return this.#tree;\n }\n\n const [subject] = this.#extensions;\n if (!subject) {\n throw new Error(\n 'No subject found. At least one extension should be added to the tester.',\n );\n }\n\n const collector = createErrorCollector();\n\n const tree = resolveAppTree(\n subject.id,\n resolveAppNodeSpecs({\n features: [],\n builtinExtensions: this.#extensions.map(_ => _.extension),\n parameters: readAppExtensionsConfig(this.#getConfig()),\n collector,\n }),\n collector,\n );\n\n const apiHolder = resolveTestApiEntries(this.#apis ?? []);\n\n instantiateAppNodeTree(tree.root, apiHolder, collector);\n\n const errors = collector.collectErrors();\n if (errors) {\n throw new Error(\n `Failed to resolve the extension tree: ${errors\n .map(e => e.message)\n .join(', ')}`,\n );\n }\n\n this.#tree = tree;\n\n return tree;\n }\n\n #getConfig(additionalConfig?: JsonObject): Config {\n const [subject, ...rest] = this.#extensions;\n\n const extensionsConfig: JsonArray = [\n ...rest.flatMap(extension =>\n extension.config\n ? [\n {\n [extension.id]: {\n config: extension.config,\n },\n },\n ]\n : [],\n ),\n {\n [subject.id]: {\n config: subject.config,\n disabled: false,\n },\n },\n ];\n\n return ConfigReader.fromConfigs([\n { context: 'render-config', data: additionalConfig ?? {} },\n {\n context: 'test',\n data: {\n app: {\n extensions: extensionsConfig,\n },\n },\n },\n ]);\n }\n}\n\n/** @public */\nexport function createExtensionTester<\n T extends ExtensionDefinitionParameters,\n TApiPairs extends any[] = any[],\n>(\n subject: ExtensionDefinition<T>,\n options?: {\n config?: T['configInput'];\n apis?: readonly [...TestApiPairs<TApiPairs>];\n },\n): ExtensionTester<NonNullable<T['output']>> {\n return ExtensionTester.forSubject(subject, options);\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -4,9 +4,10 @@ import { Link, MemoryRouter } from 'react-router-dom';
|
|
|
4
4
|
import { createSpecializedApp } from '@backstage/frontend-app-api';
|
|
5
5
|
import { render } from '@testing-library/react';
|
|
6
6
|
import { ConfigReader } from '@backstage/config';
|
|
7
|
-
import { coreExtensionData, NavItemBlueprint, useRouteRef, createExtension, createFrontendModule, createFrontendPlugin } from '@backstage/frontend-plugin-api';
|
|
7
|
+
import { coreExtensionData, NavItemBlueprint, useRouteRef, createExtension, createFrontendModule, createFrontendPlugin, createApiFactory } from '@backstage/frontend-plugin-api';
|
|
8
8
|
import { RouterBlueprint } from '@backstage/plugin-app-react';
|
|
9
9
|
import appPlugin from '@backstage/plugin-app';
|
|
10
|
+
import { getMockApiFactory } from '../apis/MockWithApiFactory.esm.js';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_MOCK_CONFIG = {
|
|
12
13
|
app: { baseUrl: "http://localhost:3000" },
|
|
@@ -97,7 +98,17 @@ function renderInTestApp(element, options) {
|
|
|
97
98
|
extensions: [
|
|
98
99
|
RouterBlueprint.make({
|
|
99
100
|
params: {
|
|
100
|
-
component: ({ children }) => /* @__PURE__ */ jsx(
|
|
101
|
+
component: ({ children }) => /* @__PURE__ */ jsx(
|
|
102
|
+
MemoryRouter,
|
|
103
|
+
{
|
|
104
|
+
initialEntries: options?.initialRouteEntries,
|
|
105
|
+
future: {
|
|
106
|
+
v7_relativeSplatPath: true,
|
|
107
|
+
v7_startTransition: true
|
|
108
|
+
},
|
|
109
|
+
children
|
|
110
|
+
}
|
|
111
|
+
)
|
|
101
112
|
}
|
|
102
113
|
})
|
|
103
114
|
]
|
|
@@ -118,7 +129,17 @@ function renderInTestApp(element, options) {
|
|
|
118
129
|
context: "render-config",
|
|
119
130
|
data: options?.config ?? DEFAULT_MOCK_CONFIG
|
|
120
131
|
}
|
|
121
|
-
])
|
|
132
|
+
]),
|
|
133
|
+
__internal: options?.apis && {
|
|
134
|
+
apiFactoryOverrides: options.apis.map((entry) => {
|
|
135
|
+
const mockFactory = getMockApiFactory(entry);
|
|
136
|
+
if (mockFactory) {
|
|
137
|
+
return mockFactory;
|
|
138
|
+
}
|
|
139
|
+
const [apiRef, implementation] = entry;
|
|
140
|
+
return createApiFactory(apiRef, implementation);
|
|
141
|
+
})
|
|
142
|
+
}
|
|
122
143
|
});
|
|
123
144
|
return render(
|
|
124
145
|
app.tree.root.instance.getData(coreExtensionData.reactElement)
|