@backstage/frontend-test-utils 0.4.6-next.1 → 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.
Files changed (41) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/apis/AlertApi/MockAlertApi.esm.js +64 -0
  3. package/dist/apis/AlertApi/MockAlertApi.esm.js.map +1 -0
  4. package/dist/apis/ConfigApi/MockConfigApi.esm.js +76 -0
  5. package/dist/apis/ConfigApi/MockConfigApi.esm.js.map +1 -0
  6. package/dist/apis/ErrorApi/MockErrorApi.esm.js +44 -0
  7. package/dist/apis/ErrorApi/MockErrorApi.esm.js.map +1 -0
  8. package/dist/apis/FeatureFlagsApi/MockFeatureFlagsApi.esm.js +55 -0
  9. package/dist/apis/FeatureFlagsApi/MockFeatureFlagsApi.esm.js.map +1 -0
  10. package/dist/apis/FetchApi/MockFetchApi.esm.js +56 -0
  11. package/dist/apis/FetchApi/MockFetchApi.esm.js.map +1 -0
  12. package/dist/apis/MockWithApiFactory.esm.js +28 -0
  13. package/dist/apis/MockWithApiFactory.esm.js.map +1 -0
  14. package/dist/apis/PermissionApi/MockPermissionApi.esm.js +13 -0
  15. package/dist/apis/PermissionApi/MockPermissionApi.esm.js.map +1 -0
  16. package/dist/apis/StorageApi/MockStorageApi.esm.js +98 -0
  17. package/dist/apis/StorageApi/MockStorageApi.esm.js.map +1 -0
  18. package/dist/apis/TestApiProvider.esm.js +31 -0
  19. package/dist/apis/TestApiProvider.esm.js.map +1 -0
  20. package/dist/apis/TranslationApi/MockTranslationApi.esm.js +68 -0
  21. package/dist/apis/TranslationApi/MockTranslationApi.esm.js.map +1 -0
  22. package/dist/apis/createApiMock.esm.js +25 -0
  23. package/dist/apis/createApiMock.esm.js.map +1 -0
  24. package/dist/apis/mockApis.esm.js +195 -0
  25. package/dist/apis/mockApis.esm.js.map +1 -0
  26. package/dist/app/createExtensionTester.esm.js +36 -2
  27. package/dist/app/createExtensionTester.esm.js.map +1 -1
  28. package/dist/app/renderInTestApp.esm.js +20 -4
  29. package/dist/app/renderInTestApp.esm.js.map +1 -1
  30. package/dist/app/renderTestApp.esm.js +45 -7
  31. package/dist/app/renderTestApp.esm.js.map +1 -1
  32. package/dist/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.esm.js +67 -0
  33. package/dist/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.esm.js.map +1 -0
  34. package/dist/frontend-plugin-api/src/translation/TranslationRef.esm.js +13 -0
  35. package/dist/frontend-plugin-api/src/translation/TranslationRef.esm.js.map +1 -0
  36. package/dist/index.d.ts +690 -51
  37. package/dist/index.esm.js +5 -3
  38. package/dist/index.esm.js.map +1 -1
  39. package/package.json +22 -9
  40. package/dist/utils/TestApiProvider.esm.js +0 -52
  41. package/dist/utils/TestApiProvider.esm.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @backstage/frontend-test-utils
2
2
 
3
+ ## 0.5.0-next.2
4
+
5
+ ### Minor Changes
6
+
7
+ - 09a6aad: **BREAKING**: Removed the `TestApiRegistry` class, use `TestApiProvider` directly instead, storing reused APIs in a variable, e.g. `const apis = [...] as const`.
8
+ - d2ac2ec: Added `MockAlertApi` and `MockFeatureFlagsApi` implementations to the `mockApis` namespace. The mock implementations include useful testing methods like `clearAlerts()`, `waitForAlert()`, `getState()`, `setState()`, and `clearState()` for better test ergonomics.
9
+ - 09a6aad: **BREAKING**: The `mockApis` namespace is no longer a re-export from `@backstage/test-utils`. It's now a standalone namespace with mock implementations of most core APIs. Mock API instances can be passed directly to `TestApiProvider`, `renderInTestApp`, and `renderTestApp` without needing `[apiRef, impl]` tuples. As part of this change, the `.factory()` method on some mocks has been removed, since it's now redundant.
10
+
11
+ ```tsx
12
+ // Before
13
+ import { mockApis } from '@backstage/frontend-test-utils';
14
+
15
+ renderInTestApp(<MyComponent />, {
16
+ apis: [[identityApiRef, mockApis.identity()]],
17
+ });
18
+
19
+ // After - mock APIs can be passed directly
20
+ renderInTestApp(<MyComponent />, {
21
+ apis: [mockApis.identity()],
22
+ });
23
+ ```
24
+
25
+ This change also adds `createApiMock`, a public utility for creating mock API factories, intended for plugin authors to create their own `.mock()` variants.
26
+
27
+ ### Patch Changes
28
+
29
+ - 15ed3f9: Added `snapshot()` method to `ExtensionTester`, which returns a tree-shaped representation of the resolved extension hierarchy. Convenient to use with `toMatchInlineSnapshot()`.
30
+ - 013ec22: Added `mountedRoutes` option to `renderTestApp` for binding route refs to paths, matching the existing option in `renderInTestApp`:
31
+
32
+ ```typescript
33
+ renderTestApp({
34
+ extensions: [...],
35
+ mountedRoutes: {
36
+ '/my-path': myRouteRef,
37
+ },
38
+ });
39
+ ```
40
+
41
+ - a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
42
+ - Updated dependencies
43
+ - @backstage/core-app-api@1.19.5-next.1
44
+ - @backstage/frontend-plugin-api@0.14.0-next.2
45
+ - @backstage/frontend-app-api@0.15.0-next.2
46
+ - @backstage/core-plugin-api@1.12.3-next.1
47
+ - @backstage/plugin-permission-react@0.4.40-next.1
48
+ - @backstage/version-bridge@1.0.12-next.0
49
+ - @backstage/test-utils@1.7.15-next.2
50
+ - @backstage/plugin-app@0.4.0-next.2
51
+ - @backstage/plugin-app-react@0.1.1-next.0
52
+
3
53
  ## 0.4.6-next.1
4
54
 
5
55
  ### Patch Changes
@@ -0,0 +1,64 @@
1
+ import ObservableImpl from 'zen-observable';
2
+
3
+ class MockAlertApi {
4
+ alerts = [];
5
+ observers = /* @__PURE__ */ new Set();
6
+ post(alert) {
7
+ this.alerts.push(alert);
8
+ this.observers.forEach((observer) => observer(alert));
9
+ }
10
+ alert$() {
11
+ return new ObservableImpl((subscriber) => {
12
+ const observer = (alert) => {
13
+ subscriber.next(alert);
14
+ };
15
+ this.observers.add(observer);
16
+ return () => {
17
+ this.observers.delete(observer);
18
+ };
19
+ });
20
+ }
21
+ /**
22
+ * Get all alerts that have been posted.
23
+ */
24
+ getAlerts() {
25
+ return this.alerts;
26
+ }
27
+ /**
28
+ * Clear all collected alerts.
29
+ */
30
+ clearAlerts() {
31
+ this.alerts.length = 0;
32
+ }
33
+ /**
34
+ * Wait for an alert matching the given predicate.
35
+ *
36
+ * @param predicate - Function to test each alert
37
+ * @param timeoutMs - Maximum time to wait in milliseconds
38
+ * @returns Promise that resolves with the matching alert
39
+ */
40
+ async waitForAlert(predicate, timeoutMs = 2e3) {
41
+ const existing = this.alerts.find(predicate);
42
+ if (existing) {
43
+ return existing;
44
+ }
45
+ const observers = this.observers;
46
+ return new Promise((resolve, reject) => {
47
+ const timeoutId = setTimeout(() => {
48
+ observers.delete(observer);
49
+ reject(new Error("Timed out waiting for alert"));
50
+ }, timeoutMs);
51
+ function observer(alert) {
52
+ if (predicate(alert)) {
53
+ clearTimeout(timeoutId);
54
+ observers.delete(observer);
55
+ resolve(alert);
56
+ }
57
+ }
58
+ observers.add(observer);
59
+ });
60
+ }
61
+ }
62
+
63
+ export { MockAlertApi };
64
+ //# sourceMappingURL=MockAlertApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockAlertApi.esm.js","sources":["../../../src/apis/AlertApi/MockAlertApi.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 { AlertMessage } from '@backstage/frontend-plugin-api';\nimport { AlertApi } from '@backstage/frontend-plugin-api';\nimport { Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\n/**\n * Mock implementation of {@link @backstage/frontend-plugin-api#AlertApi} for testing alert behavior.\n *\n * @public\n * @example\n * ```ts\n * const alertApi = new MockAlertApi();\n * alertApi.post({ message: 'Test alert' });\n * expect(alertApi.getAlerts()).toHaveLength(1);\n * ```\n */\nexport class MockAlertApi implements AlertApi {\n private readonly alerts: AlertMessage[] = [];\n private readonly observers = new Set<(alert: AlertMessage) => void>();\n\n post(alert: AlertMessage) {\n this.alerts.push(alert);\n this.observers.forEach(observer => observer(alert));\n }\n\n alert$(): Observable<AlertMessage> {\n return new ObservableImpl(subscriber => {\n const observer = (alert: AlertMessage) => {\n subscriber.next(alert);\n };\n this.observers.add(observer);\n return () => {\n this.observers.delete(observer);\n };\n });\n }\n\n /**\n * Get all alerts that have been posted.\n */\n getAlerts(): AlertMessage[] {\n return this.alerts;\n }\n\n /**\n * Clear all collected alerts.\n */\n clearAlerts(): void {\n this.alerts.length = 0;\n }\n\n /**\n * Wait for an alert matching the given predicate.\n *\n * @param predicate - Function to test each alert\n * @param timeoutMs - Maximum time to wait in milliseconds\n * @returns Promise that resolves with the matching alert\n */\n async waitForAlert(\n predicate: (alert: AlertMessage) => boolean,\n timeoutMs: number = 2000,\n ): Promise<AlertMessage> {\n const existing = this.alerts.find(predicate);\n if (existing) {\n return existing;\n }\n const observers = this.observers;\n\n return new Promise<AlertMessage>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n observers.delete(observer);\n reject(new Error('Timed out waiting for alert'));\n }, timeoutMs);\n\n function observer(alert: AlertMessage) {\n if (predicate(alert)) {\n clearTimeout(timeoutId);\n observers.delete(observer);\n resolve(alert);\n }\n }\n\n observers.add(observer);\n });\n }\n}\n"],"names":[],"mappings":";;AAgCO,MAAM,YAAA,CAAiC;AAAA,EAC3B,SAAyB,EAAC;AAAA,EAC1B,SAAA,uBAAgB,GAAA,EAAmC;AAAA,EAEpE,KAAK,KAAA,EAAqB;AACxB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAK,KAAK,CAAA;AACtB,IAAA,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,CAAA,QAAA,KAAY,QAAA,CAAS,KAAK,CAAC,CAAA;AAAA,EACpD;AAAA,EAEA,MAAA,GAAmC;AACjC,IAAA,OAAO,IAAI,eAAe,CAAA,UAAA,KAAc;AACtC,MAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAAwB;AACxC,QAAA,UAAA,CAAW,KAAK,KAAK,CAAA;AAAA,MACvB,CAAA;AACA,MAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,MAAA,OAAO,MAAM;AACX,QAAA,IAAA,CAAK,SAAA,CAAU,OAAO,QAAQ,CAAA;AAAA,MAChC,CAAA;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA,EAKA,SAAA,GAA4B;AAC1B,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,GAAoB;AAClB,IAAA,IAAA,CAAK,OAAO,MAAA,GAAS,CAAA;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,YAAA,CACJ,SAAA,EACA,SAAA,GAAoB,GAAA,EACG;AACvB,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,SAAS,CAAA;AAC3C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,OAAO,QAAA;AAAA,IACT;AACA,IAAA,MAAM,YAAY,IAAA,CAAK,SAAA;AAEvB,IAAA,OAAO,IAAI,OAAA,CAAsB,CAAC,OAAA,EAAS,MAAA,KAAW;AACpD,MAAA,MAAM,SAAA,GAAY,WAAW,MAAM;AACjC,QAAA,SAAA,CAAU,OAAO,QAAQ,CAAA;AACzB,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AAAA,MACjD,GAAG,SAAS,CAAA;AAEZ,MAAA,SAAS,SAAS,KAAA,EAAqB;AACrC,QAAA,IAAI,SAAA,CAAU,KAAK,CAAA,EAAG;AACpB,UAAA,YAAA,CAAa,SAAS,CAAA;AACtB,UAAA,SAAA,CAAU,OAAO,QAAQ,CAAA;AACzB,UAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,QACf;AAAA,MACF;AAEA,MAAA,SAAA,CAAU,IAAI,QAAQ,CAAA;AAAA,IACxB,CAAC,CAAA;AAAA,EACH;AACF;;;;"}
@@ -0,0 +1,76 @@
1
+ import { ConfigReader } from '@backstage/config';
2
+
3
+ class MockConfigApi {
4
+ config;
5
+ // NOTE: not extending in order to avoid inheriting the static `.fromConfigs`
6
+ constructor({ data }) {
7
+ this.config = new ConfigReader(data);
8
+ }
9
+ /** {@inheritdoc @backstage/config#Config.has} */
10
+ has(key) {
11
+ return this.config.has(key);
12
+ }
13
+ /** {@inheritdoc @backstage/config#Config.keys} */
14
+ keys() {
15
+ return this.config.keys();
16
+ }
17
+ /** {@inheritdoc @backstage/config#Config.get} */
18
+ get(key) {
19
+ return this.config.get(key);
20
+ }
21
+ /** {@inheritdoc @backstage/config#Config.getOptional} */
22
+ getOptional(key) {
23
+ return this.config.getOptional(key);
24
+ }
25
+ /** {@inheritdoc @backstage/config#Config.getConfig} */
26
+ getConfig(key) {
27
+ return this.config.getConfig(key);
28
+ }
29
+ /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */
30
+ getOptionalConfig(key) {
31
+ return this.config.getOptionalConfig(key);
32
+ }
33
+ /** {@inheritdoc @backstage/config#Config.getConfigArray} */
34
+ getConfigArray(key) {
35
+ return this.config.getConfigArray(key);
36
+ }
37
+ /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */
38
+ getOptionalConfigArray(key) {
39
+ return this.config.getOptionalConfigArray(key);
40
+ }
41
+ /** {@inheritdoc @backstage/config#Config.getNumber} */
42
+ getNumber(key) {
43
+ return this.config.getNumber(key);
44
+ }
45
+ /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */
46
+ getOptionalNumber(key) {
47
+ return this.config.getOptionalNumber(key);
48
+ }
49
+ /** {@inheritdoc @backstage/config#Config.getBoolean} */
50
+ getBoolean(key) {
51
+ return this.config.getBoolean(key);
52
+ }
53
+ /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */
54
+ getOptionalBoolean(key) {
55
+ return this.config.getOptionalBoolean(key);
56
+ }
57
+ /** {@inheritdoc @backstage/config#Config.getString} */
58
+ getString(key) {
59
+ return this.config.getString(key);
60
+ }
61
+ /** {@inheritdoc @backstage/config#Config.getOptionalString} */
62
+ getOptionalString(key) {
63
+ return this.config.getOptionalString(key);
64
+ }
65
+ /** {@inheritdoc @backstage/config#Config.getStringArray} */
66
+ getStringArray(key) {
67
+ return this.config.getStringArray(key);
68
+ }
69
+ /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */
70
+ getOptionalStringArray(key) {
71
+ return this.config.getOptionalStringArray(key);
72
+ }
73
+ }
74
+
75
+ export { MockConfigApi };
76
+ //# sourceMappingURL=MockConfigApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockConfigApi.esm.js","sources":["../../../src/apis/ConfigApi/MockConfigApi.ts"],"sourcesContent":["/*\n * Copyright 2022 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 { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { ConfigApi } from '@backstage/core-plugin-api';\n\n/**\n * MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader}\n * that can be used to mock configuration using a plain object.\n *\n * @public\n * @example\n * ```tsx\n * const mockConfig = new MockConfigApi({\n * data: { app: { baseUrl: 'https://example.com' } },\n * });\n *\n * const rendered = await renderInTestApp(\n * <TestApiProvider apis={[[configApiRef, mockConfig]]}>\n * <MyTestedComponent />\n * </TestApiProvider>,\n * );\n * ```\n */\nexport class MockConfigApi implements ConfigApi {\n private readonly config: ConfigReader;\n\n // NOTE: not extending in order to avoid inheriting the static `.fromConfigs`\n constructor({ data }: { data: JsonObject }) {\n this.config = new ConfigReader(data);\n }\n\n /** {@inheritdoc @backstage/config#Config.has} */\n has(key: string): boolean {\n return this.config.has(key);\n }\n /** {@inheritdoc @backstage/config#Config.keys} */\n keys(): string[] {\n return this.config.keys();\n }\n /** {@inheritdoc @backstage/config#Config.get} */\n get<T = JsonValue>(key?: string): T {\n return this.config.get(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptional} */\n getOptional<T = JsonValue>(key?: string): T | undefined {\n return this.config.getOptional(key);\n }\n /** {@inheritdoc @backstage/config#Config.getConfig} */\n getConfig(key: string): Config {\n return this.config.getConfig(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */\n getOptionalConfig(key: string): Config | undefined {\n return this.config.getOptionalConfig(key);\n }\n /** {@inheritdoc @backstage/config#Config.getConfigArray} */\n getConfigArray(key: string): Config[] {\n return this.config.getConfigArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */\n getOptionalConfigArray(key: string): Config[] | undefined {\n return this.config.getOptionalConfigArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getNumber} */\n getNumber(key: string): number {\n return this.config.getNumber(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */\n getOptionalNumber(key: string): number | undefined {\n return this.config.getOptionalNumber(key);\n }\n /** {@inheritdoc @backstage/config#Config.getBoolean} */\n getBoolean(key: string): boolean {\n return this.config.getBoolean(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */\n getOptionalBoolean(key: string): boolean | undefined {\n return this.config.getOptionalBoolean(key);\n }\n /** {@inheritdoc @backstage/config#Config.getString} */\n getString(key: string): string {\n return this.config.getString(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalString} */\n getOptionalString(key: string): string | undefined {\n return this.config.getOptionalString(key);\n }\n /** {@inheritdoc @backstage/config#Config.getStringArray} */\n getStringArray(key: string): string[] {\n return this.config.getStringArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */\n getOptionalStringArray(key: string): string[] | undefined {\n return this.config.getOptionalStringArray(key);\n }\n}\n"],"names":[],"mappings":";;AAsCO,MAAM,aAAA,CAAmC;AAAA,EAC7B,MAAA;AAAA;AAAA,EAGjB,WAAA,CAAY,EAAE,IAAA,EAAK,EAAyB;AAC1C,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,YAAA,CAAa,IAAI,CAAA;AAAA,EACrC;AAAA;AAAA,EAGA,IAAI,GAAA,EAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAAA,EAC5B;AAAA;AAAA,EAEA,IAAA,GAAiB;AACf,IAAA,OAAO,IAAA,CAAK,OAAO,IAAA,EAAK;AAAA,EAC1B;AAAA;AAAA,EAEA,IAAmB,GAAA,EAAiB;AAClC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAG,CAAA;AAAA,EAC5B;AAAA;AAAA,EAEA,YAA2B,GAAA,EAA6B;AACtD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,WAAA,CAAY,GAAG,CAAA;AAAA,EACpC;AAAA;AAAA,EAEA,UAAU,GAAA,EAAqB;AAC7B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA;AAAA,EAClC;AAAA;AAAA,EAEA,kBAAkB,GAAA,EAAiC;AACjD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,GAAG,CAAA;AAAA,EAC1C;AAAA;AAAA,EAEA,eAAe,GAAA,EAAuB;AACpC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,cAAA,CAAe,GAAG,CAAA;AAAA,EACvC;AAAA;AAAA,EAEA,uBAAuB,GAAA,EAAmC;AACxD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,sBAAA,CAAuB,GAAG,CAAA;AAAA,EAC/C;AAAA;AAAA,EAEA,UAAU,GAAA,EAAqB;AAC7B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA;AAAA,EAClC;AAAA;AAAA,EAEA,kBAAkB,GAAA,EAAiC;AACjD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,GAAG,CAAA;AAAA,EAC1C;AAAA;AAAA,EAEA,WAAW,GAAA,EAAsB;AAC/B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,UAAA,CAAW,GAAG,CAAA;AAAA,EACnC;AAAA;AAAA,EAEA,mBAAmB,GAAA,EAAkC;AACnD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,kBAAA,CAAmB,GAAG,CAAA;AAAA,EAC3C;AAAA;AAAA,EAEA,UAAU,GAAA,EAAqB;AAC7B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,SAAA,CAAU,GAAG,CAAA;AAAA,EAClC;AAAA;AAAA,EAEA,kBAAkB,GAAA,EAAiC;AACjD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,iBAAA,CAAkB,GAAG,CAAA;AAAA,EAC1C;AAAA;AAAA,EAEA,eAAe,GAAA,EAAuB;AACpC,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,cAAA,CAAe,GAAG,CAAA;AAAA,EACvC;AAAA;AAAA,EAEA,uBAAuB,GAAA,EAAmC;AACxD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,sBAAA,CAAuB,GAAG,CAAA;AAAA,EAC/C;AACF;;;;"}
@@ -0,0 +1,44 @@
1
+ const nullObservable = {
2
+ subscribe: () => ({ unsubscribe: () => {
3
+ }, closed: true }),
4
+ [Symbol.observable]() {
5
+ return this;
6
+ }
7
+ };
8
+ class MockErrorApi {
9
+ constructor(options = {}) {
10
+ this.options = options;
11
+ }
12
+ errors = new Array();
13
+ waiters = /* @__PURE__ */ new Set();
14
+ post(error, context) {
15
+ if (this.options.collect) {
16
+ this.errors.push({ error, context });
17
+ for (const waiter of this.waiters) {
18
+ if (waiter.pattern.test(error.message)) {
19
+ this.waiters.delete(waiter);
20
+ waiter.resolve({ error, context });
21
+ }
22
+ }
23
+ return;
24
+ }
25
+ throw new Error(`MockErrorApi received unexpected error, ${error}`);
26
+ }
27
+ error$() {
28
+ return nullObservable;
29
+ }
30
+ getErrors() {
31
+ return this.errors;
32
+ }
33
+ waitForError(pattern, timeoutMs = 2e3) {
34
+ return new Promise((resolve, reject) => {
35
+ setTimeout(() => {
36
+ reject(new Error("Timed out waiting for error"));
37
+ }, timeoutMs);
38
+ this.waiters.add({ resolve, pattern });
39
+ });
40
+ }
41
+ }
42
+
43
+ export { MockErrorApi };
44
+ //# sourceMappingURL=MockErrorApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockErrorApi.esm.js","sources":["../../../src/apis/ErrorApi/MockErrorApi.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 {\n ErrorApi,\n ErrorApiError,\n ErrorApiErrorContext,\n} from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\n\n/**\n * Constructor arguments for {@link MockErrorApi}\n * @public\n */\nexport type MockErrorApiOptions = {\n // Need to be true if getErrors is used in testing.\n collect?: boolean;\n};\n\n/**\n * ErrorWithContext contains error and ErrorApiErrorContext\n * @public\n */\nexport type ErrorWithContext = {\n error: ErrorApiError;\n context?: ErrorApiErrorContext;\n};\n\ntype Waiter = {\n pattern: RegExp;\n resolve: (err: ErrorWithContext) => void;\n};\n\nconst nullObservable = {\n subscribe: () => ({ unsubscribe: () => {}, closed: true }),\n\n [Symbol.observable]() {\n return this;\n },\n};\n\n/**\n * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.\n * Includes withForError and getErrors methods for error testing.\n * @public\n */\nexport class MockErrorApi implements ErrorApi {\n private readonly errors = new Array<ErrorWithContext>();\n private readonly waiters = new Set<Waiter>();\n\n constructor(private readonly options: MockErrorApiOptions = {}) {}\n\n post(error: ErrorApiError, context?: ErrorApiErrorContext) {\n if (this.options.collect) {\n this.errors.push({ error, context });\n\n for (const waiter of this.waiters) {\n if (waiter.pattern.test(error.message)) {\n this.waiters.delete(waiter);\n waiter.resolve({ error, context });\n }\n }\n\n return;\n }\n\n throw new Error(`MockErrorApi received unexpected error, ${error}`);\n }\n\n error$(): Observable<{\n error: ErrorApiError;\n context?: ErrorApiErrorContext;\n }> {\n return nullObservable;\n }\n\n getErrors(): ErrorWithContext[] {\n return this.errors;\n }\n\n waitForError(\n pattern: RegExp,\n timeoutMs: number = 2000,\n ): Promise<ErrorWithContext> {\n return new Promise<ErrorWithContext>((resolve, reject) => {\n setTimeout(() => {\n reject(new Error('Timed out waiting for error'));\n }, timeoutMs);\n\n this.waiters.add({ resolve, pattern });\n });\n }\n}\n"],"names":[],"mappings":"AA8CA,MAAM,cAAA,GAAiB;AAAA,EACrB,SAAA,EAAW,OAAO,EAAE,WAAA,EAAa,MAAM;AAAA,EAAC,CAAA,EAAG,QAAQ,IAAA,EAAK,CAAA;AAAA,EAExD,CAAC,MAAA,CAAO,UAAU,CAAA,GAAI;AACpB,IAAA,OAAO,IAAA;AAAA,EACT;AACF,CAAA;AAOO,MAAM,YAAA,CAAiC;AAAA,EAI5C,WAAA,CAA6B,OAAA,GAA+B,EAAC,EAAG;AAAnC,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AAAA,EAAoC;AAAA,EAHhD,MAAA,GAAS,IAAI,KAAA,EAAwB;AAAA,EACrC,OAAA,uBAAc,GAAA,EAAY;AAAA,EAI3C,IAAA,CAAK,OAAsB,OAAA,EAAgC;AACzD,IAAA,IAAI,IAAA,CAAK,QAAQ,OAAA,EAAS;AACxB,MAAA,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,SAAS,CAAA;AAEnC,MAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,QAAA,IAAI,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,EAAG;AACtC,UAAA,IAAA,CAAK,OAAA,CAAQ,OAAO,MAAM,CAAA;AAC1B,UAAA,MAAA,CAAO,OAAA,CAAQ,EAAE,KAAA,EAAO,OAAA,EAAS,CAAA;AAAA,QACnC;AAAA,MACF;AAEA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wCAAA,EAA2C,KAAK,CAAA,CAAE,CAAA;AAAA,EACpE;AAAA,EAEA,MAAA,GAGG;AACD,IAAA,OAAO,cAAA;AAAA,EACT;AAAA,EAEA,SAAA,GAAgC;AAC9B,IAAA,OAAO,IAAA,CAAK,MAAA;AAAA,EACd;AAAA,EAEA,YAAA,CACE,OAAA,EACA,SAAA,GAAoB,GAAA,EACO;AAC3B,IAAA,OAAO,IAAI,OAAA,CAA0B,CAAC,OAAA,EAAS,MAAA,KAAW;AACxD,MAAA,UAAA,CAAW,MAAM;AACf,QAAA,MAAA,CAAO,IAAI,KAAA,CAAM,6BAA6B,CAAC,CAAA;AAAA,MACjD,GAAG,SAAS,CAAA;AAEZ,MAAA,IAAA,CAAK,OAAA,CAAQ,GAAA,CAAI,EAAE,OAAA,EAAS,SAAS,CAAA;AAAA,IACvC,CAAC,CAAA;AAAA,EACH;AACF;;;;"}
@@ -0,0 +1,55 @@
1
+ import { FeatureFlagState } from '@backstage/frontend-plugin-api';
2
+
3
+ class MockFeatureFlagsApi {
4
+ registeredFlags = [];
5
+ states;
6
+ constructor(options) {
7
+ this.states = new Map(Object.entries(options?.initialStates ?? {}));
8
+ }
9
+ registerFlag(flag) {
10
+ if (!this.registeredFlags.some((f) => f.name === flag.name)) {
11
+ this.registeredFlags.push(flag);
12
+ }
13
+ }
14
+ getRegisteredFlags() {
15
+ return this.registeredFlags;
16
+ }
17
+ isActive(name) {
18
+ return this.states.get(name) === FeatureFlagState.Active;
19
+ }
20
+ save(options) {
21
+ if (options.merge) {
22
+ for (const [name, state] of Object.entries(options.states)) {
23
+ this.states.set(name, state);
24
+ }
25
+ } else {
26
+ this.states.clear();
27
+ for (const [name, state] of Object.entries(options.states)) {
28
+ this.states.set(name, state);
29
+ }
30
+ }
31
+ }
32
+ /**
33
+ * Get the current state of all feature flags as a record.
34
+ */
35
+ getState() {
36
+ return Object.fromEntries(this.states);
37
+ }
38
+ /**
39
+ * Set the state of multiple feature flags.
40
+ */
41
+ setState(states) {
42
+ for (const [name, state] of Object.entries(states)) {
43
+ this.states.set(name, state);
44
+ }
45
+ }
46
+ /**
47
+ * Clear all feature flag states.
48
+ */
49
+ clearState() {
50
+ this.states.clear();
51
+ }
52
+ }
53
+
54
+ export { MockFeatureFlagsApi };
55
+ //# sourceMappingURL=MockFeatureFlagsApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockFeatureFlagsApi.esm.js","sources":["../../../src/apis/FeatureFlagsApi/MockFeatureFlagsApi.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 FeatureFlag,\n FeatureFlagsApi,\n FeatureFlagsSaveOptions,\n FeatureFlagState,\n} from '@backstage/frontend-plugin-api';\n\n/**\n * Options for configuring {@link MockFeatureFlagsApi}.\n *\n * @public\n */\nexport interface MockFeatureFlagsApiOptions {\n /**\n * Initial feature flag states.\n */\n initialStates?: Record<string, FeatureFlagState>;\n}\n\n/**\n * Mock implementation of {@link @backstage/frontend-plugin-api#FeatureFlagsApi} for testing feature flag behavior.\n *\n * @public\n * @example\n * ```ts\n * const api = new MockFeatureFlagsApi({\n * initialStates: { 'my-feature': FeatureFlagState.Active }\n * });\n * expect(api.isActive('my-feature')).toBe(true);\n * ```\n */\nexport class MockFeatureFlagsApi implements FeatureFlagsApi {\n private readonly registeredFlags: FeatureFlag[] = [];\n private readonly states: Map<string, FeatureFlagState>;\n\n constructor(options?: MockFeatureFlagsApiOptions) {\n this.states = new Map(Object.entries(options?.initialStates ?? {}));\n }\n\n registerFlag(flag: FeatureFlag): void {\n if (!this.registeredFlags.some(f => f.name === flag.name)) {\n this.registeredFlags.push(flag);\n }\n }\n\n getRegisteredFlags(): FeatureFlag[] {\n return this.registeredFlags;\n }\n\n isActive(name: string): boolean {\n return this.states.get(name) === FeatureFlagState.Active;\n }\n\n save(options: FeatureFlagsSaveOptions): void {\n if (options.merge) {\n for (const [name, state] of Object.entries(options.states)) {\n this.states.set(name, state);\n }\n } else {\n this.states.clear();\n for (const [name, state] of Object.entries(options.states)) {\n this.states.set(name, state);\n }\n }\n }\n\n /**\n * Get the current state of all feature flags as a record.\n */\n getState(): Record<string, FeatureFlagState> {\n return Object.fromEntries(this.states);\n }\n\n /**\n * Set the state of multiple feature flags.\n */\n setState(states: Record<string, FeatureFlagState>): void {\n for (const [name, state] of Object.entries(states)) {\n this.states.set(name, state);\n }\n }\n\n /**\n * Clear all feature flag states.\n */\n clearState(): void {\n this.states.clear();\n }\n}\n"],"names":[],"mappings":";;AA+CO,MAAM,mBAAA,CAA+C;AAAA,EACzC,kBAAiC,EAAC;AAAA,EAClC,MAAA;AAAA,EAEjB,YAAY,OAAA,EAAsC;AAChD,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,GAAA,CAAI,MAAA,CAAO,QAAQ,OAAA,EAAS,aAAA,IAAiB,EAAE,CAAC,CAAA;AAAA,EACpE;AAAA,EAEA,aAAa,IAAA,EAAyB;AACpC,IAAA,IAAI,CAAC,KAAK,eAAA,CAAgB,IAAA,CAAK,OAAK,CAAA,CAAE,IAAA,KAAS,IAAA,CAAK,IAAI,CAAA,EAAG;AACzD,MAAA,IAAA,CAAK,eAAA,CAAgB,KAAK,IAAI,CAAA;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,kBAAA,GAAoC;AAClC,IAAA,OAAO,IAAA,CAAK,eAAA;AAAA,EACd;AAAA,EAEA,SAAS,IAAA,EAAuB;AAC9B,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAI,MAAM,gBAAA,CAAiB,MAAA;AAAA,EACpD;AAAA,EAEA,KAAK,OAAA,EAAwC;AAC3C,IAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,MAAA,KAAA,MAAW,CAAC,MAAM,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC1D,QAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,KAAK,CAAA;AAAA,MAC7B;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAClB,MAAA,KAAA,MAAW,CAAC,MAAM,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,MAAM,CAAA,EAAG;AAC1D,QAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,KAAK,CAAA;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAA6C;AAC3C,IAAA,OAAO,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,MAAM,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,MAAA,EAAgD;AACvD,IAAA,KAAA,MAAW,CAAC,IAAA,EAAM,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AAClD,MAAA,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,IAAA,EAAM,KAAK,CAAA;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAA,GAAmB;AACjB,IAAA,IAAA,CAAK,OAAO,KAAA,EAAM;AAAA,EACpB;AACF;;;;"}
@@ -0,0 +1,56 @@
1
+ import { createFetchApi, FetchMiddlewares } from '@backstage/core-app-api';
2
+
3
+ class MockFetchApi {
4
+ implementation;
5
+ /**
6
+ * Creates a mock {@link @backstage/core-plugin-api#FetchApi}.
7
+ */
8
+ constructor(options) {
9
+ this.implementation = build(options);
10
+ }
11
+ /** {@inheritdoc @backstage/core-plugin-api#FetchApi.fetch} */
12
+ get fetch() {
13
+ return this.implementation.fetch;
14
+ }
15
+ }
16
+ function build(options) {
17
+ return createFetchApi({
18
+ baseImplementation: baseImplementation(options),
19
+ middleware: [
20
+ resolvePluginProtocol(options),
21
+ injectIdentityAuth(options)
22
+ ].filter((x) => Boolean(x))
23
+ });
24
+ }
25
+ function baseImplementation(options) {
26
+ const implementation = options?.baseImplementation;
27
+ if (!implementation) {
28
+ return (input, init) => global.fetch(input, init);
29
+ } else if (implementation === "none") {
30
+ return () => Promise.resolve(new Response());
31
+ }
32
+ return implementation;
33
+ }
34
+ function resolvePluginProtocol(allOptions) {
35
+ const options = allOptions?.resolvePluginProtocol;
36
+ if (!options) {
37
+ return void 0;
38
+ }
39
+ return FetchMiddlewares.resolvePluginProtocol({
40
+ discoveryApi: options.discoveryApi
41
+ });
42
+ }
43
+ function injectIdentityAuth(allOptions) {
44
+ const options = allOptions?.injectIdentityAuth;
45
+ if (!options) {
46
+ return void 0;
47
+ }
48
+ const identityApi = "token" in options ? { getCredentials: async () => ({ token: options.token }) } : options.identityApi;
49
+ return FetchMiddlewares.injectIdentityAuth({
50
+ identityApi,
51
+ allowUrl: () => true
52
+ });
53
+ }
54
+
55
+ export { MockFetchApi };
56
+ //# sourceMappingURL=MockFetchApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockFetchApi.esm.js","sources":["../../../src/apis/FetchApi/MockFetchApi.ts"],"sourcesContent":["/*\n * Copyright 2022 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 createFetchApi,\n FetchMiddleware,\n FetchMiddlewares,\n} from '@backstage/core-app-api';\nimport {\n DiscoveryApi,\n FetchApi,\n IdentityApi,\n} from '@backstage/core-plugin-api';\n/**\n * The options given when constructing a {@link MockFetchApi}.\n *\n * @public\n */\nexport interface MockFetchApiOptions {\n /**\n * Define the underlying base `fetch` implementation.\n *\n * @defaultValue undefined\n * @remarks\n *\n * Leaving out this parameter or passing `undefined`, makes the API use the\n * global `fetch` implementation to make real network requests.\n *\n * `'none'` swallows all calls and makes no requests at all.\n *\n * You can also pass in any `fetch` compatible callback, such as a\n * `jest.fn()`, if you want to use a custom implementation or to just track\n * and assert on calls.\n */\n baseImplementation?: undefined | 'none' | typeof fetch;\n\n /**\n * Add translation from `plugin://` URLs to concrete http(s) URLs, basically\n * simulating what\n * {@link @backstage/core-app-api#FetchMiddlewares.resolvePluginProtocol}\n * does.\n *\n * @defaultValue undefined\n * @remarks\n *\n * Leaving out this parameter or passing `undefined`, disables plugin protocol\n * translation.\n *\n * To enable the feature, pass in a discovery API which is then used to\n * resolve the URLs.\n */\n resolvePluginProtocol?:\n | undefined\n | { discoveryApi: Pick<DiscoveryApi, 'getBaseUrl'> };\n\n /**\n * Add token based Authorization headers to requests, basically simulating\n * what {@link @backstage/core-app-api#FetchMiddlewares.injectIdentityAuth}\n * does.\n *\n * @defaultValue undefined\n * @remarks\n *\n * Leaving out this parameter or passing `undefined`, disables auth injection.\n *\n * To enable the feature, pass in either a static token or an identity API\n * which is queried on each request for a token.\n */\n injectIdentityAuth?:\n | undefined\n | { token: string }\n | { identityApi: Pick<IdentityApi, 'getCredentials'> };\n}\n\n/**\n * A test helper implementation of {@link @backstage/core-plugin-api#FetchApi}.\n *\n * @public\n */\nexport class MockFetchApi implements FetchApi {\n private readonly implementation: FetchApi;\n\n /**\n * Creates a mock {@link @backstage/core-plugin-api#FetchApi}.\n */\n constructor(options?: MockFetchApiOptions) {\n this.implementation = build(options);\n }\n\n /** {@inheritdoc @backstage/core-plugin-api#FetchApi.fetch} */\n get fetch(): typeof fetch {\n return this.implementation.fetch;\n }\n}\n\n//\n// Helpers\n//\n\nfunction build(options?: MockFetchApiOptions): FetchApi {\n return createFetchApi({\n baseImplementation: baseImplementation(options),\n middleware: [\n resolvePluginProtocol(options),\n injectIdentityAuth(options),\n ].filter((x): x is FetchMiddleware => Boolean(x)),\n });\n}\n\nfunction baseImplementation(\n options: MockFetchApiOptions | undefined,\n): typeof fetch {\n const implementation = options?.baseImplementation;\n if (!implementation) {\n // Return a wrapper that evaluates global.fetch at call time, not construction time.\n // This allows MSW to patch global.fetch after MockFetchApi is constructed.\n return (input, init) => global.fetch(input, init);\n } else if (implementation === 'none') {\n return () => Promise.resolve(new Response());\n }\n return implementation;\n}\n\nfunction resolvePluginProtocol(\n allOptions: MockFetchApiOptions | undefined,\n): FetchMiddleware | undefined {\n const options = allOptions?.resolvePluginProtocol;\n if (!options) {\n return undefined;\n }\n\n return FetchMiddlewares.resolvePluginProtocol({\n discoveryApi: options.discoveryApi,\n });\n}\n\nfunction injectIdentityAuth(\n allOptions: MockFetchApiOptions | undefined,\n): FetchMiddleware | undefined {\n const options = allOptions?.injectIdentityAuth;\n if (!options) {\n return undefined;\n }\n\n const identityApi: Pick<IdentityApi, 'getCredentials'> =\n 'token' in options\n ? { getCredentials: async () => ({ token: options.token }) }\n : options.identityApi;\n\n return FetchMiddlewares.injectIdentityAuth({\n identityApi: identityApi as IdentityApi,\n allowUrl: () => true,\n });\n}\n"],"names":[],"mappings":";;AA4FO,MAAM,YAAA,CAAiC;AAAA,EAC3B,cAAA;AAAA;AAAA;AAAA;AAAA,EAKjB,YAAY,OAAA,EAA+B;AACzC,IAAA,IAAA,CAAK,cAAA,GAAiB,MAAM,OAAO,CAAA;AAAA,EACrC;AAAA;AAAA,EAGA,IAAI,KAAA,GAAsB;AACxB,IAAA,OAAO,KAAK,cAAA,CAAe,KAAA;AAAA,EAC7B;AACF;AAMA,SAAS,MAAM,OAAA,EAAyC;AACtD,EAAA,OAAO,cAAA,CAAe;AAAA,IACpB,kBAAA,EAAoB,mBAAmB,OAAO,CAAA;AAAA,IAC9C,UAAA,EAAY;AAAA,MACV,sBAAsB,OAAO,CAAA;AAAA,MAC7B,mBAAmB,OAAO;AAAA,MAC1B,MAAA,CAAO,CAAC,CAAA,KAA4B,OAAA,CAAQ,CAAC,CAAC;AAAA,GACjD,CAAA;AACH;AAEA,SAAS,mBACP,OAAA,EACc;AACd,EAAA,MAAM,iBAAiB,OAAA,EAAS,kBAAA;AAChC,EAAA,IAAI,CAAC,cAAA,EAAgB;AAGnB,IAAA,OAAO,CAAC,KAAA,EAAO,IAAA,KAAS,MAAA,CAAO,KAAA,CAAM,OAAO,IAAI,CAAA;AAAA,EAClD,CAAA,MAAA,IAAW,mBAAmB,MAAA,EAAQ;AACpC,IAAA,OAAO,MAAM,OAAA,CAAQ,OAAA,CAAQ,IAAI,UAAU,CAAA;AAAA,EAC7C;AACA,EAAA,OAAO,cAAA;AACT;AAEA,SAAS,sBACP,UAAA,EAC6B;AAC7B,EAAA,MAAM,UAAU,UAAA,EAAY,qBAAA;AAC5B,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,OAAO,iBAAiB,qBAAA,CAAsB;AAAA,IAC5C,cAAc,OAAA,CAAQ;AAAA,GACvB,CAAA;AACH;AAEA,SAAS,mBACP,UAAA,EAC6B;AAC7B,EAAA,MAAM,UAAU,UAAA,EAAY,kBAAA;AAC5B,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,WAAA,GACJ,OAAA,IAAW,OAAA,GACP,EAAE,cAAA,EAAgB,aAAa,EAAE,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAM,CAAA,EAAG,GACzD,OAAA,CAAQ,WAAA;AAEd,EAAA,OAAO,iBAAiB,kBAAA,CAAmB;AAAA,IACzC,WAAA;AAAA,IACA,UAAU,MAAM;AAAA,GACjB,CAAA;AACH;;;;"}
@@ -0,0 +1,28 @@
1
+ const mockApiFactorySymbol = /* @__PURE__ */ Symbol.for("@backstage/mock-api");
2
+ function mockWithApiFactory(apiRef, implementation) {
3
+ const marked = implementation;
4
+ marked[mockApiFactorySymbol] = {
5
+ api: apiRef,
6
+ deps: {},
7
+ factory: () => implementation
8
+ };
9
+ return marked;
10
+ }
11
+ function attachMockApiFactory(apiRef, implementation) {
12
+ const marked = implementation;
13
+ marked[mockApiFactorySymbol] = {
14
+ api: apiRef,
15
+ deps: {},
16
+ factory: () => implementation
17
+ };
18
+ return marked;
19
+ }
20
+ function getMockApiFactory(value) {
21
+ if (typeof value === "object" && value !== null && mockApiFactorySymbol in value && typeof value[mockApiFactorySymbol] === "object") {
22
+ return value[mockApiFactorySymbol];
23
+ }
24
+ return void 0;
25
+ }
26
+
27
+ export { attachMockApiFactory, getMockApiFactory, mockApiFactorySymbol, mockWithApiFactory };
28
+ //# sourceMappingURL=MockWithApiFactory.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockWithApiFactory.esm.js","sources":["../../src/apis/MockWithApiFactory.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 { ApiRef } from '@backstage/frontend-plugin-api';\nimport { ApiFactory } from '@backstage/frontend-plugin-api';\n\n/**\n * Symbol used to mark mock API instances with their corresponding API factory.\n *\n * @ignore\n */\nexport const mockApiFactorySymbol = Symbol.for('@backstage/mock-api');\n\n/**\n * Symbol used to mark mock API instances with their corresponding API factory.\n * This allows mock APIs to be passed directly to test utilities without\n * needing to explicitly provide the [apiRef, implementation] tuple.\n *\n * @public\n */\nexport type MockApiFactorySymbol = typeof mockApiFactorySymbol;\n\n/**\n * Type for an API instance that has been marked as a mock API.\n *\n * @public\n */\nexport type MockWithApiFactory<TApi> = TApi & {\n [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;\n};\n\n/**\n * Helper to attach mock API metadata to an instance.\n *\n * @internal\n */\nexport function mockWithApiFactory<TApi, TImpl extends TApi = TApi>(\n apiRef: ApiRef<TApi>,\n implementation: TImpl,\n): TImpl & { [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}> } {\n const marked = implementation as TImpl & {\n [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;\n };\n (marked as any)[mockApiFactorySymbol] = {\n api: apiRef,\n deps: {},\n factory: () => implementation,\n };\n return marked;\n}\n\n/**\n * Attaches mock API factory metadata to an API instance, allowing it to be\n * passed directly to test utilities without needing to explicitly provide\n * the [apiRef, implementation] tuple.\n *\n * @public\n * @example\n * ```ts\n * const catalogApi = attachMockApiFactory(\n * catalogApiRef,\n * new InMemoryCatalogClient()\n * );\n * // Can now be passed directly to TestApiProvider\n * <TestApiProvider apis={[catalogApi]}>\n * ```\n */\nexport function attachMockApiFactory<TApi, TImpl extends TApi = TApi>(\n apiRef: ApiRef<TApi>,\n implementation: TImpl,\n): TImpl & { [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}> } {\n const marked = implementation as TImpl & {\n [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;\n };\n (marked as any)[mockApiFactorySymbol] = {\n api: apiRef,\n deps: {},\n factory: () => implementation,\n };\n return marked;\n}\n\n/**\n * Retrieves the API factory from a mock API instance.\n * Returns undefined if the value is not a mock API instance.\n *\n * @internal\n */\nexport function getMockApiFactory(\n value: unknown,\n): ApiFactory<any, any, {}> | undefined {\n if (\n typeof value === 'object' &&\n value !== null &&\n mockApiFactorySymbol in value &&\n typeof (value as any)[mockApiFactorySymbol] === 'object'\n ) {\n return (value as any)[mockApiFactorySymbol];\n }\n return undefined;\n}\n"],"names":[],"mappings":"AAwBO,MAAM,oBAAA,mBAAuB,MAAA,CAAO,GAAA,CAAI,qBAAqB;AAyB7D,SAAS,kBAAA,CACd,QACA,cAAA,EACgE;AAChE,EAAA,MAAM,MAAA,GAAS,cAAA;AAGf,EAAC,MAAA,CAAe,oBAAoB,CAAA,GAAI;AAAA,IACtC,GAAA,EAAK,MAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,SAAS,MAAM;AAAA,GACjB;AACA,EAAA,OAAO,MAAA;AACT;AAkBO,SAAS,oBAAA,CACd,QACA,cAAA,EACgE;AAChE,EAAA,MAAM,MAAA,GAAS,cAAA;AAGf,EAAC,MAAA,CAAe,oBAAoB,CAAA,GAAI;AAAA,IACtC,GAAA,EAAK,MAAA;AAAA,IACL,MAAM,EAAC;AAAA,IACP,SAAS,MAAM;AAAA,GACjB;AACA,EAAA,OAAO,MAAA;AACT;AAQO,SAAS,kBACd,KAAA,EACsC;AACtC,EAAA,IACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,oBAAA,IAAwB,KAAA,IACxB,OAAQ,KAAA,CAAc,oBAAoB,CAAA,KAAM,QAAA,EAChD;AACA,IAAA,OAAQ,MAAc,oBAAoB,CAAA;AAAA,EAC5C;AACA,EAAA,OAAO,MAAA;AACT;;;;"}
@@ -0,0 +1,13 @@
1
+ import { AuthorizeResult } from '@backstage/plugin-permission-common';
2
+
3
+ class MockPermissionApi {
4
+ constructor(requestHandler = () => AuthorizeResult.ALLOW) {
5
+ this.requestHandler = requestHandler;
6
+ }
7
+ async authorize(request) {
8
+ return { result: this.requestHandler(request) };
9
+ }
10
+ }
11
+
12
+ export { MockPermissionApi };
13
+ //# sourceMappingURL=MockPermissionApi.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MockPermissionApi.esm.js","sources":["../../../src/apis/PermissionApi/MockPermissionApi.ts"],"sourcesContent":["/*\n * Copyright 2022 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 { PermissionApi } from '@backstage/plugin-permission-react';\nimport {\n EvaluatePermissionResponse,\n EvaluatePermissionRequest,\n AuthorizeResult,\n} from '@backstage/plugin-permission-common';\n\n/**\n * Mock implementation of\n * {@link @backstage/plugin-permission-react#PermissionApi}. Supply a\n * requestHandler function to override the mock result returned for a given\n * request.\n *\n * @public\n */\nexport class MockPermissionApi implements PermissionApi {\n constructor(\n private readonly requestHandler: (\n request: EvaluatePermissionRequest,\n ) => AuthorizeResult.ALLOW | AuthorizeResult.DENY = () =>\n AuthorizeResult.ALLOW,\n ) {}\n\n async authorize(\n request: EvaluatePermissionRequest,\n ): Promise<EvaluatePermissionResponse> {\n return { result: this.requestHandler(request) };\n }\n}\n"],"names":[],"mappings":";;AA+BO,MAAM,iBAAA,CAA2C;AAAA,EACtD,WAAA,CACmB,cAAA,GAEmC,MAClD,eAAA,CAAgB,KAAA,EAClB;AAJiB,IAAA,IAAA,CAAA,cAAA,GAAA,cAAA;AAAA,EAIhB;AAAA,EAEH,MAAM,UACJ,OAAA,EACqC;AACrC,IAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,CAAK,cAAA,CAAe,OAAO,CAAA,EAAE;AAAA,EAChD;AACF;;;;"}
@@ -0,0 +1,98 @@
1
+ import ObservableImpl from 'zen-observable';
2
+
3
+ class MockStorageApi {
4
+ namespace;
5
+ data;
6
+ bucketStorageApis;
7
+ constructor(namespace, bucketStorageApis, data) {
8
+ this.namespace = namespace;
9
+ this.bucketStorageApis = bucketStorageApis;
10
+ this.data = { ...data };
11
+ }
12
+ static create(data) {
13
+ const keyValues = {};
14
+ function put(value, namespace) {
15
+ for (const [key, val] of Object.entries(value)) {
16
+ if (typeof val === "object" && val !== null) {
17
+ put(val, `${namespace}/${key}`);
18
+ } else {
19
+ const namespacedKey = `${namespace}/${key.replace(/^\//, "")}`;
20
+ keyValues[namespacedKey] = val;
21
+ }
22
+ }
23
+ }
24
+ put(data ?? {}, "");
25
+ return new MockStorageApi("", /* @__PURE__ */ new Map(), keyValues);
26
+ }
27
+ forBucket(name) {
28
+ if (!this.bucketStorageApis.has(name)) {
29
+ this.bucketStorageApis.set(
30
+ name,
31
+ new MockStorageApi(
32
+ `${this.namespace}/${name}`,
33
+ this.bucketStorageApis,
34
+ this.data
35
+ )
36
+ );
37
+ }
38
+ return this.bucketStorageApis.get(name);
39
+ }
40
+ snapshot(key) {
41
+ if (this.data.hasOwnProperty(this.getKeyName(key))) {
42
+ const data = this.data[this.getKeyName(key)];
43
+ return {
44
+ key,
45
+ presence: "present",
46
+ value: data
47
+ };
48
+ }
49
+ return {
50
+ key,
51
+ presence: "absent",
52
+ value: void 0
53
+ };
54
+ }
55
+ async set(key, data) {
56
+ const serialized = JSON.parse(JSON.stringify(data), (_key, value) => {
57
+ if (typeof value === "object" && value !== null) {
58
+ Object.freeze(value);
59
+ }
60
+ return value;
61
+ });
62
+ this.data[this.getKeyName(key)] = serialized;
63
+ this.notifyChanges({
64
+ key,
65
+ presence: "present",
66
+ value: serialized
67
+ });
68
+ }
69
+ async remove(key) {
70
+ delete this.data[this.getKeyName(key)];
71
+ this.notifyChanges({
72
+ key,
73
+ presence: "absent",
74
+ value: void 0
75
+ });
76
+ }
77
+ observe$(key) {
78
+ return this.observable.filter(({ key: messageKey }) => messageKey === key);
79
+ }
80
+ getKeyName(key) {
81
+ return `${this.namespace}/${encodeURIComponent(key)}`;
82
+ }
83
+ notifyChanges(message) {
84
+ for (const subscription of this.subscribers) {
85
+ subscription.next(message);
86
+ }
87
+ }
88
+ subscribers = /* @__PURE__ */ new Set();
89
+ observable = new ObservableImpl((subscriber) => {
90
+ this.subscribers.add(subscriber);
91
+ return () => {
92
+ this.subscribers.delete(subscriber);
93
+ };
94
+ });
95
+ }
96
+
97
+ export { MockStorageApi };
98
+ //# sourceMappingURL=MockStorageApi.esm.js.map
@@ -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;;;;"}