@backstage/frontend-test-utils 0.6.3-next.0 → 0.6.3

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # @backstage/frontend-test-utils
2
2
 
3
+ ## 0.6.3
4
+
5
+ ### Patch Changes
6
+
7
+ - bd435c3: Identity mocks passed to `renderInTestApp` (for example via `mockApis.identity(...)`) are now applied before the app's built-in guest fallback, so the configured `userEntityRef` reliably takes effect in tests instead of being silently overwritten by the default guest user.
8
+ - Updated dependencies
9
+ - @backstage/core-plugin-api@1.12.9
10
+ - @backstage/core-app-api@1.20.4
11
+ - @backstage/test-utils@1.7.21
12
+ - @backstage/plugin-permission-common@0.9.10
13
+ - @backstage/frontend-plugin-api@0.18.0
14
+ - @backstage/plugin-app@0.5.2
15
+ - @backstage/frontend-app-api@0.16.7
16
+ - @backstage/plugin-app-react@0.2.6
17
+ - @backstage/plugin-permission-react@0.5.4
18
+
19
+ ## 0.6.3-next.1
20
+
21
+ ### Patch Changes
22
+
23
+ - Updated dependencies
24
+ - @backstage/plugin-app@0.5.2-next.2
25
+ - @backstage/frontend-plugin-api@0.18.0-next.0
26
+ - @backstage/frontend-app-api@0.16.7-next.1
27
+ - @backstage/plugin-app-react@0.2.6-next.0
28
+ - @backstage/core-plugin-api@1.12.9-next.0
29
+ - @backstage/core-app-api@1.20.4-next.1
30
+ - @backstage/test-utils@1.7.21-next.1
31
+ - @backstage/plugin-permission-react@0.5.4-next.0
32
+
3
33
  ## 0.6.3-next.0
4
34
 
5
35
  ### Patch Changes
@@ -4,10 +4,11 @@ import { MemoryRouter, Routes, Route } from 'react-router-dom';
4
4
  import { prepareSpecializedApp } from '@backstage/frontend-app-api';
5
5
  import { render } from '@testing-library/react';
6
6
  import { ConfigReader } from '@backstage/config';
7
- import { createExtension, coreExtensionData, createRouteRef, createFrontendModule, createFrontendPlugin, createApiFactory } from '@backstage/frontend-plugin-api';
7
+ import { createExtension, coreExtensionData, createRouteRef, createApiFactory, identityApiRef, createFrontendModule, createFrontendPlugin } from '@backstage/frontend-plugin-api';
8
8
  import { RouterBlueprint } from '@backstage/plugin-app-react';
9
9
  import appPlugin from '@backstage/plugin-app';
10
10
  import { getMockApiFactory } from '../apis/MockWithApiFactory.esm.js';
11
+ import { getBasePath } from '../frontend-app-api/src/routing/getBasePath.esm.js';
11
12
  import { OpaqueExternalRouteRef } from '../frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js';
12
13
  import '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
13
14
  import '../frontend-internal/src/routing/OpaqueSubRouteRef.esm.js';
@@ -77,6 +78,12 @@ function renderInTestApp(element, options) {
77
78
  );
78
79
  }
79
80
  }
81
+ const apiFactoryOverrides = (options?.apis ?? []).map(
82
+ (entry) => getMockApiFactory(entry) ?? createApiFactory(...entry)
83
+ );
84
+ const identityOverrideFactory = apiFactoryOverrides.find(
85
+ (factory) => factory.api.id === identityApiRef.id
86
+ );
80
87
  const features = [
81
88
  createFrontendModule({
82
89
  pluginId: "app",
@@ -107,23 +114,19 @@ function renderInTestApp(element, options) {
107
114
  if (options?.features) {
108
115
  features.push(...options.features);
109
116
  }
117
+ const config = ConfigReader.fromConfigs([
118
+ {
119
+ context: "render-config",
120
+ data: options?.config ?? DEFAULT_MOCK_CONFIG
121
+ }
122
+ ]);
110
123
  const app = prepareSpecializedApp({
111
124
  features,
112
- config: ConfigReader.fromConfigs([
113
- {
114
- context: "render-config",
115
- data: options?.config ?? DEFAULT_MOCK_CONFIG
116
- }
117
- ]),
125
+ config,
118
126
  __internal: options?.apis && {
119
- apiFactoryOverrides: options.apis.map((entry) => {
120
- const mockFactory = getMockApiFactory(entry);
121
- if (mockFactory) {
122
- return mockFactory;
123
- }
124
- const [apiRef, implementation] = entry;
125
- return createApiFactory(apiRef, implementation);
126
- })
127
+ apiFactoryOverrides: apiFactoryOverrides.filter(
128
+ (factory) => factory.api.id !== identityApiRef.id
129
+ )
127
130
  },
128
131
  bindRoutes: externalBindings.size > 0 ? ({ bind }) => {
129
132
  for (const [externalRef, targetRef] of externalBindings) {
@@ -131,6 +134,12 @@ function renderInTestApp(element, options) {
131
134
  }
132
135
  } : void 0
133
136
  }).finalize();
137
+ if (identityOverrideFactory) {
138
+ const proxy = app.apis.get(identityApiRef);
139
+ proxy?.setTarget?.(identityOverrideFactory.factory({}), {
140
+ signOutTargetUrl: getBasePath(config) || "/"
141
+ });
142
+ }
134
143
  return render(
135
144
  app.tree.root.instance.getData(coreExtensionData.reactElement)
136
145
  );
@@ -1 +1 @@
1
- {"version":3,"file":"renderInTestApp.esm.js","sources":["../../src/app/renderInTestApp.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 { Fragment } from 'react';\nimport { MemoryRouter, Route, Routes } from 'react-router-dom';\nimport { prepareSpecializedApp } from '@backstage/frontend-app-api';\nimport { RenderResult, render } from '@testing-library/react';\nimport { ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport {\n createExtension,\n ExtensionDefinition,\n coreExtensionData,\n RouteRef,\n createFrontendPlugin,\n FrontendFeature,\n createFrontendModule,\n createApiFactory,\n createRouteRef,\n ExternalRouteRef,\n type ApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouterBlueprint } from '@backstage/plugin-app-react';\nimport appPlugin from '@backstage/plugin-app';\nimport { getMockApiFactory } from '../apis/MockWithApiFactory';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp';\nimport { TestApiPairs } from '../apis/TestApiProvider';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\nconst DEFAULT_MOCK_CONFIG = {\n app: { baseUrl: 'http://localhost:3000' },\n backend: { baseUrl: 'http://localhost:7007' },\n};\n\n/**\n * Options to customize the behavior of the test app.\n * @public\n */\nexport type TestAppOptions<TApiPairs extends any[] = any[]> = {\n /**\n * An object of paths to mount route ref on, with the key being the path and the value\n * being the route ref that the path will be bound to. This allows the route refs to be\n * used by `useRouteRef` in the rendered elements.\n *\n * @example\n * ```ts\n * renderInTestApp(<MyComponent />, {\n * mountedRoutes: {\n * '/my-path': myRouteRef,\n * }\n * })\n * // ...\n * const link = useRouteRef(myRouteRef)\n * ```\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n\n /**\n * Additional configuration passed to the app when rendering elements inside it.\n */\n config?: JsonObject;\n\n /**\n * Additional features to add to the test app.\n */\n features?: FrontendFeature[];\n\n /**\n * The route path pattern that the test element is rendered at. When set,\n * the element is wrapped in a `<Route>` with this path, enabling\n * `useParams()` to extract parameters from the URL.\n *\n * Should be used together with `initialRouteEntries` to set a concrete\n * URL that matches the pattern.\n *\n * @example\n * ```ts\n * renderInTestApp(<EntityPage />, {\n * mountPath: '/catalog/:namespace/:kind/:name',\n * initialRouteEntries: ['/catalog/default/component/my-entity'],\n * })\n * ```\n */\n mountPath?: string;\n\n /**\n * Initial route entries to use for the router.\n */\n initialRouteEntries?: string[];\n\n /**\n * API overrides to provide to the test app. Use `mockApis` helpers\n * from `@backstage/frontend-test-utils` to create mock implementations.\n *\n * @example\n * ```ts\n * import { mockApis } from '@backstage/frontend-test-utils';\n *\n * renderInTestApp(<MyComponent />, {\n * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })],\n * })\n * ```\n */\n apis?: readonly [...TestApiPairs<TApiPairs>];\n};\n\nconst appPluginOverride = appPlugin.withOverrides({\n extensions: [\n appPlugin.getExtension('sign-in-page:app').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/layout').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/routes').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/nav').override({\n disabled: true,\n }),\n ],\n});\n\n/**\n * @public\n * Renders the given element in a test app, for use in unit tests.\n */\nexport function renderInTestApp<const TApiPairs extends any[] = any[]>(\n element: JSX.Element,\n options?: TestAppOptions<TApiPairs>,\n): RenderResult {\n const mountPath = options?.mountPath;\n\n const extensions: Array<ExtensionDefinition> = [\n createExtension({\n attachTo: { id: 'app/root', input: 'children' },\n output: [coreExtensionData.reactElement],\n factory: () => {\n let content: JSX.Element = element;\n\n if (mountPath) {\n const routePath =\n mountPath === '/' || mountPath.endsWith('/*')\n ? mountPath\n : `${mountPath.replace(/\\/$/, '')}/*`;\n content = (\n <Routes>\n <Route path={routePath} element={content} />\n </Routes>\n );\n }\n\n return [coreExtensionData.reactElement(content)];\n },\n }),\n ];\n\n const externalBindings = new Map<ExternalRouteRef, RouteRef>();\n\n if (options?.mountedRoutes) {\n for (const [path, optionRef] of Object.entries(options.mountedRoutes)) {\n let routeRef: RouteRef;\n\n if (OpaqueExternalRouteRef.isType(optionRef)) {\n // Create an actual route ref for the external route, then bind the external ref to it\n routeRef = createRouteRef();\n externalBindings.set(optionRef, routeRef);\n } else {\n routeRef = optionRef;\n }\n\n extensions.push(\n createExtension({\n kind: 'test-route',\n name: path,\n attachTo: { id: 'app/root', input: 'elements' },\n output: [\n coreExtensionData.reactElement,\n coreExtensionData.routePath,\n coreExtensionData.routeRef,\n ],\n factory: () => [\n coreExtensionData.reactElement(<Fragment />),\n coreExtensionData.routePath(path),\n coreExtensionData.routeRef(routeRef),\n ],\n }),\n );\n }\n }\n\n const features: FrontendFeature[] = [\n createFrontendModule({\n pluginId: 'app',\n extensions: [\n RouterBlueprint.make({\n params: {\n component: ({ children }) => (\n <MemoryRouter\n initialEntries={options?.initialRouteEntries}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {children}\n </MemoryRouter>\n ),\n },\n }),\n ],\n }),\n createFrontendPlugin({\n pluginId: 'test',\n extensions,\n }),\n appPluginOverride,\n ];\n\n if (options?.features) {\n features.push(...options.features);\n }\n\n const app = prepareSpecializedApp({\n features,\n config: ConfigReader.fromConfigs([\n {\n context: 'render-config',\n data: options?.config ?? DEFAULT_MOCK_CONFIG,\n },\n ]),\n __internal: options?.apis && {\n apiFactoryOverrides: options.apis.map(entry => {\n const mockFactory = getMockApiFactory(entry);\n if (mockFactory) {\n return mockFactory;\n }\n const [apiRef, implementation] = entry as readonly [ApiRef<any>, any];\n return createApiFactory(apiRef, implementation);\n }),\n },\n bindRoutes:\n externalBindings.size > 0\n ? ({ bind }) => {\n for (const [externalRef, targetRef] of externalBindings) {\n bind({ ref: externalRef }, { ref: targetRef });\n }\n }\n : undefined,\n } as CreateSpecializedAppInternalOptions).finalize();\n\n return render(\n app.tree.root.instance!.getData(coreExtensionData.reactElement),\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renderInTestApp.esm.js","sources":["../../src/app/renderInTestApp.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 { Fragment } from 'react';\nimport { MemoryRouter, Route, Routes } from 'react-router-dom';\nimport { prepareSpecializedApp } from '@backstage/frontend-app-api';\nimport { RenderResult, render } from '@testing-library/react';\nimport { ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport {\n createExtension,\n ExtensionDefinition,\n coreExtensionData,\n RouteRef,\n createFrontendPlugin,\n FrontendFeature,\n createFrontendModule,\n createApiFactory,\n createRouteRef,\n ExternalRouteRef,\n identityApiRef,\n type ApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { RouterBlueprint } from '@backstage/plugin-app-react';\nimport appPlugin from '@backstage/plugin-app';\nimport { getMockApiFactory } from '../apis/MockWithApiFactory';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../frontend-app-api/src/routing/getBasePath';\nimport { TestApiPairs } from '../apis/TestApiProvider';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\nconst DEFAULT_MOCK_CONFIG = {\n app: { baseUrl: 'http://localhost:3000' },\n backend: { baseUrl: 'http://localhost:7007' },\n};\n\n/**\n * Options to customize the behavior of the test app.\n * @public\n */\nexport type TestAppOptions<TApiPairs extends any[] = any[]> = {\n /**\n * An object of paths to mount route ref on, with the key being the path and the value\n * being the route ref that the path will be bound to. This allows the route refs to be\n * used by `useRouteRef` in the rendered elements.\n *\n * @example\n * ```ts\n * renderInTestApp(<MyComponent />, {\n * mountedRoutes: {\n * '/my-path': myRouteRef,\n * }\n * })\n * // ...\n * const link = useRouteRef(myRouteRef)\n * ```\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n\n /**\n * Additional configuration passed to the app when rendering elements inside it.\n */\n config?: JsonObject;\n\n /**\n * Additional features to add to the test app.\n */\n features?: FrontendFeature[];\n\n /**\n * The route path pattern that the test element is rendered at. When set,\n * the element is wrapped in a `<Route>` with this path, enabling\n * `useParams()` to extract parameters from the URL.\n *\n * Should be used together with `initialRouteEntries` to set a concrete\n * URL that matches the pattern.\n *\n * @example\n * ```ts\n * renderInTestApp(<EntityPage />, {\n * mountPath: '/catalog/:namespace/:kind/:name',\n * initialRouteEntries: ['/catalog/default/component/my-entity'],\n * })\n * ```\n */\n mountPath?: string;\n\n /**\n * Initial route entries to use for the router.\n */\n initialRouteEntries?: string[];\n\n /**\n * API overrides to provide to the test app. Use `mockApis` helpers\n * from `@backstage/frontend-test-utils` to create mock implementations.\n *\n * @example\n * ```ts\n * import { mockApis } from '@backstage/frontend-test-utils';\n *\n * renderInTestApp(<MyComponent />, {\n * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })],\n * })\n * ```\n */\n apis?: readonly [...TestApiPairs<TApiPairs>];\n};\n\nconst appPluginOverride = appPlugin.withOverrides({\n extensions: [\n appPlugin.getExtension('sign-in-page:app').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/layout').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/routes').override({\n disabled: true,\n }),\n appPlugin.getExtension('app/nav').override({\n disabled: true,\n }),\n ],\n});\n\n/**\n * @public\n * Renders the given element in a test app, for use in unit tests.\n */\nexport function renderInTestApp<const TApiPairs extends any[] = any[]>(\n element: JSX.Element,\n options?: TestAppOptions<TApiPairs>,\n): RenderResult {\n const mountPath = options?.mountPath;\n\n const extensions: Array<ExtensionDefinition> = [\n createExtension({\n attachTo: { id: 'app/root', input: 'children' },\n output: [coreExtensionData.reactElement],\n factory: () => {\n let content: JSX.Element = element;\n\n if (mountPath) {\n const routePath =\n mountPath === '/' || mountPath.endsWith('/*')\n ? mountPath\n : `${mountPath.replace(/\\/$/, '')}/*`;\n content = (\n <Routes>\n <Route path={routePath} element={content} />\n </Routes>\n );\n }\n\n return [coreExtensionData.reactElement(content)];\n },\n }),\n ];\n\n const externalBindings = new Map<ExternalRouteRef, RouteRef>();\n\n if (options?.mountedRoutes) {\n for (const [path, optionRef] of Object.entries(options.mountedRoutes)) {\n let routeRef: RouteRef;\n\n if (OpaqueExternalRouteRef.isType(optionRef)) {\n // Create an actual route ref for the external route, then bind the external ref to it\n routeRef = createRouteRef();\n externalBindings.set(optionRef, routeRef);\n } else {\n routeRef = optionRef;\n }\n\n extensions.push(\n createExtension({\n kind: 'test-route',\n name: path,\n attachTo: { id: 'app/root', input: 'elements' },\n output: [\n coreExtensionData.reactElement,\n coreExtensionData.routePath,\n coreExtensionData.routeRef,\n ],\n factory: () => [\n coreExtensionData.reactElement(<Fragment />),\n coreExtensionData.routePath(path),\n coreExtensionData.routeRef(routeRef),\n ],\n }),\n );\n }\n }\n\n const apiFactoryOverrides = (options?.apis ?? []).map(\n entry =>\n getMockApiFactory(entry) ??\n createApiFactory(...(entry as readonly [ApiRef<any>, any])),\n );\n const identityOverrideFactory = apiFactoryOverrides.find(\n factory => factory.api.id === identityApiRef.id,\n );\n\n const features: FrontendFeature[] = [\n createFrontendModule({\n pluginId: 'app',\n extensions: [\n RouterBlueprint.make({\n params: {\n component: ({ children }) => (\n <MemoryRouter\n initialEntries={options?.initialRouteEntries}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {children}\n </MemoryRouter>\n ),\n },\n }),\n ],\n }),\n createFrontendPlugin({\n pluginId: 'test',\n extensions,\n }),\n appPluginOverride,\n ];\n\n if (options?.features) {\n features.push(...options.features);\n }\n\n const config = ConfigReader.fromConfigs([\n {\n context: 'render-config',\n data: options?.config ?? DEFAULT_MOCK_CONFIG,\n },\n ]);\n\n const app = prepareSpecializedApp({\n features,\n config,\n __internal: options?.apis && {\n apiFactoryOverrides: apiFactoryOverrides.filter(\n factory => factory.api.id !== identityApiRef.id,\n ),\n },\n bindRoutes:\n externalBindings.size > 0\n ? ({ bind }) => {\n for (const [externalRef, targetRef] of externalBindings) {\n bind({ ref: externalRef }, { ref: targetRef });\n }\n }\n : undefined,\n } as CreateSpecializedAppInternalOptions).finalize();\n\n if (identityOverrideFactory) {\n // identityApiRef always resolves to the app's internal AppIdentityProxy\n // (AppRouter requires this), so the override can't replace the factory.\n // setTarget is now idempotent (first write wins), so we just need to\n // set it before AppRouter's own guest-identity fallback does, which\n // happens during this same synchronous render call.\n const proxy = app.apis.get(identityApiRef as any) as any;\n proxy?.setTarget?.(identityOverrideFactory.factory({}), {\n signOutTargetUrl: getBasePath(config) || '/',\n });\n }\n\n return render(\n app.tree.root.instance!.getData(coreExtensionData.reactElement),\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,14 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { Fragment } from 'react';
3
3
  import { prepareSpecializedApp } from '@backstage/frontend-app-api';
4
- import { createRouteRef, createExtension, coreExtensionData, createFrontendModule, createFrontendPlugin, createApiFactory } from '@backstage/frontend-plugin-api';
4
+ import { createRouteRef, createExtension, coreExtensionData, createApiFactory, identityApiRef, createFrontendModule, createFrontendPlugin } from '@backstage/frontend-plugin-api';
5
5
  import { render } from '@testing-library/react';
6
6
  import appPlugin from '@backstage/plugin-app';
7
7
  import { ConfigReader } from '@backstage/config';
8
8
  import { MemoryRouter } from 'react-router-dom';
9
9
  import { RouterBlueprint } from '@backstage/plugin-app-react';
10
10
  import { getMockApiFactory } from '../apis/MockWithApiFactory.esm.js';
11
+ import { getBasePath } from '../frontend-app-api/src/routing/getBasePath.esm.js';
11
12
  import { OpaqueExternalRouteRef } from '../frontend-internal/src/routing/OpaqueExternalRouteRef.esm.js';
12
13
  import '../frontend-internal/src/routing/OpaqueRouteRef.esm.js';
13
14
  import '../frontend-internal/src/routing/OpaqueSubRouteRef.esm.js';
@@ -54,6 +55,17 @@ function renderTestApp(options) {
54
55
  );
55
56
  }
56
57
  }
58
+ const apiFactoryOverrides = (options?.apis ?? []).map((entry) => {
59
+ const mockFactory = getMockApiFactory(entry);
60
+ if (mockFactory) {
61
+ return mockFactory;
62
+ }
63
+ const [apiRef, implementation] = entry;
64
+ return createApiFactory(apiRef, implementation);
65
+ });
66
+ const identityOverrideFactory = apiFactoryOverrides.find(
67
+ (factory) => factory.api.id === identityApiRef.id
68
+ );
57
69
  const features = [
58
70
  createFrontendModule({
59
71
  pluginId: "app",
@@ -84,23 +96,19 @@ function renderTestApp(options) {
84
96
  if (options?.features) {
85
97
  features.push(...options.features);
86
98
  }
99
+ const config = ConfigReader.fromConfigs([
100
+ {
101
+ context: "render-config",
102
+ data: options?.config ?? DEFAULT_MOCK_CONFIG
103
+ }
104
+ ]);
87
105
  const app = prepareSpecializedApp({
88
106
  features,
89
- config: ConfigReader.fromConfigs([
90
- {
91
- context: "render-config",
92
- data: options?.config ?? DEFAULT_MOCK_CONFIG
93
- }
94
- ]),
107
+ config,
95
108
  __internal: options?.apis && {
96
- apiFactoryOverrides: options.apis.map((entry) => {
97
- const mockFactory = getMockApiFactory(entry);
98
- if (mockFactory) {
99
- return mockFactory;
100
- }
101
- const [apiRef, implementation] = entry;
102
- return createApiFactory(apiRef, implementation);
103
- })
109
+ apiFactoryOverrides: apiFactoryOverrides.filter(
110
+ (factory) => factory.api.id !== identityApiRef.id
111
+ )
104
112
  },
105
113
  bindRoutes: externalBindings.size > 0 ? ({ bind }) => {
106
114
  for (const [externalRef, targetRef] of externalBindings) {
@@ -108,6 +116,12 @@ function renderTestApp(options) {
108
116
  }
109
117
  } : void 0
110
118
  }).finalize();
119
+ if (identityOverrideFactory) {
120
+ const proxy = app.apis.get(identityApiRef);
121
+ proxy?.setTarget?.(identityOverrideFactory.factory({}), {
122
+ signOutTargetUrl: getBasePath(config) || "/"
123
+ });
124
+ }
111
125
  return render(
112
126
  app.tree.root.instance.getData(coreExtensionData.reactElement)
113
127
  );
@@ -1 +1 @@
1
- {"version":3,"file":"renderTestApp.esm.js","sources":["../../src/app/renderTestApp.tsx"],"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 { Fragment } from 'react';\nimport { prepareSpecializedApp } from '@backstage/frontend-app-api';\nimport {\n coreExtensionData,\n createApiFactory,\n createExtension,\n createFrontendModule,\n createFrontendPlugin,\n ExtensionDefinition,\n FrontendFeature,\n RouteRef,\n ExternalRouteRef,\n createRouteRef,\n type ApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { render, type RenderResult } from '@testing-library/react';\nimport appPlugin from '@backstage/plugin-app';\nimport { JsonObject } from '@backstage/types';\nimport { ConfigReader } from '@backstage/config';\nimport { MemoryRouter } from 'react-router-dom';\nimport { RouterBlueprint } from '@backstage/plugin-app-react';\nimport { getMockApiFactory } from '../apis/MockWithApiFactory';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp';\nimport { TestApiPairs } from '../apis/TestApiProvider';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\nconst DEFAULT_MOCK_CONFIG = {\n app: { baseUrl: 'http://localhost:3000' },\n backend: { baseUrl: 'http://localhost:7007' },\n};\n\n/**\n * Options for `renderTestApp`.\n *\n * @public\n */\nexport type RenderTestAppOptions<TApiPairs extends any[] = any[]> = {\n /**\n * Additional configuration passed to the app when rendering elements inside it.\n */\n config?: JsonObject;\n /**\n * Additional extensions to add to the test app.\n */\n extensions?: ExtensionDefinition<any>[];\n\n /**\n * Additional features to add to the test app.\n */\n features?: FrontendFeature[];\n\n /**\n * Initial route entries to use for the router.\n */\n initialRouteEntries?: string[];\n\n /**\n * An object of paths to mount route refs on, with the key being the path and\n * the value being the route ref that the path will be bound to. This allows\n * the route refs to be used by `useRouteRef` in the rendered elements.\n *\n * @example\n * ```ts\n * renderTestApp({\n * mountedRoutes: {\n * '/my-path': myRouteRef,\n * },\n * extensions: [...],\n * })\n * ```\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n\n /**\n * API overrides to provide to the test app. Use `mockApis` helpers\n * from `@backstage/frontend-test-utils` to create mock implementations.\n *\n * @example\n * ```ts\n * import { mockApis } from '@backstage/frontend-test-utils';\n *\n * renderTestApp({\n * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })],\n * extensions: [...],\n * })\n * ```\n */\n apis?: readonly [...TestApiPairs<TApiPairs>];\n};\n\nconst appPluginOverride = appPlugin.withOverrides({\n extensions: [\n appPlugin.getExtension('sign-in-page:app').override({\n disabled: true,\n }),\n ],\n});\n\n/**\n * Renders the provided extensions inside a Backstage app, returning the same\n * utilities as `@testing-library/react` `render` function.\n *\n * @public\n */\nexport function renderTestApp<const TApiPairs extends any[] = any[]>(\n options?: RenderTestAppOptions<TApiPairs>,\n): RenderResult {\n const extensions = [...(options?.extensions ?? [])];\n\n const externalBindings = new Map<ExternalRouteRef, RouteRef>();\n\n if (options?.mountedRoutes) {\n for (const [path, optionRef] of Object.entries(options.mountedRoutes)) {\n let routeRef: RouteRef;\n\n if (OpaqueExternalRouteRef.isType(optionRef)) {\n // Create an actual route ref for the external route, then bind the external ref to it\n routeRef = createRouteRef();\n externalBindings.set(optionRef, routeRef);\n } else {\n routeRef = optionRef;\n }\n\n extensions.push(\n createExtension({\n kind: 'test-route',\n name: path,\n attachTo: { id: 'app/routes', input: 'routes' },\n output: [\n coreExtensionData.reactElement,\n coreExtensionData.routePath,\n coreExtensionData.routeRef,\n ],\n factory: () => [\n coreExtensionData.reactElement(<Fragment />),\n coreExtensionData.routePath(path),\n coreExtensionData.routeRef(routeRef),\n ],\n }),\n );\n }\n }\n\n const features: FrontendFeature[] = [\n createFrontendModule({\n pluginId: 'app',\n extensions: [\n RouterBlueprint.make({\n params: {\n component: ({ children }) => (\n <MemoryRouter\n initialEntries={options?.initialRouteEntries}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {children}\n </MemoryRouter>\n ),\n },\n }),\n ],\n }),\n createFrontendPlugin({\n pluginId: 'test',\n extensions,\n }),\n appPluginOverride,\n ];\n\n if (options?.features) {\n features.push(...options.features);\n }\n\n const app = prepareSpecializedApp({\n features,\n config: ConfigReader.fromConfigs([\n {\n context: 'render-config',\n data: options?.config ?? DEFAULT_MOCK_CONFIG,\n },\n ]),\n __internal: options?.apis && {\n apiFactoryOverrides: options.apis.map(entry => {\n const mockFactory = getMockApiFactory(entry);\n if (mockFactory) {\n return mockFactory;\n }\n const [apiRef, implementation] = entry as readonly [ApiRef<any>, any];\n return createApiFactory(apiRef, implementation);\n }),\n },\n bindRoutes:\n externalBindings.size > 0\n ? ({ bind }) => {\n for (const [externalRef, targetRef] of externalBindings) {\n bind({ ref: externalRef }, { ref: targetRef });\n }\n }\n : undefined,\n } as CreateSpecializedAppInternalOptions).finalize();\n\n return render(\n app.tree.root.instance!.getData(coreExtensionData.reactElement),\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AA2CA,MAAM,mBAAA,GAAsB;AAAA,EAC1B,GAAA,EAAK,EAAE,OAAA,EAAS,uBAAA,EAAwB;AAAA,EACxC,OAAA,EAAS,EAAE,OAAA,EAAS,uBAAA;AACtB,CAAA;AA6DA,MAAM,iBAAA,GAAoB,UAAU,aAAA,CAAc;AAAA,EAChD,UAAA,EAAY;AAAA,IACV,SAAA,CAAU,YAAA,CAAa,kBAAkB,CAAA,CAAE,QAAA,CAAS;AAAA,MAClD,QAAA,EAAU;AAAA,KACX;AAAA;AAEL,CAAC,CAAA;AAQM,SAAS,cACd,OAAA,EACc;AACd,EAAA,MAAM,aAAa,CAAC,GAAI,OAAA,EAAS,UAAA,IAAc,EAAG,CAAA;AAElD,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAgC;AAE7D,EAAA,IAAI,SAAS,aAAA,EAAe;AAC1B,IAAA,KAAA,MAAW,CAAC,MAAM,SAAS,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,aAAa,CAAA,EAAG;AACrE,MAAA,IAAI,QAAA;AAEJ,MAAA,IAAI,sBAAA,CAAuB,MAAA,CAAO,SAAS,CAAA,EAAG;AAE5C,QAAA,QAAA,GAAW,cAAA,EAAe;AAC1B,QAAA,gBAAA,CAAiB,GAAA,CAAI,WAAW,QAAQ,CAAA;AAAA,MAC1C,CAAA,MAAO;AACL,QAAA,QAAA,GAAW,SAAA;AAAA,MACb;AAEA,MAAA,UAAA,CAAW,IAAA;AAAA,QACT,eAAA,CAAgB;AAAA,UACd,IAAA,EAAM,YAAA;AAAA,UACN,IAAA,EAAM,IAAA;AAAA,UACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,QAAA,EAAS;AAAA,UAC9C,MAAA,EAAQ;AAAA,YACN,iBAAA,CAAkB,YAAA;AAAA,YAClB,iBAAA,CAAkB,SAAA;AAAA,YAClB,iBAAA,CAAkB;AAAA,WACpB;AAAA,UACA,SAAS,MAAM;AAAA,YACb,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,QAAA,EAAA,EAAS,CAAE,CAAA;AAAA,YAC3C,iBAAA,CAAkB,UAAU,IAAI,CAAA;AAAA,YAChC,iBAAA,CAAkB,SAAS,QAAQ;AAAA;AACrC,SACD;AAAA,OACH;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,QAAA,GAA8B;AAAA,IAClC,oBAAA,CAAqB;AAAA,MACnB,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY;AAAA,QACV,gBAAgB,IAAA,CAAK;AAAA,UACnB,MAAA,EAAQ;AAAA,YACN,SAAA,EAAW,CAAC,EAAE,QAAA,EAAS,qBACrB,GAAA;AAAA,cAAC,YAAA;AAAA,cAAA;AAAA,gBACC,gBAAgB,OAAA,EAAS,mBAAA;AAAA,gBACzB,MAAA,EAAQ;AAAA,kBACN,oBAAA,EAAsB,KAAA;AAAA,kBACtB,kBAAA,EAAoB;AAAA,iBACtB;AAAA,gBAEC;AAAA;AAAA;AACH;AAEJ,SACD;AAAA;AACH,KACD,CAAA;AAAA,IACD,oBAAA,CAAqB;AAAA,MACnB,QAAA,EAAU,MAAA;AAAA,MACV;AAAA,KACD,CAAA;AAAA,IACD;AAAA,GACF;AAEA,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,QAAA,CAAS,IAAA,CAAK,GAAG,OAAA,CAAQ,QAAQ,CAAA;AAAA,EACnC;AAEA,EAAA,MAAM,MAAM,qBAAA,CAAsB;AAAA,IAChC,QAAA;AAAA,IACA,MAAA,EAAQ,aAAa,WAAA,CAAY;AAAA,MAC/B;AAAA,QACE,OAAA,EAAS,eAAA;AAAA,QACT,IAAA,EAAM,SAAS,MAAA,IAAU;AAAA;AAC3B,KACD,CAAA;AAAA,IACD,UAAA,EAAY,SAAS,IAAA,IAAQ;AAAA,MAC3B,mBAAA,EAAqB,OAAA,CAAQ,IAAA,CAAK,GAAA,CAAI,CAAA,KAAA,KAAS;AAC7C,QAAA,MAAM,WAAA,GAAc,kBAAkB,KAAK,CAAA;AAC3C,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,OAAO,WAAA;AAAA,QACT;AACA,QAAA,MAAM,CAAC,MAAA,EAAQ,cAAc,CAAA,GAAI,KAAA;AACjC,QAAA,OAAO,gBAAA,CAAiB,QAAQ,cAAc,CAAA;AAAA,MAChD,CAAC;AAAA,KACH;AAAA,IACA,YACE,gBAAA,CAAiB,IAAA,GAAO,IACpB,CAAC,EAAE,MAAK,KAAM;AACZ,MAAA,KAAA,MAAW,CAAC,WAAA,EAAa,SAAS,CAAA,IAAK,gBAAA,EAAkB;AACvD,QAAA,IAAA,CAAK,EAAE,GAAA,EAAK,WAAA,IAAe,EAAE,GAAA,EAAK,WAAW,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA,GACA;AAAA,GACgC,EAAE,QAAA,EAAS;AAEnD,EAAA,OAAO,MAAA;AAAA,IACL,IAAI,IAAA,CAAK,IAAA,CAAK,QAAA,CAAU,OAAA,CAAQ,kBAAkB,YAAY;AAAA,GAChE;AACF;;;;"}
1
+ {"version":3,"file":"renderTestApp.esm.js","sources":["../../src/app/renderTestApp.tsx"],"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 { Fragment } from 'react';\nimport { prepareSpecializedApp } from '@backstage/frontend-app-api';\nimport {\n coreExtensionData,\n createApiFactory,\n createExtension,\n createFrontendModule,\n createFrontendPlugin,\n ExtensionDefinition,\n FrontendFeature,\n RouteRef,\n ExternalRouteRef,\n createRouteRef,\n identityApiRef,\n type ApiRef,\n} from '@backstage/frontend-plugin-api';\nimport { render, type RenderResult } from '@testing-library/react';\nimport appPlugin from '@backstage/plugin-app';\nimport { JsonObject } from '@backstage/types';\nimport { ConfigReader } from '@backstage/config';\nimport { MemoryRouter } from 'react-router-dom';\nimport { RouterBlueprint } from '@backstage/plugin-app-react';\nimport { getMockApiFactory } from '../apis/MockWithApiFactory';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport type { CreateSpecializedAppInternalOptions } from '../../../frontend-app-api/src/wiring/createSpecializedApp';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../frontend-app-api/src/routing/getBasePath';\nimport { TestApiPairs } from '../apis/TestApiProvider';\nimport { OpaqueExternalRouteRef } from '@internal/frontend';\n\nconst DEFAULT_MOCK_CONFIG = {\n app: { baseUrl: 'http://localhost:3000' },\n backend: { baseUrl: 'http://localhost:7007' },\n};\n\n/**\n * Options for `renderTestApp`.\n *\n * @public\n */\nexport type RenderTestAppOptions<TApiPairs extends any[] = any[]> = {\n /**\n * Additional configuration passed to the app when rendering elements inside it.\n */\n config?: JsonObject;\n /**\n * Additional extensions to add to the test app.\n */\n extensions?: ExtensionDefinition<any>[];\n\n /**\n * Additional features to add to the test app.\n */\n features?: FrontendFeature[];\n\n /**\n * Initial route entries to use for the router.\n */\n initialRouteEntries?: string[];\n\n /**\n * An object of paths to mount route refs on, with the key being the path and\n * the value being the route ref that the path will be bound to. This allows\n * the route refs to be used by `useRouteRef` in the rendered elements.\n *\n * @example\n * ```ts\n * renderTestApp({\n * mountedRoutes: {\n * '/my-path': myRouteRef,\n * },\n * extensions: [...],\n * })\n * ```\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n\n /**\n * API overrides to provide to the test app. Use `mockApis` helpers\n * from `@backstage/frontend-test-utils` to create mock implementations.\n *\n * @example\n * ```ts\n * import { mockApis } from '@backstage/frontend-test-utils';\n *\n * renderTestApp({\n * apis: [mockApis.identity({ userEntityRef: 'user:default/guest' })],\n * extensions: [...],\n * })\n * ```\n */\n apis?: readonly [...TestApiPairs<TApiPairs>];\n};\n\nconst appPluginOverride = appPlugin.withOverrides({\n extensions: [\n appPlugin.getExtension('sign-in-page:app').override({\n disabled: true,\n }),\n ],\n});\n\n/**\n * Renders the provided extensions inside a Backstage app, returning the same\n * utilities as `@testing-library/react` `render` function.\n *\n * @public\n */\nexport function renderTestApp<const TApiPairs extends any[] = any[]>(\n options?: RenderTestAppOptions<TApiPairs>,\n): RenderResult {\n const extensions = [...(options?.extensions ?? [])];\n\n const externalBindings = new Map<ExternalRouteRef, RouteRef>();\n\n if (options?.mountedRoutes) {\n for (const [path, optionRef] of Object.entries(options.mountedRoutes)) {\n let routeRef: RouteRef;\n\n if (OpaqueExternalRouteRef.isType(optionRef)) {\n // Create an actual route ref for the external route, then bind the external ref to it\n routeRef = createRouteRef();\n externalBindings.set(optionRef, routeRef);\n } else {\n routeRef = optionRef;\n }\n\n extensions.push(\n createExtension({\n kind: 'test-route',\n name: path,\n attachTo: { id: 'app/routes', input: 'routes' },\n output: [\n coreExtensionData.reactElement,\n coreExtensionData.routePath,\n coreExtensionData.routeRef,\n ],\n factory: () => [\n coreExtensionData.reactElement(<Fragment />),\n coreExtensionData.routePath(path),\n coreExtensionData.routeRef(routeRef),\n ],\n }),\n );\n }\n }\n\n const apiFactoryOverrides = (options?.apis ?? []).map(entry => {\n const mockFactory = getMockApiFactory(entry);\n if (mockFactory) {\n return mockFactory;\n }\n const [apiRef, implementation] = entry as readonly [ApiRef<any>, any];\n return createApiFactory(apiRef, implementation);\n });\n const identityOverrideFactory = apiFactoryOverrides.find(\n factory => factory.api.id === identityApiRef.id,\n );\n\n const features: FrontendFeature[] = [\n createFrontendModule({\n pluginId: 'app',\n extensions: [\n RouterBlueprint.make({\n params: {\n component: ({ children }) => (\n <MemoryRouter\n initialEntries={options?.initialRouteEntries}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {children}\n </MemoryRouter>\n ),\n },\n }),\n ],\n }),\n createFrontendPlugin({\n pluginId: 'test',\n extensions,\n }),\n appPluginOverride,\n ];\n\n if (options?.features) {\n features.push(...options.features);\n }\n\n const config = ConfigReader.fromConfigs([\n {\n context: 'render-config',\n data: options?.config ?? DEFAULT_MOCK_CONFIG,\n },\n ]);\n\n const app = prepareSpecializedApp({\n features,\n config,\n __internal: options?.apis && {\n apiFactoryOverrides: apiFactoryOverrides.filter(\n factory => factory.api.id !== identityApiRef.id,\n ),\n },\n bindRoutes:\n externalBindings.size > 0\n ? ({ bind }) => {\n for (const [externalRef, targetRef] of externalBindings) {\n bind({ ref: externalRef }, { ref: targetRef });\n }\n }\n : undefined,\n } as CreateSpecializedAppInternalOptions).finalize();\n\n if (identityOverrideFactory) {\n // identityApiRef always resolves to the app's internal AppIdentityProxy\n // (AppRouter requires this), so the override can't replace the factory.\n // setTarget is now idempotent (first write wins), so we just need to\n // set it before AppRouter's own guest-identity fallback does, which\n // happens during this same synchronous render call.\n const proxy = app.apis.get(identityApiRef as any) as any;\n proxy?.setTarget?.(identityOverrideFactory.factory({}), {\n signOutTargetUrl: getBasePath(config) || '/',\n });\n }\n\n return render(\n app.tree.root.instance!.getData(coreExtensionData.reactElement),\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA8CA,MAAM,mBAAA,GAAsB;AAAA,EAC1B,GAAA,EAAK,EAAE,OAAA,EAAS,uBAAA,EAAwB;AAAA,EACxC,OAAA,EAAS,EAAE,OAAA,EAAS,uBAAA;AACtB,CAAA;AA6DA,MAAM,iBAAA,GAAoB,UAAU,aAAA,CAAc;AAAA,EAChD,UAAA,EAAY;AAAA,IACV,SAAA,CAAU,YAAA,CAAa,kBAAkB,CAAA,CAAE,QAAA,CAAS;AAAA,MAClD,QAAA,EAAU;AAAA,KACX;AAAA;AAEL,CAAC,CAAA;AAQM,SAAS,cACd,OAAA,EACc;AACd,EAAA,MAAM,aAAa,CAAC,GAAI,OAAA,EAAS,UAAA,IAAc,EAAG,CAAA;AAElD,EAAA,MAAM,gBAAA,uBAAuB,GAAA,EAAgC;AAE7D,EAAA,IAAI,SAAS,aAAA,EAAe;AAC1B,IAAA,KAAA,MAAW,CAAC,MAAM,SAAS,CAAA,IAAK,OAAO,OAAA,CAAQ,OAAA,CAAQ,aAAa,CAAA,EAAG;AACrE,MAAA,IAAI,QAAA;AAEJ,MAAA,IAAI,sBAAA,CAAuB,MAAA,CAAO,SAAS,CAAA,EAAG;AAE5C,QAAA,QAAA,GAAW,cAAA,EAAe;AAC1B,QAAA,gBAAA,CAAiB,GAAA,CAAI,WAAW,QAAQ,CAAA;AAAA,MAC1C,CAAA,MAAO;AACL,QAAA,QAAA,GAAW,SAAA;AAAA,MACb;AAEA,MAAA,UAAA,CAAW,IAAA;AAAA,QACT,eAAA,CAAgB;AAAA,UACd,IAAA,EAAM,YAAA;AAAA,UACN,IAAA,EAAM,IAAA;AAAA,UACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,QAAA,EAAS;AAAA,UAC9C,MAAA,EAAQ;AAAA,YACN,iBAAA,CAAkB,YAAA;AAAA,YAClB,iBAAA,CAAkB,SAAA;AAAA,YAClB,iBAAA,CAAkB;AAAA,WACpB;AAAA,UACA,SAAS,MAAM;AAAA,YACb,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,QAAA,EAAA,EAAS,CAAE,CAAA;AAAA,YAC3C,iBAAA,CAAkB,UAAU,IAAI,CAAA;AAAA,YAChC,iBAAA,CAAkB,SAAS,QAAQ;AAAA;AACrC,SACD;AAAA,OACH;AAAA,IACF;AAAA,EACF;AAEA,EAAA,MAAM,uBAAuB,OAAA,EAAS,IAAA,IAAQ,EAAC,EAAG,IAAI,CAAA,KAAA,KAAS;AAC7D,IAAA,MAAM,WAAA,GAAc,kBAAkB,KAAK,CAAA;AAC3C,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,OAAO,WAAA;AAAA,IACT;AACA,IAAA,MAAM,CAAC,MAAA,EAAQ,cAAc,CAAA,GAAI,KAAA;AACjC,IAAA,OAAO,gBAAA,CAAiB,QAAQ,cAAc,CAAA;AAAA,EAChD,CAAC,CAAA;AACD,EAAA,MAAM,0BAA0B,mBAAA,CAAoB,IAAA;AAAA,IAClD,CAAA,OAAA,KAAW,OAAA,CAAQ,GAAA,CAAI,EAAA,KAAO,cAAA,CAAe;AAAA,GAC/C;AAEA,EAAA,MAAM,QAAA,GAA8B;AAAA,IAClC,oBAAA,CAAqB;AAAA,MACnB,QAAA,EAAU,KAAA;AAAA,MACV,UAAA,EAAY;AAAA,QACV,gBAAgB,IAAA,CAAK;AAAA,UACnB,MAAA,EAAQ;AAAA,YACN,SAAA,EAAW,CAAC,EAAE,QAAA,EAAS,qBACrB,GAAA;AAAA,cAAC,YAAA;AAAA,cAAA;AAAA,gBACC,gBAAgB,OAAA,EAAS,mBAAA;AAAA,gBACzB,MAAA,EAAQ;AAAA,kBACN,oBAAA,EAAsB,KAAA;AAAA,kBACtB,kBAAA,EAAoB;AAAA,iBACtB;AAAA,gBAEC;AAAA;AAAA;AACH;AAEJ,SACD;AAAA;AACH,KACD,CAAA;AAAA,IACD,oBAAA,CAAqB;AAAA,MACnB,QAAA,EAAU,MAAA;AAAA,MACV;AAAA,KACD,CAAA;AAAA,IACD;AAAA,GACF;AAEA,EAAA,IAAI,SAAS,QAAA,EAAU;AACrB,IAAA,QAAA,CAAS,IAAA,CAAK,GAAG,OAAA,CAAQ,QAAQ,CAAA;AAAA,EACnC;AAEA,EAAA,MAAM,MAAA,GAAS,aAAa,WAAA,CAAY;AAAA,IACtC;AAAA,MACE,OAAA,EAAS,eAAA;AAAA,MACT,IAAA,EAAM,SAAS,MAAA,IAAU;AAAA;AAC3B,GACD,CAAA;AAED,EAAA,MAAM,MAAM,qBAAA,CAAsB;AAAA,IAChC,QAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA,EAAY,SAAS,IAAA,IAAQ;AAAA,MAC3B,qBAAqB,mBAAA,CAAoB,MAAA;AAAA,QACvC,CAAA,OAAA,KAAW,OAAA,CAAQ,GAAA,CAAI,EAAA,KAAO,cAAA,CAAe;AAAA;AAC/C,KACF;AAAA,IACA,YACE,gBAAA,CAAiB,IAAA,GAAO,IACpB,CAAC,EAAE,MAAK,KAAM;AACZ,MAAA,KAAA,MAAW,CAAC,WAAA,EAAa,SAAS,CAAA,IAAK,gBAAA,EAAkB;AACvD,QAAA,IAAA,CAAK,EAAE,GAAA,EAAK,WAAA,IAAe,EAAE,GAAA,EAAK,WAAW,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA,GACA;AAAA,GACgC,EAAE,QAAA,EAAS;AAEnD,EAAA,IAAI,uBAAA,EAAyB;AAM3B,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,IAAA,CAAK,GAAA,CAAI,cAAqB,CAAA;AAChD,IAAA,KAAA,EAAO,SAAA,GAAY,uBAAA,CAAwB,OAAA,CAAQ,EAAE,CAAA,EAAG;AAAA,MACtD,gBAAA,EAAkB,WAAA,CAAY,MAAM,CAAA,IAAK;AAAA,KAC1C,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA;AAAA,IACL,IAAI,IAAA,CAAK,IAAA,CAAK,QAAA,CAAU,OAAA,CAAQ,kBAAkB,YAAY;AAAA,GAChE;AACF;;;;"}
@@ -0,0 +1,12 @@
1
+ function getBasePath(configApi) {
2
+ let { pathname } = new URL(
3
+ configApi.getOptionalString("app.baseUrl") ?? "/",
4
+ "http://sample.dev"
5
+ // baseUrl can be specified as just a path
6
+ );
7
+ pathname = pathname.replace(/\/*$/, "");
8
+ return pathname;
9
+ }
10
+
11
+ export { getBasePath };
12
+ //# sourceMappingURL=getBasePath.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getBasePath.esm.js","sources":["../../../../../frontend-app-api/src/routing/getBasePath.ts"],"sourcesContent":["/*\n * Copyright 2024 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 { ConfigApi } from '@backstage/frontend-plugin-api';\n\n/** @internal */\nexport function getBasePath(configApi: ConfigApi) {\n let { pathname } = new URL(\n configApi.getOptionalString('app.baseUrl') ?? '/',\n 'http://sample.dev', // baseUrl can be specified as just a path\n );\n pathname = pathname.replace(/\\/*$/, '');\n return pathname;\n}\n"],"names":[],"mappings":"AAmBO,SAAS,YAAY,SAAA,EAAsB;AAChD,EAAA,IAAI,EAAE,QAAA,EAAS,GAAI,IAAI,GAAA;AAAA,IACrB,SAAA,CAAU,iBAAA,CAAkB,aAAa,CAAA,IAAK,GAAA;AAAA,IAC9C;AAAA;AAAA,GACF;AACA,EAAA,QAAA,GAAW,QAAA,CAAS,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA;AACtC,EAAA,OAAO,QAAA;AACT;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-test-utils",
3
- "version": "0.6.3-next.0",
3
+ "version": "0.6.3",
4
4
  "backstage": {
5
5
  "role": "web-library"
6
6
  },
@@ -31,25 +31,25 @@
31
31
  "test": "backstage-cli package test"
32
32
  },
33
33
  "dependencies": {
34
- "@backstage/config": "1.3.8",
35
- "@backstage/core-app-api": "1.20.4-next.0",
36
- "@backstage/core-plugin-api": "1.12.8",
37
- "@backstage/filter-predicates": "0.1.4",
38
- "@backstage/frontend-app-api": "0.16.7-next.0",
39
- "@backstage/frontend-plugin-api": "0.17.3",
40
- "@backstage/plugin-app": "0.5.2-next.0",
41
- "@backstage/plugin-app-react": "0.2.5",
42
- "@backstage/plugin-permission-common": "0.9.9",
43
- "@backstage/plugin-permission-react": "0.5.3",
44
- "@backstage/test-utils": "1.7.21-next.0",
45
- "@backstage/types": "1.2.2",
46
- "@backstage/version-bridge": "1.0.12",
34
+ "@backstage/config": "^1.3.8",
35
+ "@backstage/core-app-api": "^1.20.4",
36
+ "@backstage/core-plugin-api": "^1.12.9",
37
+ "@backstage/filter-predicates": "^0.1.4",
38
+ "@backstage/frontend-app-api": "^0.16.7",
39
+ "@backstage/frontend-plugin-api": "^0.18.0",
40
+ "@backstage/plugin-app": "^0.5.2",
41
+ "@backstage/plugin-app-react": "^0.2.6",
42
+ "@backstage/plugin-permission-common": "^0.9.10",
43
+ "@backstage/plugin-permission-react": "^0.5.4",
44
+ "@backstage/test-utils": "^1.7.21",
45
+ "@backstage/types": "^1.2.2",
46
+ "@backstage/version-bridge": "^1.0.12",
47
47
  "i18next": "^22.4.15",
48
48
  "zen-observable": "^0.10.0",
49
49
  "zod": "^3.25.76 || ^4.0.0"
50
50
  },
51
51
  "devDependencies": {
52
- "@backstage/cli": "0.36.4",
52
+ "@backstage/cli": "^0.36.5",
53
53
  "@testing-library/jest-dom": "^6.0.0",
54
54
  "@types/jest": "*",
55
55
  "@types/react": "^18.0.0",