@backstage/frontend-test-utils 0.1.0-next.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # @backstage/frontend-test-utils
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 59fabd5: New testing utility library for `@backstage/frontend-app-api` and `@backstage/frontend-plugin-api`.
8
+ - af7bc3e: Switched all core extensions to instead use the namespace `'app'`.
9
+
10
+ ### Patch Changes
11
+
12
+ - 59fabd5: Added `createExtensionTester` for rendering extensions in tests.
13
+ - 7e4b0db: The `createExtensionTester` helper is now able to render more than one route in the test app.
14
+ - 818eea4: Updates for compatibility with the new extension IDs.
15
+ - b9aa6e4: Migrate `renderInTestApp` to `@backstage/frontend-test-utils` for testing individual React components in an app.
16
+ - e539735: Updates for `core.router` addition.
17
+ - c21c9cf: Re-export mock API implementations as well as `TestApiProvider`, `TestApiRegistry`, `withLogCollector`, and `setupRequestMockHandlers` from `@backstage/test-utils`.
18
+ - Updated dependencies
19
+ - @backstage/frontend-plugin-api@0.4.0
20
+ - @backstage/frontend-app-api@0.4.0
21
+ - @backstage/test-utils@1.4.6
22
+ - @backstage/types@1.1.1
23
+
24
+ ## 0.1.0-next.3
25
+
26
+ ### Patch Changes
27
+
28
+ - Updated dependencies
29
+ - @backstage/frontend-app-api@0.4.0-next.3
30
+ - @backstage/frontend-plugin-api@0.4.0-next.3
31
+ - @backstage/test-utils@1.4.6-next.2
32
+ - @backstage/types@1.1.1
33
+
3
34
  ## 0.1.0-next.2
4
35
 
5
36
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -1,9 +1,21 @@
1
1
  /// <reference types="react" />
2
- export { ErrorWithContext, MockAnalyticsApi, MockConfigApi, MockErrorApi, MockErrorApiOptions, MockFetchApi, MockFetchApiOptions, MockPermissionApi, MockStorageApi, MockStorageBucket, TestApiProvider, TestApiProviderProps, TestApiRegistry, setupRequestMockHandlers, withLogCollector } from '@backstage/test-utils';
3
- import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
4
- import { JsonObject } from '@backstage/types';
2
+ export { ErrorWithContext, MockConfigApi, MockErrorApi, MockErrorApiOptions, MockFetchApi, MockFetchApiOptions, MockPermissionApi, MockStorageApi, MockStorageBucket, TestApiProvider, TestApiProviderProps, TestApiRegistry, setupRequestMockHandlers, withLogCollector } from '@backstage/test-utils';
3
+ import { AnalyticsApi, AnalyticsEvent, ExtensionDefinition } from '@backstage/frontend-plugin-api';
5
4
  import * as _testing_library_react from '@testing-library/react';
6
5
  import { RenderResult } from '@testing-library/react';
6
+ import { JsonObject } from '@backstage/types';
7
+
8
+ /**
9
+ * Mock implementation of {@link frontend-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.
10
+ * Use getEvents in tests to verify captured events.
11
+ *
12
+ * @public
13
+ */
14
+ declare class MockAnalyticsApi implements AnalyticsApi {
15
+ private events;
16
+ captureEvent(event: AnalyticsEvent): void;
17
+ getEvents(): AnalyticsEvent[];
18
+ }
7
19
 
8
20
  /** @public */
9
21
  declare class ExtensionTester {
@@ -26,4 +38,4 @@ declare function createExtensionTester<TConfig>(subject: ExtensionDefinition<TCo
26
38
  */
27
39
  declare function renderInTestApp(element: JSX.Element): _testing_library_react.RenderResult;
28
40
 
29
- export { ExtensionTester, createExtensionTester, renderInTestApp };
41
+ export { ExtensionTester, MockAnalyticsApi, createExtensionTester, renderInTestApp };
package/dist/index.esm.js CHANGED
@@ -1,13 +1,79 @@
1
1
  import { MockConfigApi } from '@backstage/test-utils';
2
- export { MockAnalyticsApi, MockConfigApi, MockErrorApi, MockFetchApi, MockPermissionApi, MockStorageApi, TestApiProvider, TestApiRegistry, setupRequestMockHandlers, withLogCollector } from '@backstage/test-utils';
3
- import { createSpecializedApp } from '@backstage/frontend-app-api';
4
- import { createExtensionOverrides, createExtension, coreExtensionData } from '@backstage/frontend-plugin-api';
2
+ export { MockConfigApi, MockErrorApi, MockFetchApi, MockPermissionApi, MockStorageApi, TestApiProvider, TestApiRegistry, setupRequestMockHandlers, withLogCollector } from '@backstage/test-utils';
3
+ import React, { Component, Suspense, useEffect, lazy, createContext, useContext, useState } from 'react';
4
+ import { createRoutesFromChildren, Route, useLocation, matchRoutes, Routes, MemoryRouter, Link } from 'react-router-dom';
5
5
  import { render } from '@testing-library/react';
6
+ import { createSpecializedApp } from '@backstage/frontend-app-api';
7
+ import { createExtension as createExtension$1, createExtensionInput, createNavItemExtension, coreExtensionData, useRouteRef, useApi as useApi$1, configApiRef as configApiRef$1, createExtensionOverrides } from '@backstage/frontend-plugin-api';
8
+ import { createApiRef, useApi, AnalyticsContext, useAnalytics, attachComponentData, useApp, configApiRef } from '@backstage/core-plugin-api';
9
+ import { getOrCreateGlobalSingleton } from '@backstage/version-bridge';
10
+
11
+ var __defProp$1 = Object.defineProperty;
12
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
13
+ var __publicField$1 = (obj, key, value) => {
14
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
15
+ return value;
16
+ };
17
+ class MockAnalyticsApi {
18
+ constructor() {
19
+ __publicField$1(this, "events", []);
20
+ }
21
+ captureEvent(event) {
22
+ const { action, subject, value, attributes, context } = event;
23
+ this.events.push({
24
+ action,
25
+ subject,
26
+ context,
27
+ ...value !== void 0 ? { value } : {},
28
+ ...attributes !== void 0 ? { attributes } : {}
29
+ });
30
+ }
31
+ getEvents() {
32
+ return this.events;
33
+ }
34
+ }
35
+
36
+ function toInternalExtensionDefinition(overrides) {
37
+ const internal = overrides;
38
+ if (internal.$$type !== "@backstage/ExtensionDefinition") {
39
+ throw new Error(
40
+ `Invalid extension definition instance, bad type '${internal.$$type}'`
41
+ );
42
+ }
43
+ if (internal.version !== "v1") {
44
+ throw new Error(
45
+ `Invalid extension definition instance, bad version '${internal.version}'`
46
+ );
47
+ }
48
+ return internal;
49
+ }
50
+ function createExtension(options) {
51
+ var _a, _b;
52
+ return {
53
+ $$type: "@backstage/ExtensionDefinition",
54
+ version: "v1",
55
+ kind: options.kind,
56
+ namespace: options.namespace,
57
+ name: options.name,
58
+ attachTo: options.attachTo,
59
+ disabled: (_a = options.disabled) != null ? _a : false,
60
+ inputs: (_b = options.inputs) != null ? _b : {},
61
+ output: options.output,
62
+ configSchema: options.configSchema,
63
+ factory({ inputs, ...rest }) {
64
+ return options.factory({
65
+ inputs,
66
+ ...rest
67
+ });
68
+ }
69
+ };
70
+ }
6
71
 
7
72
  function resolveExtensionDefinition(definition, context) {
8
73
  var _a;
9
- const { name, kind, namespace: _, ...rest } = definition;
10
- const namespace = (_a = definition.namespace) != null ? _a : context == null ? void 0 : context.namespace;
74
+ const internalDefinition = toInternalExtensionDefinition(definition);
75
+ const { name, kind, namespace: _, ...rest } = internalDefinition;
76
+ const namespace = (_a = internalDefinition.namespace) != null ? _a : context == null ? void 0 : context.namespace;
11
77
  const namePart = name && namespace ? `${namespace}/${name}` : namespace || name;
12
78
  if (!namePart) {
13
79
  throw new Error(
@@ -16,10 +82,326 @@ function resolveExtensionDefinition(definition, context) {
16
82
  }
17
83
  return {
18
84
  ...rest,
19
- id: kind ? `${kind}:${namePart}` : namePart,
20
- $$type: "@backstage/Extension"
85
+ $$type: "@backstage/Extension",
86
+ version: "v1",
87
+ id: kind ? `${kind}:${namePart}` : namePart
88
+ };
89
+ }
90
+
91
+ function createComponentRef(options) {
92
+ const { id } = options;
93
+ return {
94
+ id
21
95
  };
22
96
  }
97
+ const coreProgressComponentRef = createComponentRef({
98
+ id: "core.components.progress"
99
+ });
100
+ const coreNotFoundErrorPageComponentRef = createComponentRef({
101
+ id: "core.components.notFoundErrorPage"
102
+ });
103
+ const coreErrorBoundaryFallbackComponentRef = createComponentRef({
104
+ id: "core.components.errorBoundaryFallback"
105
+ });
106
+ const coreComponentRefs = {
107
+ progress: coreProgressComponentRef,
108
+ notFoundErrorPage: coreNotFoundErrorPageComponentRef,
109
+ errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef
110
+ };
111
+
112
+ var __defProp = Object.defineProperty;
113
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
114
+ var __publicField = (obj, key, value) => {
115
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
116
+ return value;
117
+ };
118
+ class ErrorBoundary extends Component {
119
+ constructor() {
120
+ super(...arguments);
121
+ __publicField(this, "state", { error: void 0 });
122
+ __publicField(this, "handleErrorReset", () => {
123
+ this.setState({ error: void 0 });
124
+ });
125
+ }
126
+ static getDerivedStateFromError(error) {
127
+ return { error };
128
+ }
129
+ render() {
130
+ const { error } = this.state;
131
+ const { plugin, children, Fallback } = this.props;
132
+ if (error) {
133
+ return /* @__PURE__ */ React.createElement(
134
+ Fallback,
135
+ {
136
+ plugin,
137
+ error,
138
+ resetError: this.handleErrorReset
139
+ }
140
+ );
141
+ }
142
+ return children;
143
+ }
144
+ }
145
+
146
+ getOrCreateGlobalSingleton(
147
+ "core-plugin-api:analytics-tracker-events",
148
+ () => ({
149
+ mostRecentGatheredNavigation: void 0,
150
+ mostRecentRoutableExtensionRender: void 0,
151
+ beforeUnloadRegistered: false
152
+ })
153
+ );
154
+ const routableExtensionRenderedEvent = "_ROUTABLE-EXTENSION-RENDERED";
155
+
156
+ createApiRef({ id: "core.app-tree" });
157
+
158
+ const componentsApiRef = createApiRef({
159
+ id: "core.components"
160
+ });
161
+ function useComponentRef(ref) {
162
+ const componentsApi = useApi(componentsApiRef);
163
+ return componentsApi.getComponent(ref);
164
+ }
165
+
166
+ createApiRef({
167
+ id: "core.analytics"
168
+ });
169
+
170
+ const RouteTracker$1 = (props) => {
171
+ const { disableTracking, children } = props;
172
+ const analytics = useAnalytics();
173
+ useEffect(() => {
174
+ if (disableTracking)
175
+ return;
176
+ analytics.captureEvent(routableExtensionRenderedEvent, "");
177
+ }, [analytics, disableTracking]);
178
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
179
+ };
180
+ function ExtensionBoundary(props) {
181
+ var _a;
182
+ const { node, routable, children } = props;
183
+ const plugin = node.spec.source;
184
+ const Progress = useComponentRef(coreComponentRefs.progress);
185
+ const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);
186
+ const attributes = {
187
+ extensionId: node.spec.id,
188
+ pluginId: (_a = node.spec.source) == null ? void 0 : _a.id
189
+ };
190
+ return /* @__PURE__ */ React.createElement(Suspense, { fallback: /* @__PURE__ */ React.createElement(Progress, null) }, /* @__PURE__ */ React.createElement(ErrorBoundary, { plugin, Fallback: fallback }, /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes }, /* @__PURE__ */ React.createElement(RouteTracker$1, { disableTracking: !routable }, children))));
191
+ }
192
+
193
+ function createExtensionDataRef(id) {
194
+ return {
195
+ id,
196
+ $$type: "@backstage/ExtensionDataRef",
197
+ config: {},
198
+ optional() {
199
+ return { ...this, config: { ...this.config, optional: true } };
200
+ }
201
+ };
202
+ }
203
+
204
+ function createSignInPageExtension(options) {
205
+ var _a;
206
+ return createExtension({
207
+ kind: "sign-in-page",
208
+ namespace: options == null ? void 0 : options.namespace,
209
+ name: options == null ? void 0 : options.name,
210
+ attachTo: (_a = options.attachTo) != null ? _a : { id: "app/router", input: "signInPage" },
211
+ configSchema: options.configSchema,
212
+ inputs: options.inputs,
213
+ disabled: options.disabled,
214
+ output: {
215
+ component: createSignInPageExtension.componentDataRef
216
+ },
217
+ factory({ config, inputs, node }) {
218
+ const ExtensionComponent = lazy(
219
+ () => options.loader({ config, inputs }).then((component) => ({ default: component }))
220
+ );
221
+ return {
222
+ component: (props) => /* @__PURE__ */ React.createElement(ExtensionBoundary, { node, routable: true }, /* @__PURE__ */ React.createElement(ExtensionComponent, { ...props }))
223
+ };
224
+ }
225
+ });
226
+ }
227
+ ((createSignInPageExtension2) => {
228
+ createSignInPageExtension2.componentDataRef = createExtensionDataRef("core.sign-in-page.component");
229
+ })(createSignInPageExtension || (createSignInPageExtension = {}));
230
+
231
+ const InternalAppContext$1 = createContext(void 0);
232
+
233
+ const InternalAppContext = createContext(void 0);
234
+
235
+ function isReactRouterBeta() {
236
+ const [obj] = createRoutesFromChildren(/* @__PURE__ */ React.createElement(Route, { index: true, element: /* @__PURE__ */ React.createElement("div", null) }));
237
+ return !obj.index;
238
+ }
239
+
240
+ const getExtensionContext = (pathname, routes) => {
241
+ try {
242
+ const matches = matchRoutes(routes, { pathname });
243
+ const routeMatch = matches == null ? void 0 : matches.filter((match) => {
244
+ var _a;
245
+ return ((_a = match == null ? void 0 : match.route.routeRefs) == null ? void 0 : _a.size) > 0;
246
+ }).pop();
247
+ const routeObject = routeMatch == null ? void 0 : routeMatch.route;
248
+ if (!routeObject) {
249
+ return void 0;
250
+ }
251
+ if (routeObject.path === "" && pathname !== "/") {
252
+ return void 0;
253
+ }
254
+ let routeRef;
255
+ if (routeObject.routeRefs.size === 1) {
256
+ routeRef = routeObject.routeRefs.values().next().value;
257
+ }
258
+ let plugin;
259
+ if (routeObject.plugins.size === 1) {
260
+ plugin = routeObject.plugins.values().next().value;
261
+ }
262
+ const params = Object.entries(
263
+ (routeMatch == null ? void 0 : routeMatch.params) || {}
264
+ ).reduce((acc, [key, value]) => {
265
+ if (value !== void 0 && key !== "*") {
266
+ acc[key] = value;
267
+ }
268
+ return acc;
269
+ }, {});
270
+ return {
271
+ extension: "App",
272
+ pluginId: (plugin == null ? void 0 : plugin.getId()) || "root",
273
+ ...routeRef ? { routeRef: routeRef.id } : {},
274
+ _routeNodeType: routeObject.element,
275
+ params
276
+ };
277
+ } catch {
278
+ return void 0;
279
+ }
280
+ };
281
+ const TrackNavigation = ({
282
+ pathname,
283
+ search,
284
+ hash,
285
+ attributes
286
+ }) => {
287
+ const analytics = useAnalytics();
288
+ useEffect(() => {
289
+ analytics.captureEvent("navigate", `${pathname}${search}${hash}`, {
290
+ attributes
291
+ });
292
+ }, [analytics, pathname, search, hash, attributes]);
293
+ return null;
294
+ };
295
+ const RouteTracker = ({
296
+ routeObjects
297
+ }) => {
298
+ const { pathname, search, hash } = useLocation();
299
+ const { params, ...attributes } = getExtensionContext(
300
+ pathname,
301
+ routeObjects
302
+ ) || { params: {} };
303
+ return /* @__PURE__ */ React.createElement(AnalyticsContext, { attributes }, /* @__PURE__ */ React.createElement(
304
+ TrackNavigation,
305
+ {
306
+ pathname,
307
+ search,
308
+ hash,
309
+ attributes: params
310
+ }
311
+ ));
312
+ };
313
+
314
+ function getBasePath(configApi) {
315
+ if (!isReactRouterBeta()) {
316
+ return "";
317
+ }
318
+ return readBasePath(configApi);
319
+ }
320
+ function readBasePath(configApi) {
321
+ var _a;
322
+ let { pathname } = new URL(
323
+ (_a = configApi.getOptionalString("app.baseUrl")) != null ? _a : "/",
324
+ "http://sample.dev"
325
+ // baseUrl can be specified as just a path
326
+ );
327
+ pathname = pathname.replace(/\/*$/, "");
328
+ return pathname;
329
+ }
330
+ function SignInPageWrapper({
331
+ component: Component,
332
+ appIdentityProxy,
333
+ children
334
+ }) {
335
+ const [identityApi, setIdentityApi] = useState();
336
+ const configApi = useApi(configApiRef);
337
+ const basePath = readBasePath(configApi);
338
+ if (!identityApi) {
339
+ return /* @__PURE__ */ React.createElement(Component, { onSignInSuccess: setIdentityApi });
340
+ }
341
+ appIdentityProxy.setTarget(identityApi, {
342
+ signOutTargetUrl: basePath || "/"
343
+ });
344
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, children);
345
+ }
346
+ function AppRouter(props) {
347
+ const { Router: RouterComponent, SignInPage: SignInPageComponent } = useApp().getComponents();
348
+ const configApi = useApi(configApiRef);
349
+ const basePath = readBasePath(configApi);
350
+ const mountPath = `${basePath}/*`;
351
+ const internalAppContext = useContext(InternalAppContext);
352
+ if (!internalAppContext) {
353
+ throw new Error("AppRouter must be rendered within the AppProvider");
354
+ }
355
+ const { routeObjects, appIdentityProxy } = internalAppContext;
356
+ if (!SignInPageComponent) {
357
+ appIdentityProxy.setTarget(
358
+ {
359
+ getUserId: () => "guest",
360
+ getIdToken: async () => void 0,
361
+ getProfile: () => ({
362
+ email: "guest@example.com",
363
+ displayName: "Guest"
364
+ }),
365
+ getProfileInfo: async () => ({
366
+ email: "guest@example.com",
367
+ displayName: "Guest"
368
+ }),
369
+ getBackstageIdentity: async () => ({
370
+ type: "user",
371
+ userEntityRef: "user:default/guest",
372
+ ownershipEntityRefs: ["user:default/guest"]
373
+ }),
374
+ getCredentials: async () => ({}),
375
+ signOut: async () => {
376
+ }
377
+ },
378
+ { signOutTargetUrl: basePath || "/" }
379
+ );
380
+ if (isReactRouterBeta()) {
381
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, props.children) })));
382
+ }
383
+ return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), props.children);
384
+ }
385
+ if (isReactRouterBeta()) {
386
+ return /* @__PURE__ */ React.createElement(RouterComponent, null, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
387
+ SignInPageWrapper,
388
+ {
389
+ component: SignInPageComponent,
390
+ appIdentityProxy
391
+ },
392
+ /* @__PURE__ */ React.createElement(Routes, null, /* @__PURE__ */ React.createElement(Route, { path: mountPath, element: /* @__PURE__ */ React.createElement(React.Fragment, null, props.children) }))
393
+ ));
394
+ }
395
+ return /* @__PURE__ */ React.createElement(RouterComponent, { basename: basePath }, /* @__PURE__ */ React.createElement(RouteTracker, { routeObjects }), /* @__PURE__ */ React.createElement(
396
+ SignInPageWrapper,
397
+ {
398
+ component: SignInPageComponent,
399
+ appIdentityProxy
400
+ },
401
+ props.children
402
+ ));
403
+ }
404
+ attachComponentData(AppRouter, "core.type", "AppRouter");
23
405
 
24
406
  var __accessCheck = (obj, member, msg) => {
25
407
  if (!member.has(obj))
@@ -35,6 +417,111 @@ var __privateAdd = (obj, member, value) => {
35
417
  member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
36
418
  };
37
419
  var _extensions;
420
+ const NavItem = (props) => {
421
+ const { routeRef, title, icon: Icon } = props;
422
+ const to = useRouteRef(routeRef)();
423
+ return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement(Link, { to }, /* @__PURE__ */ React.createElement(Icon, null), " ", title));
424
+ };
425
+ const TestCoreNavExtension = createExtension$1({
426
+ namespace: "app",
427
+ name: "nav",
428
+ attachTo: { id: "app/layout", input: "nav" },
429
+ inputs: {
430
+ items: createExtensionInput({
431
+ target: createNavItemExtension.targetDataRef
432
+ })
433
+ },
434
+ output: {
435
+ element: coreExtensionData.reactElement
436
+ },
437
+ factory({ inputs }) {
438
+ return {
439
+ element: /* @__PURE__ */ React.createElement("nav", null, /* @__PURE__ */ React.createElement("ul", null, inputs.items.map((item, index) => /* @__PURE__ */ React.createElement(
440
+ NavItem,
441
+ {
442
+ key: index,
443
+ icon: item.output.target.icon,
444
+ title: item.output.target.title,
445
+ routeRef: item.output.target.routeRef
446
+ }
447
+ ))))
448
+ };
449
+ }
450
+ });
451
+ const AuthenticationProvider = (props) => {
452
+ const { signInPage: SignInPage, children } = props;
453
+ const configApi = useApi$1(configApiRef$1);
454
+ const signOutTargetUrl = getBasePath(configApi) || "/";
455
+ const internalAppContext = useContext(InternalAppContext$1);
456
+ if (!internalAppContext) {
457
+ throw new Error("AppRouter must be rendered within the AppProvider");
458
+ }
459
+ const { appIdentityProxy } = internalAppContext;
460
+ const [identityApi, setIdentityApi] = useState();
461
+ if (!SignInPage) {
462
+ appIdentityProxy.setTarget(
463
+ {
464
+ getUserId: () => "guest",
465
+ getIdToken: async () => void 0,
466
+ getProfile: () => ({
467
+ email: "guest@example.com",
468
+ displayName: "Guest"
469
+ }),
470
+ getProfileInfo: async () => ({
471
+ email: "guest@example.com",
472
+ displayName: "Guest"
473
+ }),
474
+ getBackstageIdentity: async () => ({
475
+ type: "user",
476
+ userEntityRef: "user:default/guest",
477
+ ownershipEntityRefs: ["user:default/guest"]
478
+ }),
479
+ getCredentials: async () => ({}),
480
+ signOut: async () => {
481
+ }
482
+ },
483
+ { signOutTargetUrl }
484
+ );
485
+ return children;
486
+ }
487
+ if (!identityApi) {
488
+ return /* @__PURE__ */ React.createElement(SignInPage, { onSignInSuccess: setIdentityApi });
489
+ }
490
+ appIdentityProxy.setTarget(identityApi, {
491
+ signOutTargetUrl
492
+ });
493
+ return children;
494
+ };
495
+ const TestCoreRouterExtension = createExtension$1({
496
+ namespace: "app",
497
+ name: "router",
498
+ attachTo: { id: "app", input: "root" },
499
+ inputs: {
500
+ signInPage: createExtensionInput(
501
+ {
502
+ component: createSignInPageExtension.componentDataRef
503
+ },
504
+ { singleton: true, optional: true }
505
+ ),
506
+ children: createExtensionInput(
507
+ {
508
+ element: coreExtensionData.reactElement
509
+ },
510
+ { singleton: true }
511
+ )
512
+ },
513
+ output: {
514
+ element: coreExtensionData.reactElement
515
+ },
516
+ factory({ inputs }) {
517
+ var _a;
518
+ const SignInPage = (_a = inputs.signInPage) == null ? void 0 : _a.output.component;
519
+ const children = inputs.children.output.element;
520
+ return {
521
+ element: /* @__PURE__ */ React.createElement(MemoryRouter, null, /* @__PURE__ */ React.createElement(AuthenticationProvider, { signInPage: SignInPage }, children))
522
+ };
523
+ }
524
+ });
38
525
  const _ExtensionTester = class _ExtensionTester {
39
526
  constructor() {
40
527
  __privateAdd(this, _extensions, new Array());
@@ -42,17 +529,33 @@ const _ExtensionTester = class _ExtensionTester {
42
529
  /** @internal */
43
530
  static forSubject(subject, options) {
44
531
  const tester = new _ExtensionTester();
45
- tester.add(subject, options);
532
+ const { output, factory, ...rest } = toInternalExtensionDefinition(subject);
533
+ const extension = createExtension$1({
534
+ ...rest,
535
+ attachTo: { id: "app/routes", input: "routes" },
536
+ output: {
537
+ ...output,
538
+ path: coreExtensionData.routePath
539
+ },
540
+ factory: (params) => ({
541
+ ...factory(params),
542
+ path: "/"
543
+ })
544
+ });
545
+ tester.add(extension, options);
46
546
  return tester;
47
547
  }
48
548
  add(extension, options) {
49
- const withNamespace = {
549
+ const { name, namespace } = extension;
550
+ const definition = {
50
551
  ...extension,
51
- name: !extension.namespace && !extension.name ? "test" : extension.name
552
+ // setting name "test" as fallback
553
+ name: !namespace && !name ? "test" : name
52
554
  };
555
+ const { id } = resolveExtensionDefinition(definition);
53
556
  __privateGet(this, _extensions).push({
54
- id: resolveExtensionDefinition(withNamespace).id,
55
- extension: withNamespace,
557
+ id,
558
+ definition,
56
559
  config: options == null ? void 0 : options.config
57
560
  });
58
561
  return this;
@@ -66,26 +569,16 @@ const _ExtensionTester = class _ExtensionTester {
66
569
  );
67
570
  }
68
571
  const extensionsConfig = [
69
- ...rest.map((entry) => ({
70
- [entry.id]: {
71
- config: entry.config
572
+ ...rest.map((extension) => ({
573
+ [extension.id]: {
574
+ config: extension.config
72
575
  }
73
576
  })),
74
577
  {
75
578
  [subject.id]: {
76
- attachTo: { id: "core/router", input: "children" },
77
579
  config: subject.config,
78
580
  disabled: false
79
581
  }
80
- },
81
- {
82
- "core/layout": false
83
- },
84
- {
85
- "core/nav": false
86
- },
87
- {
88
- "core/routes": false
89
582
  }
90
583
  ];
91
584
  const finalConfig = {
@@ -98,7 +591,11 @@ const _ExtensionTester = class _ExtensionTester {
98
591
  const app = createSpecializedApp({
99
592
  features: [
100
593
  createExtensionOverrides({
101
- extensions: __privateGet(this, _extensions).map((entry) => entry.extension)
594
+ extensions: [
595
+ ...__privateGet(this, _extensions).map((extension) => extension.definition),
596
+ TestCoreNavExtension,
597
+ TestCoreRouterExtension
598
+ ]
102
599
  })
103
600
  ],
104
601
  config: new MockConfigApi(finalConfig)
@@ -113,9 +610,9 @@ function createExtensionTester(subject, options) {
113
610
  }
114
611
 
115
612
  function renderInTestApp(element) {
116
- const extension = createExtension({
613
+ const extension = createExtension$1({
117
614
  namespace: "test",
118
- attachTo: { id: "core", input: "root" },
615
+ attachTo: { id: "app", input: "root" },
119
616
  output: {
120
617
  element: coreExtensionData.reactElement
121
618
  },
@@ -125,5 +622,5 @@ function renderInTestApp(element) {
125
622
  return tester.render();
126
623
  }
127
624
 
128
- export { createExtensionTester, renderInTestApp };
625
+ export { MockAnalyticsApi, createExtensionTester, renderInTestApp };
129
626
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../../frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts","../src/app/createExtensionTester.ts","../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 { Extension, ExtensionDefinition } from './createExtension';\n\n/** @internal */\nexport function resolveExtensionDefinition<TConfig>(\n definition: ExtensionDefinition<TConfig>,\n context?: { namespace?: string },\n): Extension<TConfig> {\n const { name, kind, namespace: _, ...rest } = definition;\n const namespace = definition.namespace ?? context?.namespace;\n\n const namePart =\n name && namespace ? `${namespace}/${name}` : namespace || name;\n if (!namePart) {\n throw new Error(\n `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`,\n );\n }\n\n return {\n ...rest,\n id: kind ? `${kind}:${namePart}` : namePart,\n $$type: '@backstage/Extension',\n };\n}\n","/*\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 { createSpecializedApp } from '@backstage/frontend-app-api';\nimport {\n ExtensionDefinition,\n createExtensionOverrides,\n} from '@backstage/frontend-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\nimport { MockConfigApi } from '@backstage/test-utils';\nimport { JsonArray, JsonObject, JsonValue } from '@backstage/types';\nimport { RenderResult, render } from '@testing-library/react';\n\n/** @public */\nexport class ExtensionTester {\n /** @internal */\n static forSubject<TConfig>(\n subject: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n ): ExtensionTester {\n const tester = new ExtensionTester();\n tester.add(subject, options);\n return tester;\n }\n\n readonly #extensions = new Array<{\n id: string;\n extension: ExtensionDefinition<any>;\n config?: JsonValue;\n }>();\n\n add<TConfig>(\n extension: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n ): ExtensionTester {\n const withNamespace = {\n ...extension,\n name: !extension.namespace && !extension.name ? 'test' : extension.name,\n };\n this.#extensions.push({\n id: resolveExtensionDefinition(withNamespace).id,\n extension: withNamespace,\n config: options?.config as JsonValue,\n });\n\n return this;\n }\n\n render(options?: { config?: JsonObject }): RenderResult {\n const { config = {} } = options ?? {};\n\n const [subject, ...rest] = this.#extensions;\n if (!subject) {\n throw new Error(\n 'No subject found. At least one extension should be added to the tester.',\n );\n }\n\n const extensionsConfig: JsonArray = [\n ...rest.map(entry => ({\n [entry.id]: {\n config: entry.config,\n },\n })),\n {\n [subject.id]: {\n attachTo: { id: 'core/router', input: 'children' },\n config: subject.config,\n disabled: false,\n },\n },\n {\n 'core/layout': false,\n },\n {\n 'core/nav': false,\n },\n {\n 'core/routes': false,\n },\n ];\n\n const finalConfig = {\n ...config,\n app: {\n ...(typeof config.app === 'object' ? config.app : undefined),\n extensions: extensionsConfig,\n },\n };\n\n const app = createSpecializedApp({\n features: [\n createExtensionOverrides({\n extensions: this.#extensions.map(entry => entry.extension),\n }),\n ],\n config: new MockConfigApi(finalConfig),\n });\n\n return render(app.createRoot());\n }\n}\n\n/** @public */\nexport function createExtensionTester<TConfig>(\n subject: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n): ExtensionTester {\n return ExtensionTester.forSubject(subject, options);\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreExtensionData,\n createExtension,\n} from '@backstage/frontend-plugin-api';\nimport { createExtensionTester } from './createExtensionTester';\n\n/**\n * @public\n * Renders the given element in a test app, for use in unit tests.\n */\nexport function renderInTestApp(element: JSX.Element) {\n const extension = createExtension({\n namespace: 'test',\n attachTo: { id: 'core', input: 'root' },\n output: {\n element: coreExtensionData.reactElement,\n },\n factory: () => ({ element }),\n });\n const tester = createExtensionTester(extension);\n return tester.render();\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/apis/AnalyticsApi/MockAnalyticsApi.ts","../../frontend-plugin-api/src/wiring/createExtension.ts","../../frontend-plugin-api/src/wiring/resolveExtensionDefinition.ts","../../frontend-plugin-api/src/components/ComponentRef.tsx","../../frontend-plugin-api/src/components/ErrorBoundary.tsx","../../core-plugin-api/src/analytics/Tracker.ts","../../frontend-plugin-api/src/apis/definitions/AppTreeApi.ts","../../frontend-plugin-api/src/apis/definitions/ComponentsApi.ts","../../frontend-plugin-api/src/apis/definitions/AnalyticsApi.ts","../../frontend-plugin-api/src/components/ExtensionBoundary.tsx","../../frontend-plugin-api/src/wiring/createExtensionDataRef.ts","../../frontend-plugin-api/src/extensions/createSignInPageExtension.tsx","../../frontend-app-api/src/wiring/InternalAppContext.ts","../../core-app-api/src/app/InternalAppContext.ts","../../core-app-api/src/app/isReactRouterBeta.tsx","../../core-app-api/src/routing/RouteTracker.tsx","../../core-app-api/src/app/AppRouter.tsx","../src/app/createExtensionTester.tsx","../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 { AnalyticsApi, AnalyticsEvent } from '@backstage/frontend-plugin-api';\n\n/**\n * Mock implementation of {@link frontend-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.\n * Use getEvents in tests to verify captured events.\n *\n * @public\n */\nexport class MockAnalyticsApi implements AnalyticsApi {\n private events: AnalyticsEvent[] = [];\n\n captureEvent(event: AnalyticsEvent) {\n const { action, subject, value, attributes, context } = event;\n\n this.events.push({\n action,\n subject,\n context,\n ...(value !== undefined ? { value } : {}),\n ...(attributes !== undefined ? { attributes } : {}),\n });\n }\n\n getEvents(): AnalyticsEvent[] {\n return this.events;\n }\n}\n","/*\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 { AppNode } from '../apis';\nimport { PortableSchema } from '../schema';\nimport { Expand } from '../types';\nimport { ExtensionDataRef } from './createExtensionDataRef';\nimport { ExtensionInput } from './createExtensionInput';\n\n/** @public */\nexport type AnyExtensionDataMap = {\n [name in string]: ExtensionDataRef<unknown, { optional?: true }>;\n};\n\n/** @public */\nexport type AnyExtensionInputMap = {\n [inputName in string]: ExtensionInput<\n AnyExtensionDataMap,\n { optional: boolean; singleton: boolean }\n >;\n};\n\n/**\n * Converts an extension data map into the matching concrete data values type.\n * @public\n */\nexport type ExtensionDataValues<TExtensionData extends AnyExtensionDataMap> = {\n [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends {\n optional: true;\n }\n ? never\n : DataName]: TExtensionData[DataName]['T'];\n} & {\n [DataName in keyof TExtensionData as TExtensionData[DataName]['config'] extends {\n optional: true;\n }\n ? DataName\n : never]?: TExtensionData[DataName]['T'];\n};\n\n/**\n * Convert a single extension input into a matching resolved input.\n * @public\n */\nexport type ResolvedExtensionInput<TExtensionData extends AnyExtensionDataMap> =\n {\n node: AppNode;\n output: ExtensionDataValues<TExtensionData>;\n };\n\n/**\n * Converts an extension input map into a matching collection of resolved inputs.\n * @public\n */\nexport type ResolvedExtensionInputs<\n TInputs extends { [name in string]: ExtensionInput<any, any> },\n> = {\n [InputName in keyof TInputs]: false extends TInputs[InputName]['config']['singleton']\n ? Array<Expand<ResolvedExtensionInput<TInputs[InputName]['extensionData']>>>\n : false extends TInputs[InputName]['config']['optional']\n ? Expand<ResolvedExtensionInput<TInputs[InputName]['extensionData']>>\n : Expand<\n ResolvedExtensionInput<TInputs[InputName]['extensionData']> | undefined\n >;\n};\n\n/** @public */\nexport interface CreateExtensionOptions<\n TOutput extends AnyExtensionDataMap,\n TInputs extends AnyExtensionInputMap,\n TConfig,\n> {\n kind?: string;\n namespace?: string;\n name?: string;\n attachTo: { id: string; input: string };\n disabled?: boolean;\n inputs?: TInputs;\n output: TOutput;\n configSchema?: PortableSchema<TConfig>;\n factory(options: {\n node: AppNode;\n config: TConfig;\n inputs: Expand<ResolvedExtensionInputs<TInputs>>;\n }): Expand<ExtensionDataValues<TOutput>>;\n}\n\n/** @public */\nexport interface ExtensionDefinition<TConfig> {\n $$type: '@backstage/ExtensionDefinition';\n readonly kind?: string;\n readonly namespace?: string;\n readonly name?: string;\n readonly attachTo: { id: string; input: string };\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<TConfig>;\n}\n\n/** @internal */\nexport interface InternalExtensionDefinition<TConfig>\n extends ExtensionDefinition<TConfig> {\n readonly version: 'v1';\n readonly inputs: AnyExtensionInputMap;\n readonly output: AnyExtensionDataMap;\n factory(options: {\n node: AppNode;\n config: TConfig;\n inputs: ResolvedExtensionInputs<any>;\n }): ExtensionDataValues<any>;\n}\n\n/** @internal */\nexport function toInternalExtensionDefinition<TConfig>(\n overrides: ExtensionDefinition<TConfig>,\n): InternalExtensionDefinition<TConfig> {\n const internal = overrides as InternalExtensionDefinition<TConfig>;\n if (internal.$$type !== '@backstage/ExtensionDefinition') {\n throw new Error(\n `Invalid extension definition instance, bad type '${internal.$$type}'`,\n );\n }\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid extension definition instance, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n\n/** @public */\nexport function createExtension<\n TOutput extends AnyExtensionDataMap,\n TInputs extends AnyExtensionInputMap,\n TConfig = never,\n>(\n options: CreateExtensionOptions<TOutput, TInputs, TConfig>,\n): ExtensionDefinition<TConfig> {\n return {\n $$type: '@backstage/ExtensionDefinition',\n version: 'v1',\n kind: options.kind,\n namespace: options.namespace,\n name: options.name,\n attachTo: options.attachTo,\n disabled: options.disabled ?? false,\n inputs: options.inputs ?? {},\n output: options.output,\n configSchema: options.configSchema,\n factory({ inputs, ...rest }) {\n // TODO: Simplify this, but TS wouldn't infer the input type for some reason\n return options.factory({\n inputs: inputs as Expand<ResolvedExtensionInputs<TInputs>>,\n ...rest,\n });\n },\n } as InternalExtensionDefinition<TConfig>;\n}\n","/*\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 { AppNode } from '../apis';\nimport {\n AnyExtensionDataMap,\n AnyExtensionInputMap,\n ExtensionDataValues,\n ExtensionDefinition,\n ResolvedExtensionInputs,\n toInternalExtensionDefinition,\n} from './createExtension';\nimport { PortableSchema } from '../schema';\n\n/** @public */\nexport interface Extension<TConfig> {\n $$type: '@backstage/Extension';\n readonly id: string;\n readonly attachTo: { id: string; input: string };\n readonly disabled: boolean;\n readonly configSchema?: PortableSchema<TConfig>;\n}\n\n/** @internal */\nexport interface InternalExtension<TConfig> extends Extension<TConfig> {\n readonly version: 'v1';\n readonly inputs: AnyExtensionInputMap;\n readonly output: AnyExtensionDataMap;\n factory(options: {\n node: AppNode;\n config: TConfig;\n inputs: ResolvedExtensionInputs<any>;\n }): ExtensionDataValues<any>;\n}\n\n/** @internal */\nexport function toInternalExtension<TConfig>(\n overrides: Extension<TConfig>,\n): InternalExtension<TConfig> {\n const internal = overrides as InternalExtension<TConfig>;\n if (internal.$$type !== '@backstage/Extension') {\n throw new Error(\n `Invalid extension instance, bad type '${internal.$$type}'`,\n );\n }\n if (internal.version !== 'v1') {\n throw new Error(\n `Invalid extension instance, bad version '${internal.version}'`,\n );\n }\n return internal;\n}\n\n/** @internal */\nexport function resolveExtensionDefinition<TConfig>(\n definition: ExtensionDefinition<TConfig>,\n context?: { namespace?: string },\n): Extension<TConfig> {\n const internalDefinition = toInternalExtensionDefinition(definition);\n const { name, kind, namespace: _, ...rest } = internalDefinition;\n const namespace = internalDefinition.namespace ?? context?.namespace;\n\n const namePart =\n name && namespace ? `${namespace}/${name}` : namespace || name;\n if (!namePart) {\n throw new Error(\n `Extension must declare an explicit namespace or name as it could not be resolved from context, kind=${kind} namespace=${namespace} name=${name}`,\n );\n }\n\n return {\n ...rest,\n $$type: '@backstage/Extension',\n version: 'v1',\n id: kind ? `${kind}:${namePart}` : namePart,\n } as InternalExtension<TConfig>;\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n CoreErrorBoundaryFallbackProps,\n CoreNotFoundErrorPageProps,\n CoreProgressProps,\n} from '../types';\n\n/** @public */\nexport type ComponentRef<T extends {} = {}> = {\n id: string;\n T: T;\n};\n\n/** @public */\nexport function createComponentRef<T extends {} = {}>(options: {\n id: string;\n}): ComponentRef<T> {\n const { id } = options;\n return {\n id,\n } as ComponentRef<T>;\n}\n\nconst coreProgressComponentRef = createComponentRef<CoreProgressProps>({\n id: 'core.components.progress',\n});\n\nconst coreNotFoundErrorPageComponentRef =\n createComponentRef<CoreNotFoundErrorPageProps>({\n id: 'core.components.notFoundErrorPage',\n });\n\nconst coreErrorBoundaryFallbackComponentRef =\n createComponentRef<CoreErrorBoundaryFallbackProps>({\n id: 'core.components.errorBoundaryFallback',\n });\n\n/** @public */\nexport const coreComponentRefs = {\n progress: coreProgressComponentRef,\n notFoundErrorPage: coreNotFoundErrorPageComponentRef,\n errorBoundaryFallback: coreErrorBoundaryFallbackComponentRef,\n};\n","/*\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 React, { Component, ComponentType, PropsWithChildren } from 'react';\nimport { BackstagePlugin } from '../wiring';\nimport { CoreErrorBoundaryFallbackProps } from '../types';\n\ntype ErrorBoundaryProps = PropsWithChildren<{\n plugin?: BackstagePlugin;\n Fallback: ComponentType<CoreErrorBoundaryFallbackProps>;\n}>;\ntype ErrorBoundaryState = { error?: Error };\n\n/** @internal */\nexport class ErrorBoundary extends Component<\n ErrorBoundaryProps,\n ErrorBoundaryState\n> {\n static getDerivedStateFromError(error: Error) {\n return { error };\n }\n\n state: ErrorBoundaryState = { error: undefined };\n\n handleErrorReset = () => {\n this.setState({ error: undefined });\n };\n\n render() {\n const { error } = this.state;\n const { plugin, children, Fallback } = this.props;\n\n if (error) {\n return (\n <Fallback\n plugin={plugin}\n error={error}\n resetError={this.handleErrorReset}\n />\n );\n }\n\n return children;\n }\n}\n","/*\n * Copyright 2021 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 { getOrCreateGlobalSingleton } from '@backstage/version-bridge';\nimport {\n AnalyticsApi,\n AnalyticsEventAttributes,\n AnalyticsTracker,\n} from '../apis';\nimport { AnalyticsContextValue } from './';\n\ntype TempGlobalEvents = {\n /**\n * Stores the most recent \"gathered\" mountpoint navigation.\n */\n mostRecentGatheredNavigation?: {\n action: string;\n subject: string;\n value?: number;\n attributes?: AnalyticsEventAttributes;\n context: AnalyticsContextValue;\n };\n /**\n * Stores the most recent routable extension render.\n */\n mostRecentRoutableExtensionRender?: {\n context: AnalyticsContextValue;\n };\n /**\n * Tracks whether or not a beforeunload event listener has already been\n * registered.\n */\n beforeUnloadRegistered: boolean;\n};\n\n/**\n * Temporary global store for select event data. Used to make `navigate` events\n * more accurate when gathered mountpoints are used.\n */\nconst globalEvents = getOrCreateGlobalSingleton<TempGlobalEvents>(\n 'core-plugin-api:analytics-tracker-events',\n () => ({\n mostRecentGatheredNavigation: undefined,\n mostRecentRoutableExtensionRender: undefined,\n beforeUnloadRegistered: false,\n }),\n);\n\n/**\n * Internal-only event representing when a routable extension is rendered.\n */\nexport const routableExtensionRenderedEvent = '_ROUTABLE-EXTENSION-RENDERED';\n\nexport class Tracker implements AnalyticsTracker {\n constructor(\n private readonly analyticsApi: AnalyticsApi,\n private context: AnalyticsContextValue = {\n routeRef: 'unknown',\n pluginId: 'root',\n extension: 'App',\n },\n ) {\n // Only register a single beforeunload event across all trackers.\n if (!globalEvents.beforeUnloadRegistered) {\n // Before the page unloads, attempt to capture any deferred navigation\n // events that haven't yet been captured.\n addEventListener(\n 'beforeunload',\n () => {\n if (globalEvents.mostRecentGatheredNavigation) {\n this.analyticsApi.captureEvent({\n ...globalEvents.mostRecentGatheredNavigation,\n ...globalEvents.mostRecentRoutableExtensionRender,\n });\n globalEvents.mostRecentGatheredNavigation = undefined;\n globalEvents.mostRecentRoutableExtensionRender = undefined;\n }\n },\n { once: true, passive: true },\n );\n\n // Prevent duplicate handlers from being registered.\n globalEvents.beforeUnloadRegistered = true;\n }\n }\n\n setContext(context: AnalyticsContextValue) {\n this.context = context;\n }\n\n captureEvent(\n action: string,\n subject: string,\n {\n value,\n attributes,\n }: { value?: number; attributes?: AnalyticsEventAttributes } = {},\n ) {\n // Never pass internal \"_routeNodeType\" context value.\n const { _routeNodeType, ...context } = this.context;\n\n // Never fire the special \"_routable-extension-rendered\" internal event.\n if (action === routableExtensionRenderedEvent) {\n // But keep track of it if we're delaying a `navigate` event for a\n // a gathered route node type.\n if (globalEvents.mostRecentGatheredNavigation) {\n globalEvents.mostRecentRoutableExtensionRender = {\n context: {\n ...context,\n extension: 'App',\n },\n };\n }\n return;\n }\n\n // If we are about to fire a real event, and we have an un-fired gathered\n // mountpoint navigation on the global store, we need to fire the navigate\n // event first, so this real event happens accurately after the navigation.\n if (globalEvents.mostRecentGatheredNavigation) {\n try {\n this.analyticsApi.captureEvent({\n ...globalEvents.mostRecentGatheredNavigation,\n ...globalEvents.mostRecentRoutableExtensionRender,\n });\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn('Error during analytics event capture. %o', e);\n }\n\n // Clear the global stores.\n globalEvents.mostRecentGatheredNavigation = undefined;\n globalEvents.mostRecentRoutableExtensionRender = undefined;\n }\n\n // Never directly fire a navigation event on a gathered route with default\n // contextual details.\n if (\n action === 'navigate' &&\n _routeNodeType === 'gathered' &&\n context.pluginId === 'root'\n ) {\n // Instead, set it on the global store.\n globalEvents.mostRecentGatheredNavigation = {\n action,\n subject,\n value,\n attributes,\n context,\n };\n return;\n }\n\n try {\n this.analyticsApi.captureEvent({\n action,\n subject,\n value,\n attributes,\n context,\n });\n } catch (e) {\n // eslint-disable-next-line no-console\n console.warn('Error during analytics event capture. %o', e);\n }\n }\n}\n","/*\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 { createApiRef } from '@backstage/core-plugin-api';\nimport { BackstagePlugin, Extension, ExtensionDataRef } from '../../wiring';\n\n/**\n * The specification for this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The specifications for a collection of app nodes is all the information needed\n * to build the tree and instantiate the nodes.\n */\nexport interface AppNodeSpec {\n readonly id: string;\n readonly attachTo: { id: string; input: string };\n readonly extension: Extension<unknown>;\n readonly disabled: boolean;\n readonly config?: unknown;\n readonly source?: BackstagePlugin;\n}\n\n/**\n * The connections from this {@link AppNode} to other nodes.\n *\n * @public\n * @remarks\n *\n * The app node edges are resolved based on the app node specs, regardless of whether\n * adjacent nodes are disabled or not. If no parent attachment is present or\n */\nexport interface AppNodeEdges {\n readonly attachedTo?: { node: AppNode; input: string };\n readonly attachments: ReadonlyMap<string, AppNode[]>;\n}\n\n/**\n * The instance of this {@link AppNode} in the {@link AppTree}.\n *\n * @public\n * @remarks\n *\n * The app node instance is created when the `factory` function of an extension is called.\n * Instances will only be present for nodes in the app that are connected to the root\n * node and not disabled\n */\nexport interface AppNodeInstance {\n /** Returns a sequence of all extension data refs that were output by this instance */\n getDataRefs(): Iterable<ExtensionDataRef<unknown>>;\n /** Get the output data for a single extension data ref */\n getData<T>(ref: ExtensionDataRef<T>): T | undefined;\n}\n\n/**\n * A node in the {@link AppTree}.\n *\n * @public\n */\nexport interface AppNode {\n /** The specification for how this node should be instantiated */\n readonly spec: AppNodeSpec;\n /** The edges from this node to other nodes in the app tree */\n readonly edges: AppNodeEdges;\n /** The instance of this node, if it was instantiated */\n readonly instance?: AppNodeInstance;\n}\n\n/**\n * The app tree containing all {@link AppNode}s of the app.\n *\n * @public\n */\nexport interface AppTree {\n /** The root node of the app */\n readonly root: AppNode;\n /** A map of all nodes in the app by ID, including orphaned or disabled nodes */\n readonly nodes: ReadonlyMap<string /* id */, AppNode>;\n /** A sequence of all nodes with a parent that is not reachable from the app root node */\n readonly orphans: Iterable<AppNode>;\n}\n\n/**\n * The API for interacting with the {@link AppTree}.\n *\n * @public\n */\nexport interface AppTreeApi {\n /**\n * Get the {@link AppTree} for the app.\n */\n getTree(): { tree: AppTree };\n}\n\n/**\n * The `ApiRef` of {@link AppTreeApi}.\n *\n * @public\n */\nexport const appTreeApiRef = createApiRef<AppTreeApi>({ id: 'core.app-tree' });\n","/*\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 { ComponentType } from 'react';\nimport { createApiRef, useApi } from '@backstage/core-plugin-api';\nimport { ComponentRef } from '../../components';\n\n/**\n * API for looking up components based on component refs.\n *\n * @public\n */\nexport interface ComponentsApi {\n // TODO: Should component refs also provide the default implementation so that we're guaranteed to get a component?\n getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T>;\n}\n\n/**\n * The `ApiRef` of {@link ComponentsApi}.\n *\n * @public\n */\nexport const componentsApiRef = createApiRef<ComponentsApi>({\n id: 'core.components',\n});\n\n/**\n * @public\n * Returns the component associated with the given ref.\n */\nexport function useComponentRef<T extends {}>(\n ref: ComponentRef<T>,\n): ComponentType<T> {\n const componentsApi = useApi(componentsApiRef);\n return componentsApi.getComponent<T>(ref);\n}\n","/*\n * Copyright 2021 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, createApiRef } from '@backstage/core-plugin-api';\nimport { AnalyticsContextValue } from '../../analytics/types';\n\n/**\n * Represents an event worth tracking in an analytics system that could inform\n * how users of a Backstage instance are using its features.\n *\n * @public\n */\nexport type AnalyticsEvent = {\n /**\n * A string that identifies the event being tracked by the type of action the\n * event represents. Be careful not to encode extra metadata in this string\n * that should instead be placed in the Analytics Context or attributes.\n * Examples include:\n *\n * - view\n * - click\n * - filter\n * - search\n * - hover\n * - scroll\n */\n action: string;\n\n /**\n * A string that uniquely identifies the object that the action is being\n * taken on. Examples include:\n *\n * - The path of the page viewed\n * - The url of the link clicked\n * - The value that was filtered by\n * - The text that was searched for\n */\n subject: string;\n\n /**\n * An optional numeric value relevant to the event that could be aggregated\n * by analytics tools. Examples include:\n *\n * - The index or position of the clicked element in an ordered list\n * - The percentage of an element that has been scrolled through\n * - The amount of time that has elapsed since a fixed point\n * - A satisfaction score on a fixed scale\n */\n value?: number;\n\n /**\n * Optional, additional attributes (representing dimensions or metrics)\n * specific to the event that could be forwarded on to analytics systems.\n */\n attributes?: AnalyticsEventAttributes;\n\n /**\n * Contextual metadata relating to where the event was captured and by whom.\n * This could include information about the route, plugin, or extension in\n * which an event was captured.\n */\n context: AnalyticsContextValue;\n};\n\n/**\n * A structure allowing other arbitrary metadata to be provided by analytics\n * event emitters.\n *\n * @public\n */\nexport type AnalyticsEventAttributes = {\n [attribute in string]: string | boolean | number;\n};\n\n/**\n * Represents a tracker with methods that can be called to track events in a\n * configured analytics service.\n *\n * @public\n */\nexport type AnalyticsTracker = {\n captureEvent: (\n action: string,\n subject: string,\n options?: {\n value?: number;\n attributes?: AnalyticsEventAttributes;\n },\n ) => void;\n};\n\n/**\n * The Analytics API is used to track user behavior in a Backstage instance.\n *\n * @remarks\n *\n * To instrument your App or Plugin, retrieve an analytics tracker using the\n * useAnalytics() hook. This will return a pre-configured AnalyticsTracker\n * with relevant methods for instrumentation.\n *\n * @public\n */\nexport type AnalyticsApi = {\n /**\n * Primary event handler responsible for compiling and forwarding events to\n * an analytics system.\n */\n captureEvent(event: AnalyticsEvent): void;\n};\n\n/**\n * The API reference of {@link AnalyticsApi}.\n *\n * @public\n */\nexport const analyticsApiRef: ApiRef<AnalyticsApi> = createApiRef({\n id: 'core.analytics',\n});\n","/*\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 React, {\n PropsWithChildren,\n ReactNode,\n Suspense,\n useEffect,\n} from 'react';\nimport { AnalyticsContext, useAnalytics } from '@backstage/core-plugin-api';\nimport { ErrorBoundary } from './ErrorBoundary';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { routableExtensionRenderedEvent } from '../../../core-plugin-api/src/analytics/Tracker';\nimport { AppNode, useComponentRef } from '../apis';\nimport { coreComponentRefs } from './ComponentRef';\n\ntype RouteTrackerProps = PropsWithChildren<{\n disableTracking?: boolean;\n}>;\n\nconst RouteTracker = (props: RouteTrackerProps) => {\n const { disableTracking, children } = props;\n const analytics = useAnalytics();\n\n // This event, never exposed to end-users of the analytics API,\n // helps inform which extension metadata gets associated with a\n // navigation event when the route navigated to is a gathered\n // mountpoint.\n useEffect(() => {\n if (disableTracking) return;\n analytics.captureEvent(routableExtensionRenderedEvent, '');\n }, [analytics, disableTracking]);\n\n return <>{children}</>;\n};\n\n/** @public */\nexport interface ExtensionBoundaryProps {\n node: AppNode;\n routable?: boolean;\n children: ReactNode;\n}\n\n/** @public */\nexport function ExtensionBoundary(props: ExtensionBoundaryProps) {\n const { node, routable, children } = props;\n\n const plugin = node.spec.source;\n const Progress = useComponentRef(coreComponentRefs.progress);\n const fallback = useComponentRef(coreComponentRefs.errorBoundaryFallback);\n\n // Skipping \"routeRef\" attribute in the new system, the extension \"id\" should provide more insight\n const attributes = {\n extensionId: node.spec.id,\n pluginId: node.spec.source?.id,\n };\n\n return (\n <Suspense fallback={<Progress />}>\n <ErrorBoundary plugin={plugin} Fallback={fallback}>\n <AnalyticsContext attributes={attributes}>\n <RouteTracker disableTracking={!routable}>{children}</RouteTracker>\n </AnalyticsContext>\n </ErrorBoundary>\n </Suspense>\n );\n}\n","/*\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\n/** @public */\nexport type ExtensionDataRef<\n TData,\n TConfig extends { optional?: true } = {},\n> = {\n id: string;\n T: TData;\n config: TConfig;\n $$type: '@backstage/ExtensionDataRef';\n};\n\n/** @public */\nexport interface ConfigurableExtensionDataRef<\n TData,\n TConfig extends { optional?: true } = {},\n> extends ExtensionDataRef<TData, TConfig> {\n optional(): ConfigurableExtensionDataRef<TData, TData & { optional: true }>;\n}\n\n// TODO: change to options object with ID.\n/** @public */\nexport function createExtensionDataRef<TData>(\n id: string,\n): ConfigurableExtensionDataRef<TData> {\n return {\n id,\n $$type: '@backstage/ExtensionDataRef',\n config: {},\n optional() {\n return { ...this, config: { ...this.config, optional: true } };\n },\n } as ConfigurableExtensionDataRef<TData>;\n}\n","/*\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 React, { ComponentType, lazy } from 'react';\nimport { ExtensionBoundary } from '../components';\nimport { PortableSchema } from '../schema';\nimport {\n createExtension,\n ResolvedExtensionInputs,\n AnyExtensionInputMap,\n createExtensionDataRef,\n ExtensionDefinition,\n} from '../wiring';\nimport { Expand } from '../types';\nimport { SignInPageProps } from '@backstage/core-plugin-api';\n\n/**\n *\n * @public\n */\nexport function createSignInPageExtension<\n TConfig extends {},\n TInputs extends AnyExtensionInputMap,\n>(options: {\n namespace?: string;\n name?: string;\n attachTo?: { id: string; input: string };\n configSchema?: PortableSchema<TConfig>;\n disabled?: boolean;\n inputs?: TInputs;\n loader: (options: {\n config: TConfig;\n inputs: Expand<ResolvedExtensionInputs<TInputs>>;\n }) => Promise<ComponentType<SignInPageProps>>;\n}): ExtensionDefinition<TConfig> {\n return createExtension({\n kind: 'sign-in-page',\n namespace: options?.namespace,\n name: options?.name,\n attachTo: options.attachTo ?? { id: 'app/router', input: 'signInPage' },\n configSchema: options.configSchema,\n inputs: options.inputs,\n disabled: options.disabled,\n output: {\n component: createSignInPageExtension.componentDataRef,\n },\n factory({ config, inputs, node }) {\n const ExtensionComponent = lazy(() =>\n options\n .loader({ config, inputs })\n .then(component => ({ default: component })),\n );\n\n return {\n component: props => (\n <ExtensionBoundary node={node} routable>\n <ExtensionComponent {...props} />\n </ExtensionBoundary>\n ),\n };\n },\n });\n}\n\n/** @public */\nexport namespace createSignInPageExtension {\n export const componentDataRef = createExtensionDataRef<\n ComponentType<SignInPageProps>\n >('core.sign-in-page.component');\n}\n","/*\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 { createContext } from 'react';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppIdentityProxy } from '../../../core-app-api/src/apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\n\nexport const InternalAppContext = createContext<\n | undefined\n | {\n appIdentityProxy: AppIdentityProxy;\n routeObjects: BackstageRouteObject[];\n }\n>(undefined);\n","/*\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 { createContext } from 'react';\nimport { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';\nimport { BackstageRouteObject } from '../routing/types';\n\nexport const InternalAppContext = createContext<\n | undefined\n | {\n routeObjects: BackstageRouteObject[];\n appIdentityProxy: AppIdentityProxy;\n }\n>(undefined);\n","/*\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 React from 'react';\nimport { createRoutesFromChildren, Route } from 'react-router-dom';\n\nexport function isReactRouterBeta(): boolean {\n const [obj] = createRoutesFromChildren(<Route index element={<div />} />);\n return !obj.index;\n}\n","/*\n * Copyright 2021 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 React, { useEffect } from 'react';\nimport { matchRoutes, useLocation } from 'react-router-dom';\nimport {\n useAnalytics,\n AnalyticsContext,\n RouteRef,\n AnalyticsEventAttributes,\n BackstagePlugin,\n} from '@backstage/core-plugin-api';\nimport { BackstageRouteObject } from './types';\n\n/**\n * Returns an extension context given the current pathname and a list of\n * Backstage route objects.\n */\nconst getExtensionContext = (\n pathname: string,\n routes: BackstageRouteObject[],\n) => {\n try {\n // Find matching routes for the given path name.\n const matches = matchRoutes(routes, { pathname });\n\n // Of the matching routes, get the last (e.g. most specific) instance of\n // the BackstageRouteObject that contains a routeRef. Filtering by routeRef\n // ensures subRouteRefs are aligned to their parent routes' context.\n const routeMatch = matches\n ?.filter(match => match?.route.routeRefs?.size > 0)\n .pop();\n const routeObject = routeMatch?.route;\n\n // If there is no route object, then allow inheritance of default context.\n if (!routeObject) {\n return undefined;\n }\n\n // If the matched route is the root route (no path), and the pathname is\n // not the path of the homepage, then inherit from the default context.\n if (routeObject.path === '' && pathname !== '/') {\n return undefined;\n }\n\n // If there is a single route ref, use it.\n let routeRef: RouteRef | undefined;\n if (routeObject.routeRefs.size === 1) {\n routeRef = routeObject.routeRefs.values().next().value;\n }\n\n // If there is a single plugin, use it.\n let plugin: BackstagePlugin | undefined;\n if (routeObject.plugins.size === 1) {\n plugin = routeObject.plugins.values().next().value;\n }\n\n const params = Object.entries(\n routeMatch?.params || {},\n ).reduce<AnalyticsEventAttributes>((acc, [key, value]) => {\n if (value !== undefined && key !== '*') {\n acc[key] = value;\n }\n return acc;\n }, {});\n\n return {\n extension: 'App',\n pluginId: plugin?.getId() || 'root',\n ...(routeRef ? { routeRef: (routeRef as { id?: string }).id } : {}),\n _routeNodeType: routeObject.element as string,\n params,\n };\n } catch {\n return undefined;\n }\n};\n\n/**\n * Performs the actual event capture on render.\n */\nconst TrackNavigation = ({\n pathname,\n search,\n hash,\n attributes,\n}: {\n pathname: string;\n search: string;\n hash: string;\n attributes?: AnalyticsEventAttributes;\n}) => {\n const analytics = useAnalytics();\n useEffect(() => {\n analytics.captureEvent('navigate', `${pathname}${search}${hash}`, {\n attributes,\n });\n }, [analytics, pathname, search, hash, attributes]);\n\n return null;\n};\n\n/**\n * Logs a \"navigate\" event with appropriate plugin-level analytics context\n * attributes each time the user navigates to a page.\n */\nexport const RouteTracker = ({\n routeObjects,\n}: {\n routeObjects: BackstageRouteObject[];\n}) => {\n const { pathname, search, hash } = useLocation();\n\n const { params, ...attributes } = getExtensionContext(\n pathname,\n routeObjects,\n ) || { params: {} };\n\n return (\n <AnalyticsContext attributes={attributes}>\n <TrackNavigation\n pathname={pathname}\n search={search}\n hash={hash}\n attributes={params}\n />\n </AnalyticsContext>\n );\n};\n","/*\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 React, { useContext, ReactNode, ComponentType, useState } from 'react';\nimport {\n attachComponentData,\n ConfigApi,\n configApiRef,\n IdentityApi,\n SignInPageProps,\n useApi,\n useApp,\n} from '@backstage/core-plugin-api';\nimport { InternalAppContext } from './InternalAppContext';\nimport { isReactRouterBeta } from './isReactRouterBeta';\nimport { RouteTracker } from '../routing/RouteTracker';\nimport { Route, Routes } from 'react-router-dom';\nimport { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';\n\n/**\n * Get the app base path from the configured app baseUrl.\n *\n * The returned path does not have a trailing slash.\n */\nexport function getBasePath(configApi: ConfigApi) {\n if (!isReactRouterBeta()) {\n // When using rr v6 stable the base path is handled through the\n // basename prop on the router component instead.\n return '';\n }\n\n return readBasePath(configApi);\n}\n\n/**\n * Read the configured base path.\n *\n * The returned path does not have a trailing slash.\n */\nfunction readBasePath(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\n// This wraps the sign-in page and waits for sign-in to be completed before rendering the app\nfunction SignInPageWrapper({\n component: Component,\n appIdentityProxy,\n children,\n}: {\n component: ComponentType<SignInPageProps>;\n appIdentityProxy: AppIdentityProxy;\n children: ReactNode;\n}) {\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n const configApi = useApi(configApiRef);\n const basePath = readBasePath(configApi);\n\n if (!identityApi) {\n return <Component onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl: basePath || '/',\n });\n return <>{children}</>;\n}\n\n/**\n * Props for the {@link AppRouter} component.\n * @public\n */\nexport interface AppRouterProps {\n children?: ReactNode;\n}\n\n/**\n * App router and sign-in page wrapper.\n *\n * @public\n * @remarks\n *\n * The AppRouter provides the routing context and renders the sign-in page.\n * Until the user has successfully signed in, this component will render\n * the sign-in page. Once the user has signed-in, it will instead render\n * the app, while providing routing and route tracking for the app.\n */\nexport function AppRouter(props: AppRouterProps) {\n const { Router: RouterComponent, SignInPage: SignInPageComponent } =\n useApp().getComponents();\n\n const configApi = useApi(configApiRef);\n const basePath = readBasePath(configApi);\n const mountPath = `${basePath}/*`;\n const internalAppContext = useContext(InternalAppContext);\n if (!internalAppContext) {\n throw new Error('AppRouter must be rendered within the AppProvider');\n }\n const { routeObjects, appIdentityProxy } = internalAppContext;\n\n // If the app hasn't configured a sign-in page, we just continue as guest.\n if (!SignInPageComponent) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl: basePath || '/' },\n );\n\n if (isReactRouterBeta()) {\n return (\n <RouterComponent>\n <RouteTracker routeObjects={routeObjects} />\n <Routes>\n <Route path={mountPath} element={<>{props.children}</>} />\n </Routes>\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent basename={basePath}>\n <RouteTracker routeObjects={routeObjects} />\n {props.children}\n </RouterComponent>\n );\n }\n\n if (isReactRouterBeta()) {\n return (\n <RouterComponent>\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n <Routes>\n <Route path={mountPath} element={<>{props.children}</>} />\n </Routes>\n </SignInPageWrapper>\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent basename={basePath}>\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {props.children}\n </SignInPageWrapper>\n </RouterComponent>\n );\n}\n\nattachComponentData(AppRouter, 'core.type', 'AppRouter');\n","/*\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 React, { ComponentType, ReactNode, useContext, useState } from 'react';\nimport { MemoryRouter, Link } from 'react-router-dom';\nimport { RenderResult, render } from '@testing-library/react';\nimport { createSpecializedApp } from '@backstage/frontend-app-api';\nimport {\n ExtensionDefinition,\n IconComponent,\n IdentityApi,\n RouteRef,\n configApiRef,\n coreExtensionData,\n createExtension,\n createExtensionInput,\n createExtensionOverrides,\n createNavItemExtension,\n useApi,\n useRouteRef,\n} from '@backstage/frontend-plugin-api';\nimport { MockConfigApi } from '@backstage/test-utils';\nimport { JsonArray, JsonObject, JsonValue } from '@backstage/types';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { resolveExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { createSignInPageExtension } from '../../../frontend-plugin-api/src/extensions/createSignInPageExtension';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { toInternalExtensionDefinition } from '../../../frontend-plugin-api/src/wiring/createExtension';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { InternalAppContext } from '../../../frontend-app-api/src/wiring/InternalAppContext';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { SignInPageProps } from '../../../core-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../core-app-api/src/app/AppRouter';\n\nconst NavItem = (props: {\n routeRef: RouteRef<undefined>;\n title: string;\n icon: IconComponent;\n}) => {\n const { routeRef, title, icon: Icon } = props;\n const to = useRouteRef(routeRef)();\n return (\n <li>\n <Link to={to}>\n <Icon /> {title}\n </Link>\n </li>\n );\n};\n\nconst TestCoreNavExtension = createExtension({\n namespace: 'app',\n name: 'nav',\n attachTo: { id: 'app/layout', input: 'nav' },\n inputs: {\n items: createExtensionInput({\n target: createNavItemExtension.targetDataRef,\n }),\n },\n output: {\n element: coreExtensionData.reactElement,\n },\n factory({ inputs }) {\n return {\n element: (\n <nav>\n <ul>\n {inputs.items.map((item, index) => (\n <NavItem\n key={index}\n icon={item.output.target.icon}\n title={item.output.target.title}\n routeRef={item.output.target.routeRef}\n />\n ))}\n </ul>\n </nav>\n ),\n };\n },\n});\n\nconst AuthenticationProvider = (props: {\n signInPage?: ComponentType<SignInPageProps>;\n children: ReactNode;\n}) => {\n const { signInPage: SignInPage, children } = props;\n const configApi = useApi(configApiRef);\n const signOutTargetUrl = getBasePath(configApi) || '/';\n\n const internalAppContext = useContext(InternalAppContext);\n if (!internalAppContext) {\n throw new Error('AppRouter must be rendered within the AppProvider');\n }\n\n const { appIdentityProxy } = internalAppContext;\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n\n if (!SignInPage) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl },\n );\n\n return children;\n }\n\n if (!identityApi) {\n return <SignInPage onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl,\n });\n\n return children;\n};\n\nconst TestCoreRouterExtension = createExtension({\n namespace: 'app',\n name: 'router',\n attachTo: { id: 'app', input: 'root' },\n inputs: {\n signInPage: createExtensionInput(\n {\n component: createSignInPageExtension.componentDataRef,\n },\n { singleton: true, optional: true },\n ),\n children: createExtensionInput(\n {\n element: coreExtensionData.reactElement,\n },\n { singleton: true },\n ),\n },\n output: {\n element: coreExtensionData.reactElement,\n },\n factory({ inputs }) {\n const SignInPage = inputs.signInPage?.output.component;\n const children = inputs.children.output.element;\n\n return {\n element: (\n <MemoryRouter>\n <AuthenticationProvider signInPage={SignInPage}>\n {children}\n </AuthenticationProvider>\n </MemoryRouter>\n ),\n };\n },\n});\n\n/** @public */\nexport class ExtensionTester {\n /** @internal */\n static forSubject<TConfig>(\n subject: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n ): ExtensionTester {\n const tester = new ExtensionTester();\n const { output, factory, ...rest } = toInternalExtensionDefinition(subject);\n // attaching to app/routes to render as index route\n const extension = createExtension({\n ...rest,\n attachTo: { id: 'app/routes', input: 'routes' },\n output: {\n ...output,\n path: coreExtensionData.routePath,\n },\n factory: params => ({\n ...factory(params),\n path: '/',\n }),\n });\n tester.add(extension, options);\n return tester;\n }\n\n readonly #extensions = new Array<{\n id: string;\n definition: ExtensionDefinition<any>;\n config?: JsonValue;\n }>();\n\n add<TConfig>(\n extension: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n ): ExtensionTester {\n const { name, namespace } = extension;\n\n const definition = {\n ...extension,\n // setting name \"test\" as fallback\n name: !namespace && !name ? 'test' : name,\n };\n\n const { id } = resolveExtensionDefinition(definition);\n\n this.#extensions.push({\n id,\n definition,\n config: options?.config as JsonValue,\n });\n\n return this;\n }\n\n render(options?: { config?: JsonObject }): RenderResult {\n const { config = {} } = options ?? {};\n\n const [subject, ...rest] = this.#extensions;\n if (!subject) {\n throw new Error(\n 'No subject found. At least one extension should be added to the tester.',\n );\n }\n\n const extensionsConfig: JsonArray = [\n ...rest.map(extension => ({\n [extension.id]: {\n config: extension.config,\n },\n })),\n {\n [subject.id]: {\n config: subject.config,\n disabled: false,\n },\n },\n ];\n\n const finalConfig = {\n ...config,\n app: {\n ...(typeof config.app === 'object' ? config.app : undefined),\n extensions: extensionsConfig,\n },\n };\n\n const app = createSpecializedApp({\n features: [\n createExtensionOverrides({\n extensions: [\n ...this.#extensions.map(extension => extension.definition),\n TestCoreNavExtension,\n TestCoreRouterExtension,\n ],\n }),\n ],\n config: new MockConfigApi(finalConfig),\n });\n\n return render(app.createRoot());\n }\n}\n\n/** @public */\nexport function createExtensionTester<TConfig>(\n subject: ExtensionDefinition<TConfig>,\n options?: { config?: TConfig },\n): ExtensionTester {\n return ExtensionTester.forSubject(subject, options);\n}\n","/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n coreExtensionData,\n createExtension,\n} from '@backstage/frontend-plugin-api';\nimport { createExtensionTester } from './createExtensionTester';\n\n/**\n * @public\n * Renders the given element in a test app, for use in unit tests.\n */\nexport function renderInTestApp(element: JSX.Element) {\n const extension = createExtension({\n namespace: 'test',\n attachTo: { id: 'app', input: 'root' },\n output: {\n element: coreExtensionData.reactElement,\n },\n factory: () => ({ element }),\n });\n const tester = createExtensionTester(extension);\n return tester.render();\n}\n"],"names":["__publicField","RouteTracker","createSignInPageExtension","InternalAppContext","createExtension","useApi","configApiRef"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@backstage/frontend-test-utils",
3
- "version": "0.1.0-next.2",
3
+ "version": "0.1.0",
4
4
  "main": "dist/index.esm.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "license": "Apache-2.0",
@@ -23,21 +23,23 @@
23
23
  "postpack": "backstage-cli package postpack"
24
24
  },
25
25
  "devDependencies": {
26
- "@backstage/cli": "^0.25.0-next.2",
27
- "@testing-library/jest-dom": "^6.0.0"
26
+ "@backstage/cli": "^0.25.0",
27
+ "@testing-library/jest-dom": "^6.0.0",
28
+ "@types/react": "*"
28
29
  },
29
30
  "files": [
30
31
  "dist"
31
32
  ],
32
33
  "dependencies": {
33
- "@backstage/frontend-app-api": "^0.4.0-next.2",
34
- "@backstage/frontend-plugin-api": "^0.4.0-next.2",
35
- "@backstage/test-utils": "^1.4.6-next.2",
34
+ "@backstage/frontend-app-api": "^0.4.0",
35
+ "@backstage/frontend-plugin-api": "^0.4.0",
36
+ "@backstage/test-utils": "^1.4.6",
36
37
  "@backstage/types": "^1.1.1"
37
38
  },
38
39
  "peerDependencies": {
39
40
  "@testing-library/react": "^12.1.3 || ^13.0.0 || ^14.0.0",
40
- "react": "^16.13.1 || ^17.0.0 || ^18.0.0"
41
+ "react": "^16.13.1 || ^17.0.0 || ^18.0.0",
42
+ "react-router-dom": "6.0.0-beta.0 || ^6.3.0"
41
43
  },
42
44
  "module": "./dist/index.esm.js"
43
45
  }