@backstage/test-utils 0.1.20 → 0.1.24
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 +97 -0
- package/dist/index.d.ts +104 -14
- package/dist/index.esm.js +291 -58
- package/dist/index.esm.js.map +1 -1
- package/package.json +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,102 @@
|
|
|
1
1
|
# @backstage/test-utils
|
|
2
2
|
|
|
3
|
+
## 0.1.24
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used.
|
|
8
|
+
- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/core-plugin-api@0.3.0
|
|
11
|
+
- @backstage/core-app-api@0.2.0
|
|
12
|
+
|
|
13
|
+
## 0.1.23
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 000190de69: The `ApiRegistry` from `@backstage/core-app-api` class has been deprecated and will be removed in a future release. To replace it, we have introduced two new helpers that are exported from `@backstage/test-utils`, namely `TestApiProvider` and `TestApiRegistry`.
|
|
18
|
+
|
|
19
|
+
These two new helpers are more tailored for writing tests and development setups, as they allow for partial implementations of each of the APIs.
|
|
20
|
+
|
|
21
|
+
When migrating existing code it is typically best to prefer usage of `TestApiProvider` when possible, so for example the following code:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
render(
|
|
25
|
+
<ApiProvider
|
|
26
|
+
apis={ApiRegistry.from([
|
|
27
|
+
[identityApiRef, mockIdentityApi as unknown as IdentityApi]
|
|
28
|
+
])}
|
|
29
|
+
>
|
|
30
|
+
{...}
|
|
31
|
+
</ApiProvider>
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Would be migrated to this:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
render(
|
|
39
|
+
<TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>
|
|
40
|
+
{...}
|
|
41
|
+
</TestApiProvider>
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
In cases where the `ApiProvider` is used in a more standalone way, for example to reuse a set of APIs across multiple tests, the `TestApiRegistry` can be used instead. Note that the `TestApiRegistry` only has a single static factory method, `.from()`, and it is slightly different from the existing `.from()` method on `ApiRegistry` in that it doesn't require the API pairs to be wrapped in an outer array.
|
|
46
|
+
|
|
47
|
+
Usage that looks like this:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const apis = ApiRegistry.with(
|
|
51
|
+
identityApiRef,
|
|
52
|
+
mockIdentityApi as unknown as IdentityApi,
|
|
53
|
+
).with(configApiRef, new ConfigReader({}));
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
OR like this:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const apis = ApiRegistry.from([
|
|
60
|
+
[identityApiRef, mockIdentityApi as unknown as IdentityApi],
|
|
61
|
+
[configApiRef, new ConfigReader({})],
|
|
62
|
+
]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Would be migrated to this:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
const apis = TestApiRegistry.from(
|
|
69
|
+
[identityApiRef, mockIdentityApi],
|
|
70
|
+
[configApiRef, new ConfigReader({})],
|
|
71
|
+
);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If your app is still using the `ApiRegistry` to construct the `apis` for `createApp`, we recommend that you move over to use the new method of supplying API factories instead, using `createApiFactory`.
|
|
75
|
+
|
|
76
|
+
- Updated dependencies
|
|
77
|
+
- @backstage/core-app-api@0.1.23
|
|
78
|
+
- @backstage/core-plugin-api@0.2.1
|
|
79
|
+
|
|
80
|
+
## 0.1.22
|
|
81
|
+
|
|
82
|
+
### Patch Changes
|
|
83
|
+
|
|
84
|
+
- 0b1de52732: Migrated to using new `ErrorApiError` and `ErrorApiErrorContext` names.
|
|
85
|
+
- 2dd2a7b2cc: Migrated to using `createSpecializedApp`.
|
|
86
|
+
- Updated dependencies
|
|
87
|
+
- @backstage/core-plugin-api@0.2.0
|
|
88
|
+
- @backstage/core-app-api@0.1.21
|
|
89
|
+
|
|
90
|
+
## 0.1.21
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- 71fd5cd735: Update Keyboard deprecation with a link to the recommended successor
|
|
95
|
+
- Updated dependencies
|
|
96
|
+
- @backstage/theme@0.2.13
|
|
97
|
+
- @backstage/core-plugin-api@0.1.13
|
|
98
|
+
- @backstage/core-app-api@0.1.20
|
|
99
|
+
|
|
3
100
|
## 0.1.20
|
|
4
101
|
|
|
5
102
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnalyticsApi, AnalyticsEvent,
|
|
1
|
+
import { AnalyticsApi, AnalyticsEvent, ErrorApiError, ErrorApiErrorContext, ErrorApi, StorageApi, StorageValueChange, RouteRef, ExternalRouteRef, ApiHolder, ApiRef } from '@backstage/core-plugin-api';
|
|
2
2
|
import { Observable } from '@backstage/types';
|
|
3
3
|
import { ComponentType, ReactNode, ReactElement } from 'react';
|
|
4
4
|
import { RenderResult } from '@testing-library/react';
|
|
@@ -6,11 +6,12 @@ import { RenderResult } from '@testing-library/react';
|
|
|
6
6
|
/**
|
|
7
7
|
* Mock implementation of {@link core-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.
|
|
8
8
|
* Use getEvents in tests to verify captured events.
|
|
9
|
+
*
|
|
9
10
|
* @public
|
|
10
11
|
*/
|
|
11
12
|
declare class MockAnalyticsApi implements AnalyticsApi {
|
|
12
13
|
private events;
|
|
13
|
-
captureEvent(
|
|
14
|
+
captureEvent(event: AnalyticsEvent): void;
|
|
14
15
|
getEvents(): AnalyticsEvent[];
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -22,12 +23,12 @@ declare type MockErrorApiOptions = {
|
|
|
22
23
|
collect?: boolean;
|
|
23
24
|
};
|
|
24
25
|
/**
|
|
25
|
-
* ErrorWithContext contains error and
|
|
26
|
+
* ErrorWithContext contains error and ErrorApiErrorContext
|
|
26
27
|
* @public
|
|
27
28
|
*/
|
|
28
29
|
declare type ErrorWithContext = {
|
|
29
|
-
error:
|
|
30
|
-
context?:
|
|
30
|
+
error: ErrorApiError;
|
|
31
|
+
context?: ErrorApiErrorContext;
|
|
31
32
|
};
|
|
32
33
|
/**
|
|
33
34
|
* Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.
|
|
@@ -39,10 +40,10 @@ declare class MockErrorApi implements ErrorApi {
|
|
|
39
40
|
private readonly errors;
|
|
40
41
|
private readonly waiters;
|
|
41
42
|
constructor(options?: MockErrorApiOptions);
|
|
42
|
-
post(error:
|
|
43
|
+
post(error: ErrorApiError, context?: ErrorApiErrorContext): void;
|
|
43
44
|
error$(): Observable<{
|
|
44
|
-
error:
|
|
45
|
-
context?:
|
|
45
|
+
error: ErrorApiError;
|
|
46
|
+
context?: ErrorApiErrorContext;
|
|
46
47
|
}>;
|
|
47
48
|
getErrors(): ErrorWithContext[];
|
|
48
49
|
waitForError(pattern: RegExp, timeoutMs?: number): Promise<ErrorWithContext>;
|
|
@@ -77,8 +78,8 @@ declare class MockStorageApi implements StorageApi {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/**
|
|
80
|
-
* This is a mocking method suggested in the Jest
|
|
81
|
-
* It can be used to mock values
|
|
81
|
+
* This is a mocking method suggested in the Jest docs, as it is not implemented in JSDOM yet.
|
|
82
|
+
* It can be used to mock values for the MUI `useMediaQuery` hook if it is used in a tested component.
|
|
82
83
|
*
|
|
83
84
|
* For issues checkout the documentation:
|
|
84
85
|
* https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
|
@@ -88,8 +89,8 @@ declare class MockStorageApi implements StorageApi {
|
|
|
88
89
|
*
|
|
89
90
|
* @public
|
|
90
91
|
*/
|
|
91
|
-
declare function mockBreakpoint(
|
|
92
|
-
matches
|
|
92
|
+
declare function mockBreakpoint(options: {
|
|
93
|
+
matches: boolean;
|
|
93
94
|
}): void;
|
|
94
95
|
|
|
95
96
|
/**
|
|
@@ -165,7 +166,7 @@ declare function setupRequestMockHandlers(worker: {
|
|
|
165
166
|
|
|
166
167
|
/**
|
|
167
168
|
* @public
|
|
168
|
-
* @deprecated
|
|
169
|
+
* @deprecated superseded by {@link @testing-library/user-event#userEvent}
|
|
169
170
|
*/
|
|
170
171
|
declare class Keyboard {
|
|
171
172
|
static type(target: any, input: any): Promise<void>;
|
|
@@ -244,4 +245,93 @@ declare function withLogCollector<T extends LogFuncs>(logsToCollect: T[], callba
|
|
|
244
245
|
*/
|
|
245
246
|
declare function renderWithEffects(nodes: ReactElement): Promise<RenderResult>;
|
|
246
247
|
|
|
247
|
-
|
|
248
|
+
/** @ignore */
|
|
249
|
+
declare type TestApiProviderPropsApiPair<TApi> = TApi extends infer TImpl ? readonly [ApiRef<TApi>, Partial<TImpl>] : never;
|
|
250
|
+
/** @ignore */
|
|
251
|
+
declare type TestApiProviderPropsApiPairs<TApiPairs> = {
|
|
252
|
+
[TIndex in keyof TApiPairs]: TestApiProviderPropsApiPair<TApiPairs[TIndex]>;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Properties for the {@link TestApiProvider} component.
|
|
256
|
+
*
|
|
257
|
+
* @public
|
|
258
|
+
*/
|
|
259
|
+
declare type TestApiProviderProps<TApiPairs extends any[]> = {
|
|
260
|
+
apis: readonly [...TestApiProviderPropsApiPairs<TApiPairs>];
|
|
261
|
+
children: ReactNode;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* The `TestApiRegistry` is an {@link @backstage/core-plugin-api#ApiHolder} implementation
|
|
265
|
+
* that is particularly well suited for development and test environments such as
|
|
266
|
+
* unit tests, storybooks, and isolated plugin development setups.
|
|
267
|
+
*
|
|
268
|
+
* @public
|
|
269
|
+
*/
|
|
270
|
+
declare class TestApiRegistry implements ApiHolder {
|
|
271
|
+
private readonly apis;
|
|
272
|
+
/**
|
|
273
|
+
* Creates a new {@link TestApiRegistry} with a list of API implementation pairs.
|
|
274
|
+
*
|
|
275
|
+
* Similar to the {@link TestApiProvider}, there is no need to provide a full
|
|
276
|
+
* implementation of each API, it's enough to implement the methods that are tested.
|
|
277
|
+
*
|
|
278
|
+
* @example
|
|
279
|
+
* ```ts
|
|
280
|
+
* const apis = TestApiRegistry.from(
|
|
281
|
+
* [configApiRef, new ConfigReader({})],
|
|
282
|
+
* [identityApiRef, { getUserId: () => 'tester' }],
|
|
283
|
+
* );
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* @public
|
|
287
|
+
* @param apis - A list of pairs mapping an ApiRef to its respective implementation.
|
|
288
|
+
*/
|
|
289
|
+
static from<TApiPairs extends any[]>(...apis: readonly [...TestApiProviderPropsApiPairs<TApiPairs>]): TestApiRegistry;
|
|
290
|
+
private constructor();
|
|
291
|
+
/**
|
|
292
|
+
* Returns an implementation of the API.
|
|
293
|
+
*
|
|
294
|
+
* @public
|
|
295
|
+
*/
|
|
296
|
+
get<T>(api: ApiRef<T>): T | undefined;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The `TestApiProvider` is a Utility API context provider that is particularly
|
|
300
|
+
* well suited for development and test environments such as unit tests, storybooks,
|
|
301
|
+
* and isolated plugin development setups.
|
|
302
|
+
*
|
|
303
|
+
* It lets you provide any number of API implementations, without necessarily
|
|
304
|
+
* having to fully implement each of the APIs.
|
|
305
|
+
*
|
|
306
|
+
* A migration from `ApiRegistry` and `ApiProvider` might look like this, from:
|
|
307
|
+
*
|
|
308
|
+
* ```tsx
|
|
309
|
+
* renderInTestApp(
|
|
310
|
+
* <ApiProvider
|
|
311
|
+
* apis={ApiRegistry.from([
|
|
312
|
+
* [identityApiRef, mockIdentityApi as unknown as IdentityApi]
|
|
313
|
+
* ])}
|
|
314
|
+
* >
|
|
315
|
+
* {...}
|
|
316
|
+
* </ApiProvider>
|
|
317
|
+
* )
|
|
318
|
+
* ```
|
|
319
|
+
*
|
|
320
|
+
* To the following:
|
|
321
|
+
*
|
|
322
|
+
* ```tsx
|
|
323
|
+
* renderInTestApp(
|
|
324
|
+
* <TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>
|
|
325
|
+
* {...}
|
|
326
|
+
* </TestApiProvider>
|
|
327
|
+
* )
|
|
328
|
+
* ```
|
|
329
|
+
*
|
|
330
|
+
* Note that the cast to `IdentityApi` is no longer needed as long as the mock API
|
|
331
|
+
* implements a subset of the `IdentityApi`.
|
|
332
|
+
*
|
|
333
|
+
* @public
|
|
334
|
+
**/
|
|
335
|
+
declare const TestApiProvider: <T extends any[]>(props: TestApiProviderProps<T>) => JSX.Element;
|
|
336
|
+
|
|
337
|
+
export { AsyncLogCollector, CollectedLogs, ErrorWithContext, Keyboard, LogCollector, LogFuncs, MockAnalyticsApi, MockErrorApi, MockErrorApiOptions, MockStorageApi, MockStorageBucket, SyncLogCollector, TestApiProvider, TestApiProviderProps, TestApiRegistry, TestAppOptions, mockBreakpoint, msw, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
package/dist/index.esm.js
CHANGED
|
@@ -3,28 +3,25 @@ import React from 'react';
|
|
|
3
3
|
import { MemoryRouter } from 'react-router';
|
|
4
4
|
import { Route } from 'react-router-dom';
|
|
5
5
|
import { lightTheme } from '@backstage/theme';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
6
|
+
import { ThemeProvider } from '@material-ui/core/styles';
|
|
7
|
+
import { CssBaseline } from '@material-ui/core';
|
|
8
|
+
import MockIcon from '@material-ui/icons/AcUnit';
|
|
9
|
+
import { UrlPatternDiscovery, AlertApiForwarder, NoOpAnalyticsApi, ErrorAlerter, ErrorApiForwarder, UnhandledErrorForwarder, WebStorage, OAuthRequestManager, GoogleAuth, MicrosoftAuth, GithubAuth, OktaAuth, GitlabAuth, Auth0Auth, OAuth2, SamlAuth, OneLoginAuth, BitbucketAuth, AtlassianAuth, createSpecializedApp, ApiProvider } from '@backstage/core-app-api';
|
|
10
|
+
import { createApiFactory, discoveryApiRef, configApiRef, alertApiRef, analyticsApiRef, errorApiRef, storageApiRef, oauthRequestApiRef, googleAuthApiRef, microsoftAuthApiRef, githubAuthApiRef, oktaAuthApiRef, gitlabAuthApiRef, auth0AuthApiRef, oauth2ApiRef, samlAuthApiRef, oneloginAuthApiRef, oidcAuthApiRef, bitbucketAuthApiRef, atlassianAuthApiRef, createRouteRef, attachComponentData } from '@backstage/core-plugin-api';
|
|
11
|
+
import { act, render, fireEvent } from '@testing-library/react';
|
|
10
12
|
|
|
11
13
|
class MockAnalyticsApi {
|
|
12
14
|
constructor() {
|
|
13
15
|
this.events = [];
|
|
14
16
|
}
|
|
15
|
-
captureEvent({
|
|
16
|
-
action,
|
|
17
|
-
subject,
|
|
18
|
-
value,
|
|
19
|
-
attributes,
|
|
20
|
-
context
|
|
21
|
-
}) {
|
|
17
|
+
captureEvent(event) {
|
|
18
|
+
const { action, subject, value, attributes, context } = event;
|
|
22
19
|
this.events.push({
|
|
23
20
|
action,
|
|
24
21
|
subject,
|
|
25
22
|
context,
|
|
26
|
-
...value !== void 0 ? {value} : {},
|
|
27
|
-
...attributes !== void 0 ? {attributes} : {}
|
|
23
|
+
...value !== void 0 ? { value } : {},
|
|
24
|
+
...attributes !== void 0 ? { attributes } : {}
|
|
28
25
|
});
|
|
29
26
|
}
|
|
30
27
|
getEvents() {
|
|
@@ -33,8 +30,8 @@ class MockAnalyticsApi {
|
|
|
33
30
|
}
|
|
34
31
|
|
|
35
32
|
const nullObservable = {
|
|
36
|
-
subscribe: () => ({unsubscribe: () => {
|
|
37
|
-
}, closed: true}),
|
|
33
|
+
subscribe: () => ({ unsubscribe: () => {
|
|
34
|
+
}, closed: true }),
|
|
38
35
|
[Symbol.observable]() {
|
|
39
36
|
return this;
|
|
40
37
|
}
|
|
@@ -43,15 +40,15 @@ class MockErrorApi {
|
|
|
43
40
|
constructor(options = {}) {
|
|
44
41
|
this.options = options;
|
|
45
42
|
this.errors = new Array();
|
|
46
|
-
this.waiters = new Set();
|
|
43
|
+
this.waiters = /* @__PURE__ */ new Set();
|
|
47
44
|
}
|
|
48
45
|
post(error, context) {
|
|
49
46
|
if (this.options.collect) {
|
|
50
|
-
this.errors.push({error, context});
|
|
47
|
+
this.errors.push({ error, context });
|
|
51
48
|
for (const waiter of this.waiters) {
|
|
52
49
|
if (waiter.pattern.test(error.message)) {
|
|
53
50
|
this.waiters.delete(waiter);
|
|
54
|
-
waiter.resolve({error, context});
|
|
51
|
+
waiter.resolve({ error, context });
|
|
55
52
|
}
|
|
56
53
|
}
|
|
57
54
|
return;
|
|
@@ -69,14 +66,14 @@ class MockErrorApi {
|
|
|
69
66
|
setTimeout(() => {
|
|
70
67
|
reject(new Error("Timed out waiting for error"));
|
|
71
68
|
}, timeoutMs);
|
|
72
|
-
this.waiters.add({resolve, pattern});
|
|
69
|
+
this.waiters.add({ resolve, pattern });
|
|
73
70
|
});
|
|
74
71
|
}
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
class MockStorageApi {
|
|
78
75
|
constructor(namespace, bucketStorageApis, data) {
|
|
79
|
-
this.subscribers = new Set();
|
|
76
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
80
77
|
this.observable = new ObservableImpl((subscriber) => {
|
|
81
78
|
this.subscribers.add(subscriber);
|
|
82
79
|
return () => {
|
|
@@ -85,10 +82,10 @@ class MockStorageApi {
|
|
|
85
82
|
});
|
|
86
83
|
this.namespace = namespace;
|
|
87
84
|
this.bucketStorageApis = bucketStorageApis;
|
|
88
|
-
this.data = {...data};
|
|
85
|
+
this.data = { ...data };
|
|
89
86
|
}
|
|
90
87
|
static create(data) {
|
|
91
|
-
return new MockStorageApi("", new Map(), data);
|
|
88
|
+
return new MockStorageApi("", /* @__PURE__ */ new Map(), data);
|
|
92
89
|
}
|
|
93
90
|
forBucket(name) {
|
|
94
91
|
if (!this.bucketStorageApis.has(name)) {
|
|
@@ -101,14 +98,14 @@ class MockStorageApi {
|
|
|
101
98
|
}
|
|
102
99
|
async set(key, data) {
|
|
103
100
|
this.data[this.getKeyName(key)] = data;
|
|
104
|
-
this.notifyChanges({key, newValue: data});
|
|
101
|
+
this.notifyChanges({ key, newValue: data });
|
|
105
102
|
}
|
|
106
103
|
async remove(key) {
|
|
107
104
|
delete this.data[this.getKeyName(key)];
|
|
108
|
-
this.notifyChanges({key, newValue: void 0});
|
|
105
|
+
this.notifyChanges({ key, newValue: void 0 });
|
|
109
106
|
}
|
|
110
107
|
observe$(key) {
|
|
111
|
-
return this.observable.filter(({key: messageKey}) => messageKey === key);
|
|
108
|
+
return this.observable.filter(({ key: messageKey }) => messageKey === key);
|
|
112
109
|
}
|
|
113
110
|
getKeyName(key) {
|
|
114
111
|
return `${this.namespace}/${encodeURIComponent(key)}`;
|
|
@@ -120,19 +117,22 @@ class MockStorageApi {
|
|
|
120
117
|
}
|
|
121
118
|
}
|
|
122
119
|
|
|
123
|
-
function mockBreakpoint(
|
|
120
|
+
function mockBreakpoint(options) {
|
|
124
121
|
Object.defineProperty(window, "matchMedia", {
|
|
125
122
|
writable: true,
|
|
126
|
-
value: jest.fn().mockImplementation((query) =>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
123
|
+
value: jest.fn().mockImplementation((query) => {
|
|
124
|
+
var _a;
|
|
125
|
+
return {
|
|
126
|
+
matches: (_a = options.matches) != null ? _a : false,
|
|
127
|
+
media: query,
|
|
128
|
+
onchange: null,
|
|
129
|
+
addListener: jest.fn(),
|
|
130
|
+
removeListener: jest.fn(),
|
|
131
|
+
addEventListener: jest.fn(),
|
|
132
|
+
removeEventListener: jest.fn(),
|
|
133
|
+
dispatchEvent: jest.fn()
|
|
134
|
+
};
|
|
135
|
+
})
|
|
136
136
|
});
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -144,18 +144,229 @@ async function renderWithEffects(nodes) {
|
|
|
144
144
|
return value;
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
const defaultApis = [
|
|
148
|
+
createApiFactory({
|
|
149
|
+
api: discoveryApiRef,
|
|
150
|
+
deps: { configApi: configApiRef },
|
|
151
|
+
factory: ({ configApi }) => UrlPatternDiscovery.compile(`${configApi.getString("backend.baseUrl")}/api/{{ pluginId }}`)
|
|
152
|
+
}),
|
|
153
|
+
createApiFactory(alertApiRef, new AlertApiForwarder()),
|
|
154
|
+
createApiFactory(analyticsApiRef, new NoOpAnalyticsApi()),
|
|
155
|
+
createApiFactory({
|
|
156
|
+
api: errorApiRef,
|
|
157
|
+
deps: { alertApi: alertApiRef },
|
|
158
|
+
factory: ({ alertApi }) => {
|
|
159
|
+
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
|
|
160
|
+
UnhandledErrorForwarder.forward(errorApi, { hidden: false });
|
|
161
|
+
return errorApi;
|
|
162
|
+
}
|
|
163
|
+
}),
|
|
164
|
+
createApiFactory({
|
|
165
|
+
api: storageApiRef,
|
|
166
|
+
deps: { errorApi: errorApiRef },
|
|
167
|
+
factory: ({ errorApi }) => WebStorage.create({ errorApi })
|
|
168
|
+
}),
|
|
169
|
+
createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),
|
|
170
|
+
createApiFactory({
|
|
171
|
+
api: googleAuthApiRef,
|
|
172
|
+
deps: {
|
|
173
|
+
discoveryApi: discoveryApiRef,
|
|
174
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
175
|
+
configApi: configApiRef
|
|
176
|
+
},
|
|
177
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GoogleAuth.create({
|
|
178
|
+
discoveryApi,
|
|
179
|
+
oauthRequestApi,
|
|
180
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
181
|
+
})
|
|
182
|
+
}),
|
|
183
|
+
createApiFactory({
|
|
184
|
+
api: microsoftAuthApiRef,
|
|
185
|
+
deps: {
|
|
186
|
+
discoveryApi: discoveryApiRef,
|
|
187
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
188
|
+
configApi: configApiRef
|
|
189
|
+
},
|
|
190
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => MicrosoftAuth.create({
|
|
191
|
+
discoveryApi,
|
|
192
|
+
oauthRequestApi,
|
|
193
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
194
|
+
})
|
|
195
|
+
}),
|
|
196
|
+
createApiFactory({
|
|
197
|
+
api: githubAuthApiRef,
|
|
198
|
+
deps: {
|
|
199
|
+
discoveryApi: discoveryApiRef,
|
|
200
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
201
|
+
configApi: configApiRef
|
|
202
|
+
},
|
|
203
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GithubAuth.create({
|
|
204
|
+
discoveryApi,
|
|
205
|
+
oauthRequestApi,
|
|
206
|
+
defaultScopes: ["read:user"],
|
|
207
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
208
|
+
})
|
|
209
|
+
}),
|
|
210
|
+
createApiFactory({
|
|
211
|
+
api: oktaAuthApiRef,
|
|
212
|
+
deps: {
|
|
213
|
+
discoveryApi: discoveryApiRef,
|
|
214
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
215
|
+
configApi: configApiRef
|
|
216
|
+
},
|
|
217
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OktaAuth.create({
|
|
218
|
+
discoveryApi,
|
|
219
|
+
oauthRequestApi,
|
|
220
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
221
|
+
})
|
|
222
|
+
}),
|
|
223
|
+
createApiFactory({
|
|
224
|
+
api: gitlabAuthApiRef,
|
|
225
|
+
deps: {
|
|
226
|
+
discoveryApi: discoveryApiRef,
|
|
227
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
228
|
+
configApi: configApiRef
|
|
229
|
+
},
|
|
230
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GitlabAuth.create({
|
|
231
|
+
discoveryApi,
|
|
232
|
+
oauthRequestApi,
|
|
233
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
234
|
+
})
|
|
235
|
+
}),
|
|
236
|
+
createApiFactory({
|
|
237
|
+
api: auth0AuthApiRef,
|
|
238
|
+
deps: {
|
|
239
|
+
discoveryApi: discoveryApiRef,
|
|
240
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
241
|
+
configApi: configApiRef
|
|
242
|
+
},
|
|
243
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => Auth0Auth.create({
|
|
244
|
+
discoveryApi,
|
|
245
|
+
oauthRequestApi,
|
|
246
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
247
|
+
})
|
|
248
|
+
}),
|
|
249
|
+
createApiFactory({
|
|
250
|
+
api: oauth2ApiRef,
|
|
251
|
+
deps: {
|
|
252
|
+
discoveryApi: discoveryApiRef,
|
|
253
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
254
|
+
configApi: configApiRef
|
|
255
|
+
},
|
|
256
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
|
|
257
|
+
discoveryApi,
|
|
258
|
+
oauthRequestApi,
|
|
259
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
260
|
+
})
|
|
261
|
+
}),
|
|
262
|
+
createApiFactory({
|
|
263
|
+
api: samlAuthApiRef,
|
|
264
|
+
deps: {
|
|
265
|
+
discoveryApi: discoveryApiRef,
|
|
266
|
+
configApi: configApiRef
|
|
267
|
+
},
|
|
268
|
+
factory: ({ discoveryApi, configApi }) => SamlAuth.create({
|
|
269
|
+
discoveryApi,
|
|
270
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
271
|
+
})
|
|
272
|
+
}),
|
|
273
|
+
createApiFactory({
|
|
274
|
+
api: oneloginAuthApiRef,
|
|
275
|
+
deps: {
|
|
276
|
+
discoveryApi: discoveryApiRef,
|
|
277
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
278
|
+
configApi: configApiRef
|
|
279
|
+
},
|
|
280
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OneLoginAuth.create({
|
|
281
|
+
discoveryApi,
|
|
282
|
+
oauthRequestApi,
|
|
283
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
284
|
+
})
|
|
285
|
+
}),
|
|
286
|
+
createApiFactory({
|
|
287
|
+
api: oidcAuthApiRef,
|
|
288
|
+
deps: {
|
|
289
|
+
discoveryApi: discoveryApiRef,
|
|
290
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
291
|
+
configApi: configApiRef
|
|
292
|
+
},
|
|
293
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
|
|
294
|
+
discoveryApi,
|
|
295
|
+
oauthRequestApi,
|
|
296
|
+
provider: {
|
|
297
|
+
id: "oidc",
|
|
298
|
+
title: "Your Identity Provider",
|
|
299
|
+
icon: () => null
|
|
300
|
+
},
|
|
301
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
302
|
+
})
|
|
303
|
+
}),
|
|
304
|
+
createApiFactory({
|
|
305
|
+
api: bitbucketAuthApiRef,
|
|
306
|
+
deps: {
|
|
307
|
+
discoveryApi: discoveryApiRef,
|
|
308
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
309
|
+
configApi: configApiRef
|
|
310
|
+
},
|
|
311
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => BitbucketAuth.create({
|
|
312
|
+
discoveryApi,
|
|
313
|
+
oauthRequestApi,
|
|
314
|
+
defaultScopes: ["team"],
|
|
315
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
316
|
+
})
|
|
317
|
+
}),
|
|
318
|
+
createApiFactory({
|
|
319
|
+
api: atlassianAuthApiRef,
|
|
320
|
+
deps: {
|
|
321
|
+
discoveryApi: discoveryApiRef,
|
|
322
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
323
|
+
configApi: configApiRef
|
|
324
|
+
},
|
|
325
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
|
326
|
+
return AtlassianAuth.create({
|
|
327
|
+
discoveryApi,
|
|
328
|
+
oauthRequestApi,
|
|
329
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
];
|
|
334
|
+
|
|
147
335
|
const mockApis = [
|
|
148
336
|
createApiFactory(errorApiRef, new MockErrorApi()),
|
|
149
337
|
createApiFactory(storageApiRef, MockStorageApi.create())
|
|
150
338
|
];
|
|
151
339
|
|
|
152
|
-
const
|
|
340
|
+
const mockIcons = {
|
|
341
|
+
"kind:api": MockIcon,
|
|
342
|
+
"kind:component": MockIcon,
|
|
343
|
+
"kind:domain": MockIcon,
|
|
344
|
+
"kind:group": MockIcon,
|
|
345
|
+
"kind:location": MockIcon,
|
|
346
|
+
"kind:system": MockIcon,
|
|
347
|
+
"kind:user": MockIcon,
|
|
348
|
+
brokenImage: MockIcon,
|
|
349
|
+
catalog: MockIcon,
|
|
350
|
+
scaffolder: MockIcon,
|
|
351
|
+
techdocs: MockIcon,
|
|
352
|
+
search: MockIcon,
|
|
353
|
+
chat: MockIcon,
|
|
354
|
+
dashboard: MockIcon,
|
|
355
|
+
docs: MockIcon,
|
|
356
|
+
email: MockIcon,
|
|
357
|
+
github: MockIcon,
|
|
358
|
+
group: MockIcon,
|
|
359
|
+
help: MockIcon,
|
|
360
|
+
user: MockIcon,
|
|
361
|
+
warning: MockIcon
|
|
362
|
+
};
|
|
363
|
+
const ErrorBoundaryFallback = ({ error }) => {
|
|
153
364
|
throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`);
|
|
154
365
|
};
|
|
155
366
|
const NotFoundErrorPage = () => {
|
|
156
367
|
throw new Error("Reached NotFound Page");
|
|
157
368
|
};
|
|
158
|
-
const BootErrorPage = ({step, error}) => {
|
|
369
|
+
const BootErrorPage = ({ step, error }) => {
|
|
159
370
|
throw new Error(`Reached BootError Page at step ${step} with error ${error}`);
|
|
160
371
|
};
|
|
161
372
|
const Progress = () => /* @__PURE__ */ React.createElement("div", {
|
|
@@ -166,33 +377,37 @@ function isExternalRouteRef(routeRef) {
|
|
|
166
377
|
}
|
|
167
378
|
function wrapInTestApp(Component, options = {}) {
|
|
168
379
|
var _a;
|
|
169
|
-
const {routeEntries = ["/"]} = options;
|
|
170
|
-
const boundRoutes = new Map();
|
|
171
|
-
const app =
|
|
380
|
+
const { routeEntries = ["/"] } = options;
|
|
381
|
+
const boundRoutes = /* @__PURE__ */ new Map();
|
|
382
|
+
const app = createSpecializedApp({
|
|
172
383
|
apis: mockApis,
|
|
384
|
+
defaultApis,
|
|
173
385
|
configLoader: false,
|
|
174
386
|
components: {
|
|
175
387
|
Progress,
|
|
176
388
|
BootErrorPage,
|
|
177
389
|
NotFoundErrorPage,
|
|
178
390
|
ErrorBoundaryFallback,
|
|
179
|
-
Router: ({children}) => /* @__PURE__ */ React.createElement(MemoryRouter, {
|
|
391
|
+
Router: ({ children }) => /* @__PURE__ */ React.createElement(MemoryRouter, {
|
|
180
392
|
initialEntries: routeEntries,
|
|
181
393
|
children
|
|
182
394
|
})
|
|
183
395
|
},
|
|
396
|
+
icons: mockIcons,
|
|
184
397
|
plugins: [],
|
|
185
398
|
themes: [
|
|
186
399
|
{
|
|
187
400
|
id: "light",
|
|
188
|
-
theme: lightTheme,
|
|
189
401
|
title: "Test App Theme",
|
|
190
|
-
variant: "light"
|
|
402
|
+
variant: "light",
|
|
403
|
+
Provider: ({ children }) => /* @__PURE__ */ React.createElement(ThemeProvider, {
|
|
404
|
+
theme: lightTheme
|
|
405
|
+
}, /* @__PURE__ */ React.createElement(CssBaseline, null, children))
|
|
191
406
|
}
|
|
192
407
|
],
|
|
193
|
-
bindRoutes: ({bind}) => {
|
|
408
|
+
bindRoutes: ({ bind }) => {
|
|
194
409
|
for (const [externalRef, absoluteRef] of boundRoutes) {
|
|
195
|
-
bind({ref: externalRef}, {
|
|
410
|
+
bind({ ref: externalRef }, {
|
|
196
411
|
ref: absoluteRef
|
|
197
412
|
});
|
|
198
413
|
}
|
|
@@ -207,7 +422,7 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
207
422
|
const routeElements = Object.entries((_a = options.mountedRoutes) != null ? _a : {}).map(([path, routeRef]) => {
|
|
208
423
|
const Page = () => /* @__PURE__ */ React.createElement("div", null, "Mounted at ", path);
|
|
209
424
|
if (isExternalRouteRef(routeRef)) {
|
|
210
|
-
const absoluteRef = createRouteRef({id: "id"});
|
|
425
|
+
const absoluteRef = createRouteRef({ id: "id" });
|
|
211
426
|
boundRoutes.set(routeRef, absoluteRef);
|
|
212
427
|
attachComponentData(Page, "core.mountPoint", absoluteRef);
|
|
213
428
|
} else {
|
|
@@ -236,7 +451,7 @@ const msw = {
|
|
|
236
451
|
}
|
|
237
452
|
};
|
|
238
453
|
function setupRequestMockHandlers(worker) {
|
|
239
|
-
beforeAll(() => worker.listen({onUnhandledRequest: "error"}));
|
|
454
|
+
beforeAll(() => worker.listen({ onUnhandledRequest: "error" }));
|
|
240
455
|
afterAll(() => worker.close());
|
|
241
456
|
afterEach(() => worker.resetHandlers());
|
|
242
457
|
}
|
|
@@ -252,7 +467,7 @@ class Keyboard {
|
|
|
252
467
|
await new Keyboard(target).type(input);
|
|
253
468
|
}
|
|
254
469
|
static async typeDebug(target, input) {
|
|
255
|
-
await new Keyboard(target, {debug: true}).type(input);
|
|
470
|
+
await new Keyboard(target, { debug: true }).type(input);
|
|
256
471
|
}
|
|
257
472
|
static toReadableInput(chars) {
|
|
258
473
|
return chars.split("").map((char) => {
|
|
@@ -278,7 +493,7 @@ class Keyboard {
|
|
|
278
493
|
throw new Error(`Unknown char name: '${name}'`);
|
|
279
494
|
});
|
|
280
495
|
}
|
|
281
|
-
constructor(target, {debug = false} = {}) {
|
|
496
|
+
constructor(target, { debug = false } = {}) {
|
|
282
497
|
this.debug = debug;
|
|
283
498
|
if (target.ownerDocument) {
|
|
284
499
|
this.document = target.ownerDocument;
|
|
@@ -323,7 +538,7 @@ class Keyboard {
|
|
|
323
538
|
await this._sendKey(key, charCode, () => {
|
|
324
539
|
this._log(`sending +${key} = '${nextValue}' to ${this._pretty(focused)}`);
|
|
325
540
|
fireEvent.change(focused, {
|
|
326
|
-
target: {value: nextValue},
|
|
541
|
+
target: { value: nextValue },
|
|
327
542
|
bubbles: true,
|
|
328
543
|
cancelable: true
|
|
329
544
|
});
|
|
@@ -341,7 +556,7 @@ class Keyboard {
|
|
|
341
556
|
}
|
|
342
557
|
async click() {
|
|
343
558
|
this._log(`clicking ${this._pretty(this.focused)}`);
|
|
344
|
-
await act
|
|
559
|
+
await act(async () => fireEvent.click(this.focused));
|
|
345
560
|
}
|
|
346
561
|
async tab() {
|
|
347
562
|
await this._sendKey("Tab", codes.Tab, () => {
|
|
@@ -370,12 +585,12 @@ class Keyboard {
|
|
|
370
585
|
}
|
|
371
586
|
async enter(value) {
|
|
372
587
|
this._log(`submitting '${value}' via ${this._pretty(this.focused)}`);
|
|
373
|
-
await act
|
|
588
|
+
await act(() => this._sendKey("Enter", codes.Enter, () => {
|
|
374
589
|
if (this.focused.type === "button") {
|
|
375
|
-
fireEvent.click(this.focused, {target: {value}});
|
|
590
|
+
fireEvent.click(this.focused, { target: { value } });
|
|
376
591
|
} else {
|
|
377
592
|
fireEvent.submit(this.focused, {
|
|
378
|
-
target: {value},
|
|
593
|
+
target: { value },
|
|
379
594
|
bubbles: true,
|
|
380
595
|
cancelable: true
|
|
381
596
|
});
|
|
@@ -384,10 +599,10 @@ class Keyboard {
|
|
|
384
599
|
}
|
|
385
600
|
async escape() {
|
|
386
601
|
this._log(`escape from ${this._pretty(this.focused)}`);
|
|
387
|
-
await act
|
|
602
|
+
await act(async () => this._sendKey("Escape", codes.Esc));
|
|
388
603
|
}
|
|
389
604
|
async _sendKey(key, charCode, action) {
|
|
390
|
-
const event = {key, charCode, keyCode: charCode, which: charCode};
|
|
605
|
+
const event = { key, charCode, keyCode: charCode, which: charCode };
|
|
391
606
|
const focused = this.focused;
|
|
392
607
|
if (fireEvent.keyDown(focused, event)) {
|
|
393
608
|
if (fireEvent.keyPress(focused, event)) {
|
|
@@ -452,5 +667,23 @@ function withLogCollector(logsToCollect, callback) {
|
|
|
452
667
|
}
|
|
453
668
|
}
|
|
454
669
|
|
|
455
|
-
|
|
670
|
+
class TestApiRegistry {
|
|
671
|
+
constructor(apis) {
|
|
672
|
+
this.apis = apis;
|
|
673
|
+
}
|
|
674
|
+
static from(...apis) {
|
|
675
|
+
return new TestApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl])));
|
|
676
|
+
}
|
|
677
|
+
get(api) {
|
|
678
|
+
return this.apis.get(api.id);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const TestApiProvider = (props) => {
|
|
682
|
+
return /* @__PURE__ */ React.createElement(ApiProvider, {
|
|
683
|
+
apis: TestApiRegistry.from(...props.apis),
|
|
684
|
+
children: props.children
|
|
685
|
+
});
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
export { Keyboard, MockAnalyticsApi, MockErrorApi, MockStorageApi, TestApiProvider, TestApiRegistry, mockBreakpoint, msw, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
|
456
689
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/index.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.esm.js","sources":["../src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts","../src/testUtils/apis/ErrorApi/MockErrorApi.ts","../src/testUtils/apis/StorageApi/MockStorageApi.ts","../src/testUtils/mockBreakpoint.ts","../src/testUtils/testingLibrary.ts","../src/testUtils/mockApis.ts","../src/testUtils/appWrappers.tsx","../src/testUtils/msw/index.ts","../src/testUtils/Keyboard.js","../src/testUtils/logCollector.ts"],"sourcesContent":["/*\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 { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api';\n\n/**\n * Mock implementation of {@link core-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.\n * Use getEvents in tests to verify captured events.\n * @public\n */\nexport class MockAnalyticsApi implements AnalyticsApi {\n private events: AnalyticsEvent[] = [];\n\n captureEvent({\n action,\n subject,\n value,\n attributes,\n context,\n }: AnalyticsEvent) {\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 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ErrorApi, ErrorContext } from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\n\n/**\n * Constructor arguments for {@link MockErrorApi}\n * @public\n */\nexport type MockErrorApiOptions = {\n // Need to be true if getErrors is used in testing.\n collect?: boolean;\n};\n\n/**\n * ErrorWithContext contains error and ErrorContext\n * @public\n */\nexport type ErrorWithContext = {\n error: Error;\n context?: ErrorContext;\n};\n\ntype Waiter = {\n pattern: RegExp;\n resolve: (err: ErrorWithContext) => void;\n};\n\nconst nullObservable = {\n subscribe: () => ({ unsubscribe: () => {}, closed: true }),\n\n [Symbol.observable]() {\n return this;\n },\n};\n\n/**\n * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.\n * Incudes withForError and getErrors methods for error testing.\n * @public\n */\nexport class MockErrorApi implements ErrorApi {\n private readonly errors = new Array<ErrorWithContext>();\n private readonly waiters = new Set<Waiter>();\n\n constructor(private readonly options: MockErrorApiOptions = {}) {}\n\n post(error: Error, context?: ErrorContext) {\n if (this.options.collect) {\n this.errors.push({ error, context });\n\n for (const waiter of this.waiters) {\n if (waiter.pattern.test(error.message)) {\n this.waiters.delete(waiter);\n waiter.resolve({ error, context });\n }\n }\n\n return;\n }\n\n throw new Error(`MockErrorApi received unexpected error, ${error}`);\n }\n\n error$(): Observable<{ error: Error; context?: ErrorContext }> {\n return nullObservable;\n }\n\n getErrors(): ErrorWithContext[] {\n return this.errors;\n }\n\n waitForError(\n pattern: RegExp,\n timeoutMs: number = 2000,\n ): Promise<ErrorWithContext> {\n return new Promise<ErrorWithContext>((resolve, reject) => {\n setTimeout(() => {\n reject(new Error('Timed out waiting for error'));\n }, timeoutMs);\n\n this.waiters.add({ resolve, pattern });\n });\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StorageApi, StorageValueChange } from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\n/**\n * Type for map holding data in {@link MockStorageApi}\n * @public\n */\nexport type MockStorageBucket = { [key: string]: any };\n\n/**\n * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests\n * @public\n */\nexport class MockStorageApi implements StorageApi {\n private readonly namespace: string;\n private readonly data: MockStorageBucket;\n private readonly bucketStorageApis: Map<string, MockStorageApi>;\n\n private constructor(\n namespace: string,\n bucketStorageApis: Map<string, MockStorageApi>,\n data?: MockStorageBucket,\n ) {\n this.namespace = namespace;\n this.bucketStorageApis = bucketStorageApis;\n this.data = { ...data };\n }\n\n static create(data?: MockStorageBucket) {\n return new MockStorageApi('', new Map(), data);\n }\n\n forBucket(name: string): StorageApi {\n if (!this.bucketStorageApis.has(name)) {\n this.bucketStorageApis.set(\n name,\n new MockStorageApi(\n `${this.namespace}/${name}`,\n this.bucketStorageApis,\n this.data,\n ),\n );\n }\n return this.bucketStorageApis.get(name)!;\n }\n\n get<T>(key: string): T | undefined {\n return this.data[this.getKeyName(key)];\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n this.data[this.getKeyName(key)] = data;\n this.notifyChanges({ key, newValue: data });\n }\n\n async remove(key: string): Promise<void> {\n delete this.data[this.getKeyName(key)];\n this.notifyChanges({ key, newValue: undefined });\n }\n\n observe$<T>(key: string): Observable<StorageValueChange<T>> {\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges<T>(message: StorageValueChange<T>) {\n for (const subscription of this.subscribers) {\n subscription.next(message);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueChange>\n >();\n\n private readonly observable = new ObservableImpl<StorageValueChange>(\n subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n },\n );\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * This is a mocking method suggested in the Jest Doc's, as it is not implemented in JSDOM yet.\n * It can be used to mock values when the MUI `useMediaQuery` hook if it is used in a tested component.\n *\n * For issues checkout the documentation:\n * https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom\n *\n * If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced\n * https://material-ui.com/components/use-media-query/#testing\n *\n * @public\n */\nexport default function mockBreakpoint({ matches = false }) {\n Object.defineProperty(window, 'matchMedia', {\n writable: true,\n value: jest.fn().mockImplementation(query => ({\n matches: matches,\n media: query,\n onchange: null,\n addListener: jest.fn(), // deprecated\n removeListener: jest.fn(), // deprecated\n addEventListener: jest.fn(),\n removeEventListener: jest.fn(),\n dispatchEvent: jest.fn(),\n })),\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactElement } from 'react';\nimport { act } from 'react-dom/test-utils';\nimport { render, RenderResult } from '@testing-library/react';\n\n/**\n * @public\n * Simplifies rendering of async components in by taking care of the wrapping inside act\n *\n * @remarks\n *\n * Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async\n * act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit\n * cleaner, since act doesn't return the result of the evaluated function.\n * https://github.com/testing-library/react-testing-library/issues/281\n * https://github.com/facebook/react/pull/14853\n */\nexport async function renderWithEffects(\n nodes: ReactElement,\n): Promise<RenderResult> {\n let value: RenderResult;\n await act(async () => {\n value = render(nodes);\n });\n // @ts-ignore\n return value;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n storageApiRef,\n errorApiRef,\n createApiFactory,\n} from '@backstage/core-plugin-api';\nimport { MockErrorApi, MockStorageApi } from './apis';\n\nexport const mockApis = [\n createApiFactory(errorApiRef, new MockErrorApi()),\n createApiFactory(storageApiRef, MockStorageApi.create()),\n];\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { ComponentType, ReactNode, ReactElement } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { Route } from 'react-router-dom';\nimport { lightTheme } from '@backstage/theme';\nimport { createApp } from '@backstage/core-app-api';\nimport {\n BootErrorPageProps,\n RouteRef,\n ExternalRouteRef,\n attachComponentData,\n createRouteRef,\n} from '@backstage/core-plugin-api';\nimport { RenderResult } from '@testing-library/react';\nimport { renderWithEffects } from './testingLibrary';\nimport { mockApis } from './mockApis';\n\nconst ErrorBoundaryFallback = ({ error }: { error: Error }) => {\n throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`);\n};\nconst NotFoundErrorPage = () => {\n throw new Error('Reached NotFound Page');\n};\nconst BootErrorPage = ({ step, error }: BootErrorPageProps) => {\n throw new Error(`Reached BootError Page at step ${step} with error ${error}`);\n};\nconst Progress = () => <div data-testid=\"progress\" />;\n\n/**\n * Options to customize the behavior of the test app wrapper.\n * @public\n */\nexport type TestAppOptions = {\n /**\n * Initial route entries to pass along as `initialEntries` to the router.\n */\n routeEntries?: string[];\n\n /**\n * An object of paths to mount route ref on, with the key being the path and the value\n * being the RouteRef 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 * wrapInTestApp(<MyComponent />, \\{\n * mountedRoutes: \\{\n * '/my-path': myRouteRef,\n * \\}\n * \\})\n * // ...\n * const link = useRouteRef(myRouteRef)\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n};\n\nfunction isExternalRouteRef(\n routeRef: RouteRef | ExternalRouteRef,\n): routeRef is ExternalRouteRef {\n // TODO(Rugvip): Least ugly workaround for now, but replace :D\n return String(routeRef).includes('{type=external,');\n}\n\n/**\n * Wraps a component inside a Backstage test app, providing a mocked theme\n * and app context, along with mocked APIs.\n *\n * @param Component - A component or react node to render inside the test app.\n * @param options - Additional options for the rendering.\n * @public\n */\nexport function wrapInTestApp(\n Component: ComponentType | ReactNode,\n options: TestAppOptions = {},\n): ReactElement {\n const { routeEntries = ['/'] } = options;\n const boundRoutes = new Map<ExternalRouteRef, RouteRef>();\n\n const app = createApp({\n apis: mockApis,\n // Bit of a hack to make sure that the default config loader isn't used\n // as that would force every single test to wait for config loading.\n configLoader: false as unknown as undefined,\n components: {\n Progress,\n BootErrorPage,\n NotFoundErrorPage,\n ErrorBoundaryFallback,\n Router: ({ children }) => (\n <MemoryRouter initialEntries={routeEntries} children={children} />\n ),\n },\n plugins: [],\n themes: [\n {\n id: 'light',\n theme: lightTheme,\n title: 'Test App Theme',\n variant: 'light',\n },\n ],\n bindRoutes: ({ bind }) => {\n for (const [externalRef, absoluteRef] of boundRoutes) {\n bind(\n { ref: externalRef },\n {\n ref: absoluteRef,\n },\n );\n }\n },\n });\n\n let wrappedElement: React.ReactElement;\n if (Component instanceof Function) {\n wrappedElement = <Component />;\n } else {\n wrappedElement = Component as React.ReactElement;\n }\n\n const routeElements = Object.entries(options.mountedRoutes ?? {}).map(\n ([path, routeRef]) => {\n const Page = () => <div>Mounted at {path}</div>;\n\n // Allow external route refs to be bound to paths as well, for convenience.\n // We work around it by creating and binding an absolute ref to the external one.\n if (isExternalRouteRef(routeRef)) {\n const absoluteRef = createRouteRef({ id: 'id' });\n boundRoutes.set(routeRef, absoluteRef);\n attachComponentData(Page, 'core.mountPoint', absoluteRef);\n } else {\n attachComponentData(Page, 'core.mountPoint', routeRef);\n }\n return <Route key={path} path={path} element={<Page />} />;\n },\n );\n\n const AppProvider = app.getProvider();\n const AppRouter = app.getRouter();\n\n return (\n <AppProvider>\n <AppRouter>\n {routeElements}\n {/* The path of * here is needed to be set as a catch all, so it will render the wrapper element\n * and work with nested routes if they exist too */}\n <Route path=\"*\" element={wrappedElement} />\n </AppRouter>\n </AppProvider>\n );\n}\n\n/**\n * Renders a component inside a Backstage test app, providing a mocked theme\n * and app context, along with mocked APIs.\n *\n * The render executes async effects similar to `renderWithEffects`. To avoid this\n * behavior, use a regular `render()` + `wrapInTestApp()` instead.\n *\n * @param Component - A component or react node to render inside the test app.\n * @param options - Additional options for the rendering.\n * @public\n */\nexport async function renderInTestApp(\n Component: ComponentType | ReactNode,\n options: TestAppOptions = {},\n): Promise<RenderResult> {\n return renderWithEffects(wrapInTestApp(Component, options));\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.\n * @public\n */\nexport const msw = {\n setupDefaultHandlers: (worker: {\n listen: (t: any) => void;\n close: () => void;\n resetHandlers: () => void;\n }) => {\n setupRequestMockHandlers(worker);\n },\n};\n\n/**\n * Sets up handlers for request mocking\n * @public\n * @param worker - service worker\n */\nexport function setupRequestMockHandlers(worker: {\n listen: (t: any) => void;\n close: () => void;\n resetHandlers: () => void;\n}) {\n beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));\n afterAll(() => worker.close());\n afterEach(() => worker.resetHandlers());\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { act, fireEvent } from '@testing-library/react';\n\nconst codes = {\n Tab: 9,\n Enter: 10,\n Click: 17 /* This keyboard can click, deal with it */,\n Esc: 27,\n};\n\n/**\n * @public\n * @deprecated because it has no usages. Perhaps resurfaced in the future when need be.\n */\nexport class Keyboard {\n static async type(target, input) {\n await new Keyboard(target).type(input);\n }\n\n static async typeDebug(target, input) {\n await new Keyboard(target, { debug: true }).type(input);\n }\n\n static toReadableInput(chars) {\n return chars.split('').map(char => {\n switch (char.charCodeAt(0)) {\n case codes.Tab:\n return '<Tab>';\n case codes.Enter:\n return '<Enter>';\n case codes.Click:\n return '<Click>';\n case codes.Esc:\n return '<Esc>';\n default:\n return char;\n }\n });\n }\n\n static fromReadableInput(input) {\n return input.trim().replace(/\\s*<([a-zA-Z]+)>\\s*/g, (match, name) => {\n if (name in codes) {\n return String.fromCharCode(codes[name]);\n }\n throw new Error(`Unknown char name: '${name}'`);\n });\n }\n\n constructor(target, { debug = false } = {}) {\n this.debug = debug;\n\n if (target.ownerDocument) {\n this.document = target.ownerDocument;\n } else if (target.baseElement) {\n this.document = target.baseElement.ownerDocument;\n } else {\n throw new TypeError(\n 'Keyboard(target): target must be DOM node or react-testing-library render() output',\n );\n }\n }\n\n toString() {\n return `Keyboard{document=${this.document}, debug=${this.debug}}`;\n }\n\n _log(message, ...args) {\n if (this.debug) {\n // eslint-disable-next-line no-console\n console.log(`[Keyboard] ${message}`, ...args);\n }\n }\n\n _pretty(element) {\n const attrs = [...element.attributes]\n .map(attr => `${attr.name}=\"${attr.value}\"`)\n .join(' ');\n return `<${element.nodeName.toLocaleLowerCase('en-US')} ${attrs}>`;\n }\n\n get focused() {\n return this.document.activeElement;\n }\n\n async type(input) {\n this._log(\n `sending sequence '${input}' with initial focus ${this._pretty(\n this.focused,\n )}`,\n );\n await this.send(Keyboard.fromReadableInput(input));\n }\n\n async send(chars) {\n for (const key of chars.split('')) {\n const charCode = key.charCodeAt(0);\n\n if (charCode === codes.Tab) {\n await this.tab();\n continue;\n }\n\n const focused = this.focused;\n if (!focused || focused === this.document.body) {\n throw Error(\n `No element focused in document while trying to type '${Keyboard.toReadableInput(\n chars,\n )}'`,\n );\n }\n const nextValue = (focused.value || '') + key;\n\n if (charCode >= 32) {\n await this._sendKey(key, charCode, () => {\n this._log(\n `sending +${key} = '${nextValue}' to ${this._pretty(focused)}`,\n );\n fireEvent.change(focused, {\n target: { value: nextValue },\n bubbles: true,\n cancelable: true,\n });\n });\n } else if (charCode === codes.Enter) {\n await this.enter(focused.value || '');\n } else if (charCode === codes.Esc) {\n await this.escape();\n } else if (charCode === codes.Click) {\n await this.click();\n } else {\n throw new Error(`Unsupported char code, ${charCode}`);\n }\n }\n }\n\n async click() {\n this._log(`clicking ${this._pretty(this.focused)}`);\n await act(async () => fireEvent.click(this.focused));\n }\n\n async tab() {\n await this._sendKey('Tab', codes.Tab, () => {\n const focusable = this.document.querySelectorAll(\n [\n 'a[href]',\n 'area[href]',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'button:not([disabled])',\n 'iframe',\n 'object',\n 'embed',\n '*[tabindex]',\n '*[contenteditable]',\n ].join(','),\n );\n\n const tabbable = [...focusable].filter(el => {\n return el.tabIndex >= 0;\n });\n\n const focused = this.document.activeElement;\n const focusedIndex = tabbable.indexOf(focused);\n const nextFocus = tabbable[focusedIndex + (1 % tabbable.length)];\n\n this._log(\n `tabbing to ${this._pretty(nextFocus)} ${this.focused.textContent}`,\n );\n nextFocus.focus();\n });\n }\n\n async enter(value) {\n this._log(`submitting '${value}' via ${this._pretty(this.focused)}`);\n await act(() =>\n this._sendKey('Enter', codes.Enter, () => {\n if (this.focused.type === 'button') {\n fireEvent.click(this.focused, { target: { value } });\n } else {\n fireEvent.submit(this.focused, {\n target: { value },\n bubbles: true,\n cancelable: true,\n });\n }\n }),\n );\n }\n\n async escape() {\n this._log(`escape from ${this._pretty(this.focused)}`);\n await act(async () => this._sendKey('Escape', codes.Esc));\n }\n\n async _sendKey(key, charCode, action) {\n const event = { key, charCode, keyCode: charCode, which: charCode };\n const focused = this.focused;\n\n if (fireEvent.keyDown(focused, event)) {\n if (fireEvent.keyPress(focused, event)) {\n if (action) {\n action();\n }\n }\n }\n fireEvent.keyUp(focused, event);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* eslint-disable no-console */\n\n/**\n * Severity levels of {@link CollectedLogs}\n * @public */\nexport type LogFuncs = 'log' | 'warn' | 'error';\n/**\n * AsyncLogCollector type used in {@link (withLogCollector:1)} callback function.\n * @public */\nexport type AsyncLogCollector = () => Promise<void>;\n/**\n * SyncLogCollector type used in {@link (withLogCollector:2)} callback function.\n * @public */\nexport type SyncLogCollector = () => void;\n/**\n * Union type used in {@link (withLogCollector:3)} callback function.\n * @public */\nexport type LogCollector = AsyncLogCollector | SyncLogCollector;\n/**\n * Map of severity level and corresponding log lines.\n * @public */\nexport type CollectedLogs<T extends LogFuncs> = { [key in T]: string[] };\n\nconst allCategories = ['log', 'warn', 'error'];\n\n/**\n * Asynchronous log collector with that collects all categories\n * @public */\nexport function withLogCollector(\n callback: AsyncLogCollector,\n): Promise<CollectedLogs<LogFuncs>>;\n\n/**\n * Synchronous log collector with that collects all categories\n * @public */\nexport function withLogCollector(\n callback: SyncLogCollector,\n): CollectedLogs<LogFuncs>;\n\n/**\n * Asynchronous log collector with that only collects selected categories\n * @public\n */\nexport function withLogCollector<T extends LogFuncs>(\n logsToCollect: T[],\n callback: AsyncLogCollector,\n): Promise<CollectedLogs<T>>;\n\n/**\n * Synchronous log collector with that only collects selected categories\n * @public */\nexport function withLogCollector<T extends LogFuncs>(\n logsToCollect: T[],\n callback: SyncLogCollector,\n): CollectedLogs<T>;\n\n/**\n * Log collector that collect logs either from a sync or async collector.\n * @public\n * @deprecated import from test-utils instead\n * */\nexport function withLogCollector(\n logsToCollect: LogFuncs[] | LogCollector,\n callback?: LogCollector,\n): CollectedLogs<LogFuncs> | Promise<CollectedLogs<LogFuncs>> {\n const oneArg = !callback;\n const actualCallback = (oneArg ? logsToCollect : callback) as LogCollector;\n const categories = (oneArg ? allCategories : logsToCollect) as LogFuncs[];\n\n const logs = {\n log: new Array<string>(),\n warn: new Array<string>(),\n error: new Array<string>(),\n };\n\n const origLog = console.log;\n const origWarn = console.warn;\n const origError = console.error;\n\n if (categories.includes('log')) {\n console.log = (message: string) => {\n logs.log.push(message);\n };\n }\n if (categories.includes('warn')) {\n console.warn = (message: string) => {\n logs.warn.push(message);\n };\n }\n if (categories.includes('error')) {\n console.error = (message: string) => {\n logs.error.push(message);\n };\n }\n\n const restore = () => {\n console.log = origLog;\n console.warn = origWarn;\n console.error = origError;\n };\n\n try {\n const ret = actualCallback();\n\n if (!ret || !ret.then) {\n restore();\n return logs;\n }\n\n return ret.then(\n () => {\n restore();\n return logs;\n },\n error => {\n restore();\n throw error;\n },\n );\n } catch (error) {\n restore();\n throw error;\n }\n}\n"],"names":["act"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.esm.js","sources":["../src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts","../src/testUtils/apis/ErrorApi/MockErrorApi.ts","../src/testUtils/apis/StorageApi/MockStorageApi.ts","../src/testUtils/mockBreakpoint.ts","../src/testUtils/testingLibrary.ts","../src/testUtils/defaultApis.ts","../src/testUtils/mockApis.ts","../src/testUtils/appWrappers.tsx","../src/testUtils/msw/index.ts","../src/testUtils/Keyboard.js","../src/testUtils/logCollector.ts","../src/testUtils/TestApiProvider.tsx"],"sourcesContent":["/*\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 { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api';\n\n/**\n * Mock implementation of {@link core-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 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ErrorApi,\n ErrorApiError,\n ErrorApiErrorContext,\n} from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\n\n/**\n * Constructor arguments for {@link MockErrorApi}\n * @public\n */\nexport type MockErrorApiOptions = {\n // Need to be true if getErrors is used in testing.\n collect?: boolean;\n};\n\n/**\n * ErrorWithContext contains error and ErrorApiErrorContext\n * @public\n */\nexport type ErrorWithContext = {\n error: ErrorApiError;\n context?: ErrorApiErrorContext;\n};\n\ntype Waiter = {\n pattern: RegExp;\n resolve: (err: ErrorWithContext) => void;\n};\n\nconst nullObservable = {\n subscribe: () => ({ unsubscribe: () => {}, closed: true }),\n\n [Symbol.observable]() {\n return this;\n },\n};\n\n/**\n * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.\n * Incudes withForError and getErrors methods for error testing.\n * @public\n */\nexport class MockErrorApi implements ErrorApi {\n private readonly errors = new Array<ErrorWithContext>();\n private readonly waiters = new Set<Waiter>();\n\n constructor(private readonly options: MockErrorApiOptions = {}) {}\n\n post(error: ErrorApiError, context?: ErrorApiErrorContext) {\n if (this.options.collect) {\n this.errors.push({ error, context });\n\n for (const waiter of this.waiters) {\n if (waiter.pattern.test(error.message)) {\n this.waiters.delete(waiter);\n waiter.resolve({ error, context });\n }\n }\n\n return;\n }\n\n throw new Error(`MockErrorApi received unexpected error, ${error}`);\n }\n\n error$(): Observable<{\n error: ErrorApiError;\n context?: ErrorApiErrorContext;\n }> {\n return nullObservable;\n }\n\n getErrors(): ErrorWithContext[] {\n return this.errors;\n }\n\n waitForError(\n pattern: RegExp,\n timeoutMs: number = 2000,\n ): Promise<ErrorWithContext> {\n return new Promise<ErrorWithContext>((resolve, reject) => {\n setTimeout(() => {\n reject(new Error('Timed out waiting for error'));\n }, timeoutMs);\n\n this.waiters.add({ resolve, pattern });\n });\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { StorageApi, StorageValueChange } from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\nimport ObservableImpl from 'zen-observable';\n\n/**\n * Type for map holding data in {@link MockStorageApi}\n * @public\n */\nexport type MockStorageBucket = { [key: string]: any };\n\n/**\n * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests\n * @public\n */\nexport class MockStorageApi implements StorageApi {\n private readonly namespace: string;\n private readonly data: MockStorageBucket;\n private readonly bucketStorageApis: Map<string, MockStorageApi>;\n\n private constructor(\n namespace: string,\n bucketStorageApis: Map<string, MockStorageApi>,\n data?: MockStorageBucket,\n ) {\n this.namespace = namespace;\n this.bucketStorageApis = bucketStorageApis;\n this.data = { ...data };\n }\n\n static create(data?: MockStorageBucket) {\n return new MockStorageApi('', new Map(), data);\n }\n\n forBucket(name: string): StorageApi {\n if (!this.bucketStorageApis.has(name)) {\n this.bucketStorageApis.set(\n name,\n new MockStorageApi(\n `${this.namespace}/${name}`,\n this.bucketStorageApis,\n this.data,\n ),\n );\n }\n return this.bucketStorageApis.get(name)!;\n }\n\n get<T>(key: string): T | undefined {\n return this.data[this.getKeyName(key)];\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n this.data[this.getKeyName(key)] = data;\n this.notifyChanges({ key, newValue: data });\n }\n\n async remove(key: string): Promise<void> {\n delete this.data[this.getKeyName(key)];\n this.notifyChanges({ key, newValue: undefined });\n }\n\n observe$<T>(key: string): Observable<StorageValueChange<T>> {\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges<T>(message: StorageValueChange<T>) {\n for (const subscription of this.subscribers) {\n subscription.next(message);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueChange>\n >();\n\n private readonly observable = new ObservableImpl<StorageValueChange>(\n subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n },\n );\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * This is a mocking method suggested in the Jest docs, as it is not implemented in JSDOM yet.\n * It can be used to mock values for the MUI `useMediaQuery` hook if it is used in a tested component.\n *\n * For issues checkout the documentation:\n * https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom\n *\n * If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced\n * https://material-ui.com/components/use-media-query/#testing\n *\n * @public\n */\nexport default function mockBreakpoint(options: { matches: boolean }) {\n Object.defineProperty(window, 'matchMedia', {\n writable: true,\n value: jest.fn().mockImplementation(query => ({\n matches: options.matches ?? false,\n media: query,\n onchange: null,\n addListener: jest.fn(), // deprecated\n removeListener: jest.fn(), // deprecated\n addEventListener: jest.fn(),\n removeEventListener: jest.fn(),\n dispatchEvent: jest.fn(),\n })),\n });\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { ReactElement } from 'react';\nimport { act, render, RenderResult } from '@testing-library/react';\n\n/**\n * @public\n * Simplifies rendering of async components in by taking care of the wrapping inside act\n *\n * @remarks\n *\n * Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async\n * act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit\n * cleaner, since act doesn't return the result of the evaluated function.\n * https://github.com/testing-library/react-testing-library/issues/281\n * https://github.com/facebook/react/pull/14853\n */\nexport async function renderWithEffects(\n nodes: ReactElement,\n): Promise<RenderResult> {\n let value: RenderResult;\n await act(async () => {\n value = render(nodes);\n });\n // @ts-ignore\n return value;\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 {\n AlertApiForwarder,\n NoOpAnalyticsApi,\n ErrorApiForwarder,\n ErrorAlerter,\n GoogleAuth,\n GithubAuth,\n OAuth2,\n OktaAuth,\n GitlabAuth,\n Auth0Auth,\n MicrosoftAuth,\n BitbucketAuth,\n OAuthRequestManager,\n WebStorage,\n UrlPatternDiscovery,\n SamlAuth,\n OneLoginAuth,\n UnhandledErrorForwarder,\n AtlassianAuth,\n} from '@backstage/core-app-api';\n\nimport {\n createApiFactory,\n alertApiRef,\n analyticsApiRef,\n errorApiRef,\n discoveryApiRef,\n oauthRequestApiRef,\n googleAuthApiRef,\n githubAuthApiRef,\n oauth2ApiRef,\n oktaAuthApiRef,\n gitlabAuthApiRef,\n auth0AuthApiRef,\n microsoftAuthApiRef,\n storageApiRef,\n configApiRef,\n samlAuthApiRef,\n oneloginAuthApiRef,\n oidcAuthApiRef,\n bitbucketAuthApiRef,\n atlassianAuthApiRef,\n} from '@backstage/core-plugin-api';\n\n// TODO(Rugvip): This is just a copy of the createApp default APIs for now, but\n// we should clean up this list a bit move more things over to mocks.\nexport const defaultApis = [\n createApiFactory({\n api: discoveryApiRef,\n deps: { configApi: configApiRef },\n factory: ({ configApi }) =>\n UrlPatternDiscovery.compile(\n `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,\n ),\n }),\n createApiFactory(alertApiRef, new AlertApiForwarder()),\n createApiFactory(analyticsApiRef, new NoOpAnalyticsApi()),\n createApiFactory({\n api: errorApiRef,\n deps: { alertApi: alertApiRef },\n factory: ({ alertApi }) => {\n const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());\n UnhandledErrorForwarder.forward(errorApi, { hidden: false });\n return errorApi;\n },\n }),\n createApiFactory({\n api: storageApiRef,\n deps: { errorApi: errorApiRef },\n factory: ({ errorApi }) => WebStorage.create({ errorApi }),\n }),\n createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),\n createApiFactory({\n api: googleAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n GoogleAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: microsoftAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n MicrosoftAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: githubAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n GithubAuth.create({\n discoveryApi,\n oauthRequestApi,\n defaultScopes: ['read:user'],\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: oktaAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n OktaAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: gitlabAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n GitlabAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: auth0AuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n Auth0Auth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: oauth2ApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n OAuth2.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: samlAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, configApi }) =>\n SamlAuth.create({\n discoveryApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: oneloginAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n OneLoginAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: oidcAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n OAuth2.create({\n discoveryApi,\n oauthRequestApi,\n provider: {\n id: 'oidc',\n title: 'Your Identity Provider',\n icon: () => null,\n },\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: bitbucketAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) =>\n BitbucketAuth.create({\n discoveryApi,\n oauthRequestApi,\n defaultScopes: ['team'],\n environment: configApi.getOptionalString('auth.environment'),\n }),\n }),\n createApiFactory({\n api: atlassianAuthApiRef,\n deps: {\n discoveryApi: discoveryApiRef,\n oauthRequestApi: oauthRequestApiRef,\n configApi: configApiRef,\n },\n factory: ({ discoveryApi, oauthRequestApi, configApi }) => {\n return AtlassianAuth.create({\n discoveryApi,\n oauthRequestApi,\n environment: configApi.getOptionalString('auth.environment'),\n });\n },\n }),\n];\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n storageApiRef,\n errorApiRef,\n createApiFactory,\n} from '@backstage/core-plugin-api';\nimport { MockErrorApi, MockStorageApi } from './apis';\n\nexport const mockApis = [\n createApiFactory(errorApiRef, new MockErrorApi()),\n createApiFactory(storageApiRef, MockStorageApi.create()),\n];\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport React, { ComponentType, ReactNode, ReactElement } from 'react';\nimport { MemoryRouter } from 'react-router';\nimport { Route } from 'react-router-dom';\nimport { lightTheme } from '@backstage/theme';\nimport { ThemeProvider } from '@material-ui/core/styles';\nimport { CssBaseline } from '@material-ui/core';\nimport MockIcon from '@material-ui/icons/AcUnit';\nimport { createSpecializedApp } from '@backstage/core-app-api';\nimport {\n BootErrorPageProps,\n RouteRef,\n ExternalRouteRef,\n attachComponentData,\n createRouteRef,\n} from '@backstage/core-plugin-api';\nimport { RenderResult } from '@testing-library/react';\nimport { renderWithEffects } from './testingLibrary';\nimport { defaultApis } from './defaultApis';\nimport { mockApis } from './mockApis';\n\nconst mockIcons = {\n 'kind:api': MockIcon,\n 'kind:component': MockIcon,\n 'kind:domain': MockIcon,\n 'kind:group': MockIcon,\n 'kind:location': MockIcon,\n 'kind:system': MockIcon,\n 'kind:user': MockIcon,\n\n brokenImage: MockIcon,\n catalog: MockIcon,\n scaffolder: MockIcon,\n techdocs: MockIcon,\n search: MockIcon,\n chat: MockIcon,\n dashboard: MockIcon,\n docs: MockIcon,\n email: MockIcon,\n github: MockIcon,\n group: MockIcon,\n help: MockIcon,\n user: MockIcon,\n warning: MockIcon,\n};\n\nconst ErrorBoundaryFallback = ({ error }: { error: Error }) => {\n throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`);\n};\nconst NotFoundErrorPage = () => {\n throw new Error('Reached NotFound Page');\n};\nconst BootErrorPage = ({ step, error }: BootErrorPageProps) => {\n throw new Error(`Reached BootError Page at step ${step} with error ${error}`);\n};\nconst Progress = () => <div data-testid=\"progress\" />;\n\n/**\n * Options to customize the behavior of the test app wrapper.\n * @public\n */\nexport type TestAppOptions = {\n /**\n * Initial route entries to pass along as `initialEntries` to the router.\n */\n routeEntries?: string[];\n\n /**\n * An object of paths to mount route ref on, with the key being the path and the value\n * being the RouteRef 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 * wrapInTestApp(<MyComponent />, \\{\n * mountedRoutes: \\{\n * '/my-path': myRouteRef,\n * \\}\n * \\})\n * // ...\n * const link = useRouteRef(myRouteRef)\n */\n mountedRoutes?: { [path: string]: RouteRef | ExternalRouteRef };\n};\n\nfunction isExternalRouteRef(\n routeRef: RouteRef | ExternalRouteRef,\n): routeRef is ExternalRouteRef {\n // TODO(Rugvip): Least ugly workaround for now, but replace :D\n return String(routeRef).includes('{type=external,');\n}\n\n/**\n * Wraps a component inside a Backstage test app, providing a mocked theme\n * and app context, along with mocked APIs.\n *\n * @param Component - A component or react node to render inside the test app.\n * @param options - Additional options for the rendering.\n * @public\n */\nexport function wrapInTestApp(\n Component: ComponentType | ReactNode,\n options: TestAppOptions = {},\n): ReactElement {\n const { routeEntries = ['/'] } = options;\n const boundRoutes = new Map<ExternalRouteRef, RouteRef>();\n\n const app = createSpecializedApp({\n apis: mockApis,\n defaultApis,\n // Bit of a hack to make sure that the default config loader isn't used\n // as that would force every single test to wait for config loading.\n configLoader: false as unknown as undefined,\n components: {\n Progress,\n BootErrorPage,\n NotFoundErrorPage,\n ErrorBoundaryFallback,\n Router: ({ children }) => (\n <MemoryRouter initialEntries={routeEntries} children={children} />\n ),\n },\n icons: mockIcons,\n plugins: [],\n themes: [\n {\n id: 'light',\n title: 'Test App Theme',\n variant: 'light',\n Provider: ({ children }) => (\n <ThemeProvider theme={lightTheme}>\n <CssBaseline>{children}</CssBaseline>\n </ThemeProvider>\n ),\n },\n ],\n bindRoutes: ({ bind }) => {\n for (const [externalRef, absoluteRef] of boundRoutes) {\n bind(\n { ref: externalRef },\n {\n ref: absoluteRef,\n },\n );\n }\n },\n });\n\n let wrappedElement: React.ReactElement;\n if (Component instanceof Function) {\n wrappedElement = <Component />;\n } else {\n wrappedElement = Component as React.ReactElement;\n }\n\n const routeElements = Object.entries(options.mountedRoutes ?? {}).map(\n ([path, routeRef]) => {\n const Page = () => <div>Mounted at {path}</div>;\n\n // Allow external route refs to be bound to paths as well, for convenience.\n // We work around it by creating and binding an absolute ref to the external one.\n if (isExternalRouteRef(routeRef)) {\n const absoluteRef = createRouteRef({ id: 'id' });\n boundRoutes.set(routeRef, absoluteRef);\n attachComponentData(Page, 'core.mountPoint', absoluteRef);\n } else {\n attachComponentData(Page, 'core.mountPoint', routeRef);\n }\n return <Route key={path} path={path} element={<Page />} />;\n },\n );\n\n const AppProvider = app.getProvider();\n const AppRouter = app.getRouter();\n\n return (\n <AppProvider>\n <AppRouter>\n {routeElements}\n {/* The path of * here is needed to be set as a catch all, so it will render the wrapper element\n * and work with nested routes if they exist too */}\n <Route path=\"*\" element={wrappedElement} />\n </AppRouter>\n </AppProvider>\n );\n}\n\n/**\n * Renders a component inside a Backstage test app, providing a mocked theme\n * and app context, along with mocked APIs.\n *\n * The render executes async effects similar to `renderWithEffects`. To avoid this\n * behavior, use a regular `render()` + `wrapInTestApp()` instead.\n *\n * @param Component - A component or react node to render inside the test app.\n * @param options - Additional options for the rendering.\n * @public\n */\nexport async function renderInTestApp(\n Component: ComponentType | ReactNode,\n options: TestAppOptions = {},\n): Promise<RenderResult> {\n return renderWithEffects(wrapInTestApp(Component, options));\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.\n * @public\n */\nexport const msw = {\n setupDefaultHandlers: (worker: {\n listen: (t: any) => void;\n close: () => void;\n resetHandlers: () => void;\n }) => {\n setupRequestMockHandlers(worker);\n },\n};\n\n/**\n * Sets up handlers for request mocking\n * @public\n * @param worker - service worker\n */\nexport function setupRequestMockHandlers(worker: {\n listen: (t: any) => void;\n close: () => void;\n resetHandlers: () => void;\n}) {\n beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));\n afterAll(() => worker.close());\n afterEach(() => worker.resetHandlers());\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { act, fireEvent } from '@testing-library/react';\n\nconst codes = {\n Tab: 9,\n Enter: 10,\n Click: 17 /* This keyboard can click, deal with it */,\n Esc: 27,\n};\n\n/**\n * @public\n * @deprecated superseded by {@link @testing-library/user-event#userEvent}\n */\nexport class Keyboard {\n static async type(target, input) {\n await new Keyboard(target).type(input);\n }\n\n static async typeDebug(target, input) {\n await new Keyboard(target, { debug: true }).type(input);\n }\n\n static toReadableInput(chars) {\n return chars.split('').map(char => {\n switch (char.charCodeAt(0)) {\n case codes.Tab:\n return '<Tab>';\n case codes.Enter:\n return '<Enter>';\n case codes.Click:\n return '<Click>';\n case codes.Esc:\n return '<Esc>';\n default:\n return char;\n }\n });\n }\n\n static fromReadableInput(input) {\n return input.trim().replace(/\\s*<([a-zA-Z]+)>\\s*/g, (match, name) => {\n if (name in codes) {\n return String.fromCharCode(codes[name]);\n }\n throw new Error(`Unknown char name: '${name}'`);\n });\n }\n\n constructor(target, { debug = false } = {}) {\n this.debug = debug;\n\n if (target.ownerDocument) {\n this.document = target.ownerDocument;\n } else if (target.baseElement) {\n this.document = target.baseElement.ownerDocument;\n } else {\n throw new TypeError(\n 'Keyboard(target): target must be DOM node or react-testing-library render() output',\n );\n }\n }\n\n toString() {\n return `Keyboard{document=${this.document}, debug=${this.debug}}`;\n }\n\n _log(message, ...args) {\n if (this.debug) {\n // eslint-disable-next-line no-console\n console.log(`[Keyboard] ${message}`, ...args);\n }\n }\n\n _pretty(element) {\n const attrs = [...element.attributes]\n .map(attr => `${attr.name}=\"${attr.value}\"`)\n .join(' ');\n return `<${element.nodeName.toLocaleLowerCase('en-US')} ${attrs}>`;\n }\n\n get focused() {\n return this.document.activeElement;\n }\n\n async type(input) {\n this._log(\n `sending sequence '${input}' with initial focus ${this._pretty(\n this.focused,\n )}`,\n );\n await this.send(Keyboard.fromReadableInput(input));\n }\n\n async send(chars) {\n for (const key of chars.split('')) {\n const charCode = key.charCodeAt(0);\n\n if (charCode === codes.Tab) {\n await this.tab();\n continue;\n }\n\n const focused = this.focused;\n if (!focused || focused === this.document.body) {\n throw Error(\n `No element focused in document while trying to type '${Keyboard.toReadableInput(\n chars,\n )}'`,\n );\n }\n const nextValue = (focused.value || '') + key;\n\n if (charCode >= 32) {\n await this._sendKey(key, charCode, () => {\n this._log(\n `sending +${key} = '${nextValue}' to ${this._pretty(focused)}`,\n );\n fireEvent.change(focused, {\n target: { value: nextValue },\n bubbles: true,\n cancelable: true,\n });\n });\n } else if (charCode === codes.Enter) {\n await this.enter(focused.value || '');\n } else if (charCode === codes.Esc) {\n await this.escape();\n } else if (charCode === codes.Click) {\n await this.click();\n } else {\n throw new Error(`Unsupported char code, ${charCode}`);\n }\n }\n }\n\n async click() {\n this._log(`clicking ${this._pretty(this.focused)}`);\n await act(async () => fireEvent.click(this.focused));\n }\n\n async tab() {\n await this._sendKey('Tab', codes.Tab, () => {\n const focusable = this.document.querySelectorAll(\n [\n 'a[href]',\n 'area[href]',\n 'input:not([disabled])',\n 'select:not([disabled])',\n 'textarea:not([disabled])',\n 'button:not([disabled])',\n 'iframe',\n 'object',\n 'embed',\n '*[tabindex]',\n '*[contenteditable]',\n ].join(','),\n );\n\n const tabbable = [...focusable].filter(el => {\n return el.tabIndex >= 0;\n });\n\n const focused = this.document.activeElement;\n const focusedIndex = tabbable.indexOf(focused);\n const nextFocus = tabbable[focusedIndex + (1 % tabbable.length)];\n\n this._log(\n `tabbing to ${this._pretty(nextFocus)} ${this.focused.textContent}`,\n );\n nextFocus.focus();\n });\n }\n\n async enter(value) {\n this._log(`submitting '${value}' via ${this._pretty(this.focused)}`);\n await act(() =>\n this._sendKey('Enter', codes.Enter, () => {\n if (this.focused.type === 'button') {\n fireEvent.click(this.focused, { target: { value } });\n } else {\n fireEvent.submit(this.focused, {\n target: { value },\n bubbles: true,\n cancelable: true,\n });\n }\n }),\n );\n }\n\n async escape() {\n this._log(`escape from ${this._pretty(this.focused)}`);\n await act(async () => this._sendKey('Escape', codes.Esc));\n }\n\n async _sendKey(key, charCode, action) {\n const event = { key, charCode, keyCode: charCode, which: charCode };\n const focused = this.focused;\n\n if (fireEvent.keyDown(focused, event)) {\n if (fireEvent.keyPress(focused, event)) {\n if (action) {\n action();\n }\n }\n }\n fireEvent.keyUp(focused, event);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/* eslint-disable no-console */\n\n/**\n * Severity levels of {@link CollectedLogs}\n * @public */\nexport type LogFuncs = 'log' | 'warn' | 'error';\n/**\n * AsyncLogCollector type used in {@link (withLogCollector:1)} callback function.\n * @public */\nexport type AsyncLogCollector = () => Promise<void>;\n/**\n * SyncLogCollector type used in {@link (withLogCollector:2)} callback function.\n * @public */\nexport type SyncLogCollector = () => void;\n/**\n * Union type used in {@link (withLogCollector:3)} callback function.\n * @public */\nexport type LogCollector = AsyncLogCollector | SyncLogCollector;\n/**\n * Map of severity level and corresponding log lines.\n * @public */\nexport type CollectedLogs<T extends LogFuncs> = { [key in T]: string[] };\n\nconst allCategories = ['log', 'warn', 'error'];\n\n/**\n * Asynchronous log collector with that collects all categories\n * @public */\nexport function withLogCollector(\n callback: AsyncLogCollector,\n): Promise<CollectedLogs<LogFuncs>>;\n\n/**\n * Synchronous log collector with that collects all categories\n * @public */\nexport function withLogCollector(\n callback: SyncLogCollector,\n): CollectedLogs<LogFuncs>;\n\n/**\n * Asynchronous log collector with that only collects selected categories\n * @public\n */\nexport function withLogCollector<T extends LogFuncs>(\n logsToCollect: T[],\n callback: AsyncLogCollector,\n): Promise<CollectedLogs<T>>;\n\n/**\n * Synchronous log collector with that only collects selected categories\n * @public */\nexport function withLogCollector<T extends LogFuncs>(\n logsToCollect: T[],\n callback: SyncLogCollector,\n): CollectedLogs<T>;\n\n/**\n * Log collector that collect logs either from a sync or async collector.\n * @public\n * @deprecated import from test-utils instead\n * */\nexport function withLogCollector(\n logsToCollect: LogFuncs[] | LogCollector,\n callback?: LogCollector,\n): CollectedLogs<LogFuncs> | Promise<CollectedLogs<LogFuncs>> {\n const oneArg = !callback;\n const actualCallback = (oneArg ? logsToCollect : callback) as LogCollector;\n const categories = (oneArg ? allCategories : logsToCollect) as LogFuncs[];\n\n const logs = {\n log: new Array<string>(),\n warn: new Array<string>(),\n error: new Array<string>(),\n };\n\n const origLog = console.log;\n const origWarn = console.warn;\n const origError = console.error;\n\n if (categories.includes('log')) {\n console.log = (message: string) => {\n logs.log.push(message);\n };\n }\n if (categories.includes('warn')) {\n console.warn = (message: string) => {\n logs.warn.push(message);\n };\n }\n if (categories.includes('error')) {\n console.error = (message: string) => {\n logs.error.push(message);\n };\n }\n\n const restore = () => {\n console.log = origLog;\n console.warn = origWarn;\n console.error = origError;\n };\n\n try {\n const ret = actualCallback();\n\n if (!ret || !ret.then) {\n restore();\n return logs;\n }\n\n return ret.then(\n () => {\n restore();\n return logs;\n },\n error => {\n restore();\n throw error;\n },\n );\n } catch (error) {\n restore();\n throw error;\n }\n}\n","/*\n * Copyright 2020 Spotify AB\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, { ReactNode } from 'react';\nimport { ApiProvider } from '@backstage/core-app-api';\nimport { ApiRef, ApiHolder } from '@backstage/core-plugin-api';\n\n/** @ignore */\ntype TestApiProviderPropsApiPair<TApi> = TApi extends infer TImpl\n ? readonly [ApiRef<TApi>, Partial<TImpl>]\n : never;\n\n/** @ignore */\ntype TestApiProviderPropsApiPairs<TApiPairs> = {\n [TIndex in keyof TApiPairs]: TestApiProviderPropsApiPair<TApiPairs[TIndex]>;\n};\n\n/**\n * Properties for the {@link TestApiProvider} component.\n *\n * @public\n */\nexport type TestApiProviderProps<TApiPairs extends any[]> = {\n apis: readonly [...TestApiProviderPropsApiPairs<TApiPairs>];\n children: ReactNode;\n};\n\n/**\n * The `TestApiRegistry` is an {@link @backstage/core-plugin-api#ApiHolder} implementation\n * that is particularly well suited for development and test environments such as\n * unit tests, storybooks, and isolated plugin development setups.\n *\n * @public\n */\nexport class TestApiRegistry implements ApiHolder {\n /**\n * Creates a new {@link TestApiRegistry} with a list of API implementation pairs.\n *\n * Similar to the {@link TestApiProvider}, there is no need to provide a full\n * implementation of each API, it's enough to implement the methods that are tested.\n *\n * @example\n * ```ts\n * const apis = TestApiRegistry.from(\n * [configApiRef, new ConfigReader({})],\n * [identityApiRef, { getUserId: () => 'tester' }],\n * );\n * ```\n *\n * @public\n * @param apis - A list of pairs mapping an ApiRef to its respective implementation.\n */\n static from<TApiPairs extends any[]>(\n ...apis: readonly [...TestApiProviderPropsApiPairs<TApiPairs>]\n ) {\n return new TestApiRegistry(\n new Map(apis.map(([api, impl]) => [api.id, impl])),\n );\n }\n\n private constructor(private readonly apis: Map<string, unknown>) {}\n\n /**\n * Returns an implementation of the API.\n *\n * @public\n */\n get<T>(api: ApiRef<T>): T | undefined {\n return this.apis.get(api.id) as T | undefined;\n }\n}\n\n/**\n * The `TestApiProvider` is a Utility API context provider that is particularly\n * well suited for development and test environments such as unit tests, storybooks,\n * and isolated plugin development setups.\n *\n * It lets you provide any number of API implementations, without necessarily\n * having to fully implement each of the APIs.\n *\n * A migration from `ApiRegistry` and `ApiProvider` might look like this, from:\n *\n * ```tsx\n * renderInTestApp(\n * <ApiProvider\n * apis={ApiRegistry.from([\n * [identityApiRef, mockIdentityApi as unknown as IdentityApi]\n * ])}\n * >\n * {...}\n * </ApiProvider>\n * )\n * ```\n *\n * To the following:\n *\n * ```tsx\n * renderInTestApp(\n * <TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>\n * {...}\n * </TestApiProvider>\n * )\n * ```\n *\n * Note that the cast to `IdentityApi` is no longer needed as long as the mock API\n * implements a subset of the `IdentityApi`.\n *\n * @public\n **/\nexport const TestApiProvider = <T extends any[]>(\n props: TestApiProviderProps<T>,\n) => {\n return (\n <ApiProvider\n apis={TestApiRegistry.from(...props.apis)}\n children={props.children}\n />\n );\n};\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/test-utils",
|
|
3
3
|
"description": "Utilities to test Backstage plugins and apps.",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.24",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -29,28 +29,30 @@
|
|
|
29
29
|
"clean": "backstage-cli clean"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@backstage/core-app-api": "^0.
|
|
33
|
-
"@backstage/core-plugin-api": "^0.
|
|
34
|
-
"@backstage/theme": "^0.2.
|
|
32
|
+
"@backstage/core-app-api": "^0.2.0",
|
|
33
|
+
"@backstage/core-plugin-api": "^0.3.0",
|
|
34
|
+
"@backstage/theme": "^0.2.14",
|
|
35
35
|
"@backstage/types": "^0.1.1",
|
|
36
36
|
"@material-ui/core": "^4.12.2",
|
|
37
|
+
"@material-ui/icons": "^4.11.2",
|
|
37
38
|
"@testing-library/jest-dom": "^5.10.1",
|
|
38
39
|
"@testing-library/react": "^11.2.5",
|
|
39
40
|
"@testing-library/user-event": "^13.1.8",
|
|
40
|
-
"@types/react": "*",
|
|
41
|
-
"react": "^16.12.0",
|
|
42
|
-
"react-dom": "^16.12.0",
|
|
43
41
|
"react-router": "6.0.0-beta.0",
|
|
44
42
|
"react-router-dom": "6.0.0-beta.0",
|
|
45
43
|
"zen-observable": "^0.8.15"
|
|
46
44
|
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"@types/react": "^16.13.1 || ^17.0.0",
|
|
47
|
+
"react": "^16.13.1 || ^17.0.0"
|
|
48
|
+
},
|
|
47
49
|
"devDependencies": {
|
|
48
|
-
"@backstage/cli": "^0.
|
|
50
|
+
"@backstage/cli": "^0.10.1",
|
|
49
51
|
"@types/jest": "^26.0.7",
|
|
50
52
|
"@types/node": "^14.14.32"
|
|
51
53
|
},
|
|
52
54
|
"files": [
|
|
53
55
|
"dist"
|
|
54
56
|
],
|
|
55
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "562be0b43016294e27af3ad024191bb86b13b1c1"
|
|
56
58
|
}
|