@backstage/test-utils 0.1.18 → 0.1.22
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 +45 -0
- package/dist/index.d.ts +140 -15
- package/dist/index.esm.js +457 -17
- package/dist/index.esm.js.map +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @backstage/test-utils
|
|
2
2
|
|
|
3
|
+
## 0.1.22
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 0b1de52732: Migrated to using new `ErrorApiError` and `ErrorApiErrorContext` names.
|
|
8
|
+
- 2dd2a7b2cc: Migrated to using `createSpecializedApp`.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/core-plugin-api@0.2.0
|
|
11
|
+
- @backstage/core-app-api@0.1.21
|
|
12
|
+
|
|
13
|
+
## 0.1.21
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 71fd5cd735: Update Keyboard deprecation with a link to the recommended successor
|
|
18
|
+
- Updated dependencies
|
|
19
|
+
- @backstage/theme@0.2.13
|
|
20
|
+
- @backstage/core-plugin-api@0.1.13
|
|
21
|
+
- @backstage/core-app-api@0.1.20
|
|
22
|
+
|
|
23
|
+
## 0.1.20
|
|
24
|
+
|
|
25
|
+
### Patch Changes
|
|
26
|
+
|
|
27
|
+
- bb12aae352: Migrates all utility methods from `test-utils-core` into `test-utils` and delete exports from the old package.
|
|
28
|
+
This should have no impact since this package is considered internal and have no usages outside core packages.
|
|
29
|
+
|
|
30
|
+
Notable changes are that the testing tool `msw.setupDefaultHandlers()` have been deprecated in favour of `setupRequestMockHandlers()`.
|
|
31
|
+
|
|
32
|
+
- c5bb1df55d: Bump `msw` to `v0.35.0` to resolve [CVE-2021-32796](https://github.com/advisories/GHSA-5fg8-2547-mr8q).
|
|
33
|
+
- 10615525f3: Switch to use the json and observable types from `@backstage/types`
|
|
34
|
+
- Updated dependencies
|
|
35
|
+
- @backstage/theme@0.2.12
|
|
36
|
+
- @backstage/core-app-api@0.1.19
|
|
37
|
+
- @backstage/core-plugin-api@0.1.12
|
|
38
|
+
|
|
39
|
+
## 0.1.19
|
|
40
|
+
|
|
41
|
+
### Patch Changes
|
|
42
|
+
|
|
43
|
+
- 54bbe25c34: Store the namespaced bucket storage for each instance that was created with `MockStorage.create()` instead of global variable.
|
|
44
|
+
- Updated dependencies
|
|
45
|
+
- @backstage/core-app-api@0.1.17
|
|
46
|
+
- @backstage/theme@0.2.11
|
|
47
|
+
|
|
3
48
|
## 0.1.18
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,41 +1,68 @@
|
|
|
1
|
-
import { AnalyticsApi, AnalyticsEvent,
|
|
1
|
+
import { AnalyticsApi, AnalyticsEvent, ErrorApiError, ErrorApiErrorContext, ErrorApi, StorageApi, StorageValueChange, RouteRef, ExternalRouteRef } from '@backstage/core-plugin-api';
|
|
2
|
+
import { Observable } from '@backstage/types';
|
|
2
3
|
import { ComponentType, ReactNode, ReactElement } from 'react';
|
|
3
4
|
import { RenderResult } from '@testing-library/react';
|
|
4
|
-
export * from '@backstage/test-utils-core';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Mock implementation of {@link core-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.
|
|
8
|
+
* Use getEvents in tests to verify captured events.
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
6
11
|
declare class MockAnalyticsApi implements AnalyticsApi {
|
|
7
12
|
private events;
|
|
8
13
|
captureEvent({ action, subject, value, attributes, context, }: AnalyticsEvent): void;
|
|
9
14
|
getEvents(): AnalyticsEvent[];
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Constructor arguments for {@link MockErrorApi}
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
declare type MockErrorApiOptions = {
|
|
13
22
|
collect?: boolean;
|
|
14
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* ErrorWithContext contains error and ErrorApiErrorContext
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
15
28
|
declare type ErrorWithContext = {
|
|
16
|
-
error:
|
|
17
|
-
context?:
|
|
29
|
+
error: ErrorApiError;
|
|
30
|
+
context?: ErrorApiErrorContext;
|
|
18
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.
|
|
34
|
+
* Incudes withForError and getErrors methods for error testing.
|
|
35
|
+
* @public
|
|
36
|
+
*/
|
|
19
37
|
declare class MockErrorApi implements ErrorApi {
|
|
20
38
|
private readonly options;
|
|
21
39
|
private readonly errors;
|
|
22
40
|
private readonly waiters;
|
|
23
|
-
constructor(options?:
|
|
24
|
-
post(error:
|
|
41
|
+
constructor(options?: MockErrorApiOptions);
|
|
42
|
+
post(error: ErrorApiError, context?: ErrorApiErrorContext): void;
|
|
25
43
|
error$(): Observable<{
|
|
26
|
-
error:
|
|
27
|
-
context?:
|
|
44
|
+
error: ErrorApiError;
|
|
45
|
+
context?: ErrorApiErrorContext;
|
|
28
46
|
}>;
|
|
29
47
|
getErrors(): ErrorWithContext[];
|
|
30
48
|
waitForError(pattern: RegExp, timeoutMs?: number): Promise<ErrorWithContext>;
|
|
31
49
|
}
|
|
32
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Type for map holding data in {@link MockStorageApi}
|
|
53
|
+
* @public
|
|
54
|
+
*/
|
|
33
55
|
declare type MockStorageBucket = {
|
|
34
56
|
[key: string]: any;
|
|
35
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests
|
|
60
|
+
* @public
|
|
61
|
+
*/
|
|
36
62
|
declare class MockStorageApi implements StorageApi {
|
|
37
63
|
private readonly namespace;
|
|
38
64
|
private readonly data;
|
|
65
|
+
private readonly bucketStorageApis;
|
|
39
66
|
private constructor();
|
|
40
67
|
static create(data?: MockStorageBucket): MockStorageApi;
|
|
41
68
|
forBucket(name: string): StorageApi;
|
|
@@ -59,7 +86,7 @@ declare class MockStorageApi implements StorageApi {
|
|
|
59
86
|
* If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced
|
|
60
87
|
* https://material-ui.com/components/use-media-query/#testing
|
|
61
88
|
*
|
|
62
|
-
* @
|
|
89
|
+
* @public
|
|
63
90
|
*/
|
|
64
91
|
declare function mockBreakpoint({ matches }: {
|
|
65
92
|
matches?: boolean | undefined;
|
|
@@ -67,6 +94,7 @@ declare function mockBreakpoint({ matches }: {
|
|
|
67
94
|
|
|
68
95
|
/**
|
|
69
96
|
* Options to customize the behavior of the test app wrapper.
|
|
97
|
+
* @public
|
|
70
98
|
*/
|
|
71
99
|
declare type TestAppOptions = {
|
|
72
100
|
/**
|
|
@@ -79,11 +107,11 @@ declare type TestAppOptions = {
|
|
|
79
107
|
* used by `useRouteRef` in the rendered elements.
|
|
80
108
|
*
|
|
81
109
|
* @example
|
|
82
|
-
* wrapInTestApp(<MyComponent />, {
|
|
83
|
-
* mountedRoutes: {
|
|
110
|
+
* wrapInTestApp(<MyComponent />, \{
|
|
111
|
+
* mountedRoutes: \{
|
|
84
112
|
* '/my-path': myRouteRef,
|
|
85
|
-
* }
|
|
86
|
-
* })
|
|
113
|
+
* \}
|
|
114
|
+
* \})
|
|
87
115
|
* // ...
|
|
88
116
|
* const link = useRouteRef(myRouteRef)
|
|
89
117
|
*/
|
|
@@ -97,6 +125,7 @@ declare type TestAppOptions = {
|
|
|
97
125
|
*
|
|
98
126
|
* @param Component - A component or react node to render inside the test app.
|
|
99
127
|
* @param options - Additional options for the rendering.
|
|
128
|
+
* @public
|
|
100
129
|
*/
|
|
101
130
|
declare function wrapInTestApp(Component: ComponentType | ReactNode, options?: TestAppOptions): ReactElement;
|
|
102
131
|
/**
|
|
@@ -108,9 +137,14 @@ declare function wrapInTestApp(Component: ComponentType | ReactNode, options?: T
|
|
|
108
137
|
*
|
|
109
138
|
* @param Component - A component or react node to render inside the test app.
|
|
110
139
|
* @param options - Additional options for the rendering.
|
|
140
|
+
* @public
|
|
111
141
|
*/
|
|
112
142
|
declare function renderInTestApp(Component: ComponentType | ReactNode, options?: TestAppOptions): Promise<RenderResult>;
|
|
113
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.
|
|
146
|
+
* @public
|
|
147
|
+
*/
|
|
114
148
|
declare const msw: {
|
|
115
149
|
setupDefaultHandlers: (worker: {
|
|
116
150
|
listen: (t: any) => void;
|
|
@@ -118,5 +152,96 @@ declare const msw: {
|
|
|
118
152
|
resetHandlers: () => void;
|
|
119
153
|
}) => void;
|
|
120
154
|
};
|
|
155
|
+
/**
|
|
156
|
+
* Sets up handlers for request mocking
|
|
157
|
+
* @public
|
|
158
|
+
* @param worker - service worker
|
|
159
|
+
*/
|
|
160
|
+
declare function setupRequestMockHandlers(worker: {
|
|
161
|
+
listen: (t: any) => void;
|
|
162
|
+
close: () => void;
|
|
163
|
+
resetHandlers: () => void;
|
|
164
|
+
}): void;
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* @public
|
|
168
|
+
* @deprecated superseded by {@link @testing-library/user-event#userEvent}
|
|
169
|
+
*/
|
|
170
|
+
declare class Keyboard {
|
|
171
|
+
static type(target: any, input: any): Promise<void>;
|
|
172
|
+
static typeDebug(target: any, input: any): Promise<void>;
|
|
173
|
+
static toReadableInput(chars: any): any;
|
|
174
|
+
static fromReadableInput(input: any): any;
|
|
175
|
+
constructor(target: any, { debug }?: {
|
|
176
|
+
debug?: boolean | undefined;
|
|
177
|
+
});
|
|
178
|
+
debug: boolean;
|
|
179
|
+
document: any;
|
|
180
|
+
toString(): string;
|
|
181
|
+
_log(message: any, ...args: any[]): void;
|
|
182
|
+
_pretty(element: any): string;
|
|
183
|
+
get focused(): any;
|
|
184
|
+
type(input: any): Promise<void>;
|
|
185
|
+
send(chars: any): Promise<void>;
|
|
186
|
+
click(): Promise<void>;
|
|
187
|
+
tab(): Promise<void>;
|
|
188
|
+
enter(value: any): Promise<void>;
|
|
189
|
+
escape(): Promise<void>;
|
|
190
|
+
_sendKey(key: any, charCode: any, action: any): Promise<void>;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Severity levels of {@link CollectedLogs}
|
|
195
|
+
* @public */
|
|
196
|
+
declare type LogFuncs = 'log' | 'warn' | 'error';
|
|
197
|
+
/**
|
|
198
|
+
* AsyncLogCollector type used in {@link (withLogCollector:1)} callback function.
|
|
199
|
+
* @public */
|
|
200
|
+
declare type AsyncLogCollector = () => Promise<void>;
|
|
201
|
+
/**
|
|
202
|
+
* SyncLogCollector type used in {@link (withLogCollector:2)} callback function.
|
|
203
|
+
* @public */
|
|
204
|
+
declare type SyncLogCollector = () => void;
|
|
205
|
+
/**
|
|
206
|
+
* Union type used in {@link (withLogCollector:3)} callback function.
|
|
207
|
+
* @public */
|
|
208
|
+
declare type LogCollector = AsyncLogCollector | SyncLogCollector;
|
|
209
|
+
/**
|
|
210
|
+
* Map of severity level and corresponding log lines.
|
|
211
|
+
* @public */
|
|
212
|
+
declare type CollectedLogs<T extends LogFuncs> = {
|
|
213
|
+
[key in T]: string[];
|
|
214
|
+
};
|
|
215
|
+
/**
|
|
216
|
+
* Asynchronous log collector with that collects all categories
|
|
217
|
+
* @public */
|
|
218
|
+
declare function withLogCollector(callback: AsyncLogCollector): Promise<CollectedLogs<LogFuncs>>;
|
|
219
|
+
/**
|
|
220
|
+
* Synchronous log collector with that collects all categories
|
|
221
|
+
* @public */
|
|
222
|
+
declare function withLogCollector(callback: SyncLogCollector): CollectedLogs<LogFuncs>;
|
|
223
|
+
/**
|
|
224
|
+
* Asynchronous log collector with that only collects selected categories
|
|
225
|
+
* @public
|
|
226
|
+
*/
|
|
227
|
+
declare function withLogCollector<T extends LogFuncs>(logsToCollect: T[], callback: AsyncLogCollector): Promise<CollectedLogs<T>>;
|
|
228
|
+
/**
|
|
229
|
+
* Synchronous log collector with that only collects selected categories
|
|
230
|
+
* @public */
|
|
231
|
+
declare function withLogCollector<T extends LogFuncs>(logsToCollect: T[], callback: SyncLogCollector): CollectedLogs<T>;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @public
|
|
235
|
+
* Simplifies rendering of async components in by taking care of the wrapping inside act
|
|
236
|
+
*
|
|
237
|
+
* @remarks
|
|
238
|
+
*
|
|
239
|
+
* Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async
|
|
240
|
+
* act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit
|
|
241
|
+
* cleaner, since act doesn't return the result of the evaluated function.
|
|
242
|
+
* https://github.com/testing-library/react-testing-library/issues/281
|
|
243
|
+
* https://github.com/facebook/react/pull/14853
|
|
244
|
+
*/
|
|
245
|
+
declare function renderWithEffects(nodes: ReactElement): Promise<RenderResult>;
|
|
121
246
|
|
|
122
|
-
export { MockAnalyticsApi, MockErrorApi, MockStorageApi, MockStorageBucket, mockBreakpoint, msw, renderInTestApp, wrapInTestApp };
|
|
247
|
+
export { AsyncLogCollector, CollectedLogs, ErrorWithContext, Keyboard, LogCollector, LogFuncs, MockAnalyticsApi, MockErrorApi, MockErrorApiOptions, MockStorageApi, MockStorageBucket, SyncLogCollector, TestAppOptions, mockBreakpoint, msw, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
package/dist/index.esm.js
CHANGED
|
@@ -3,10 +3,13 @@ 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
|
-
|
|
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 } 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 } from 'react-dom/test-utils';
|
|
12
|
+
import { render, fireEvent, act as act$1 } from '@testing-library/react';
|
|
10
13
|
|
|
11
14
|
class MockAnalyticsApi {
|
|
12
15
|
constructor() {
|
|
@@ -74,9 +77,8 @@ class MockErrorApi {
|
|
|
74
77
|
}
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
const bucketStorageApis = new Map();
|
|
78
80
|
class MockStorageApi {
|
|
79
|
-
constructor(namespace, data) {
|
|
81
|
+
constructor(namespace, bucketStorageApis, data) {
|
|
80
82
|
this.subscribers = new Set();
|
|
81
83
|
this.observable = new ObservableImpl((subscriber) => {
|
|
82
84
|
this.subscribers.add(subscriber);
|
|
@@ -85,16 +87,17 @@ class MockStorageApi {
|
|
|
85
87
|
};
|
|
86
88
|
});
|
|
87
89
|
this.namespace = namespace;
|
|
90
|
+
this.bucketStorageApis = bucketStorageApis;
|
|
88
91
|
this.data = {...data};
|
|
89
92
|
}
|
|
90
93
|
static create(data) {
|
|
91
|
-
return new MockStorageApi("", data);
|
|
94
|
+
return new MockStorageApi("", new Map(), data);
|
|
92
95
|
}
|
|
93
96
|
forBucket(name) {
|
|
94
|
-
if (!bucketStorageApis.has(name)) {
|
|
95
|
-
bucketStorageApis.set(name, new MockStorageApi(`${this.namespace}/${name}`, this.data));
|
|
97
|
+
if (!this.bucketStorageApis.has(name)) {
|
|
98
|
+
this.bucketStorageApis.set(name, new MockStorageApi(`${this.namespace}/${name}`, this.bucketStorageApis, this.data));
|
|
96
99
|
}
|
|
97
|
-
return bucketStorageApis.get(name);
|
|
100
|
+
return this.bucketStorageApis.get(name);
|
|
98
101
|
}
|
|
99
102
|
get(key) {
|
|
100
103
|
return this.data[this.getKeyName(key)];
|
|
@@ -136,11 +139,230 @@ function mockBreakpoint({matches = false}) {
|
|
|
136
139
|
});
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
async function renderWithEffects(nodes) {
|
|
143
|
+
let value;
|
|
144
|
+
await act(async () => {
|
|
145
|
+
value = render(nodes);
|
|
146
|
+
});
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const defaultApis = [
|
|
151
|
+
createApiFactory({
|
|
152
|
+
api: discoveryApiRef,
|
|
153
|
+
deps: {configApi: configApiRef},
|
|
154
|
+
factory: ({configApi}) => UrlPatternDiscovery.compile(`${configApi.getString("backend.baseUrl")}/api/{{ pluginId }}`)
|
|
155
|
+
}),
|
|
156
|
+
createApiFactory(alertApiRef, new AlertApiForwarder()),
|
|
157
|
+
createApiFactory(analyticsApiRef, new NoOpAnalyticsApi()),
|
|
158
|
+
createApiFactory({
|
|
159
|
+
api: errorApiRef,
|
|
160
|
+
deps: {alertApi: alertApiRef},
|
|
161
|
+
factory: ({alertApi}) => {
|
|
162
|
+
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
|
|
163
|
+
UnhandledErrorForwarder.forward(errorApi, {hidden: false});
|
|
164
|
+
return errorApi;
|
|
165
|
+
}
|
|
166
|
+
}),
|
|
167
|
+
createApiFactory({
|
|
168
|
+
api: storageApiRef,
|
|
169
|
+
deps: {errorApi: errorApiRef},
|
|
170
|
+
factory: ({errorApi}) => WebStorage.create({errorApi})
|
|
171
|
+
}),
|
|
172
|
+
createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),
|
|
173
|
+
createApiFactory({
|
|
174
|
+
api: googleAuthApiRef,
|
|
175
|
+
deps: {
|
|
176
|
+
discoveryApi: discoveryApiRef,
|
|
177
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
178
|
+
configApi: configApiRef
|
|
179
|
+
},
|
|
180
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => GoogleAuth.create({
|
|
181
|
+
discoveryApi,
|
|
182
|
+
oauthRequestApi,
|
|
183
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
184
|
+
})
|
|
185
|
+
}),
|
|
186
|
+
createApiFactory({
|
|
187
|
+
api: microsoftAuthApiRef,
|
|
188
|
+
deps: {
|
|
189
|
+
discoveryApi: discoveryApiRef,
|
|
190
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
191
|
+
configApi: configApiRef
|
|
192
|
+
},
|
|
193
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => MicrosoftAuth.create({
|
|
194
|
+
discoveryApi,
|
|
195
|
+
oauthRequestApi,
|
|
196
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
197
|
+
})
|
|
198
|
+
}),
|
|
199
|
+
createApiFactory({
|
|
200
|
+
api: githubAuthApiRef,
|
|
201
|
+
deps: {
|
|
202
|
+
discoveryApi: discoveryApiRef,
|
|
203
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
204
|
+
configApi: configApiRef
|
|
205
|
+
},
|
|
206
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => GithubAuth.create({
|
|
207
|
+
discoveryApi,
|
|
208
|
+
oauthRequestApi,
|
|
209
|
+
defaultScopes: ["read:user"],
|
|
210
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
211
|
+
})
|
|
212
|
+
}),
|
|
213
|
+
createApiFactory({
|
|
214
|
+
api: oktaAuthApiRef,
|
|
215
|
+
deps: {
|
|
216
|
+
discoveryApi: discoveryApiRef,
|
|
217
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
218
|
+
configApi: configApiRef
|
|
219
|
+
},
|
|
220
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => OktaAuth.create({
|
|
221
|
+
discoveryApi,
|
|
222
|
+
oauthRequestApi,
|
|
223
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
224
|
+
})
|
|
225
|
+
}),
|
|
226
|
+
createApiFactory({
|
|
227
|
+
api: gitlabAuthApiRef,
|
|
228
|
+
deps: {
|
|
229
|
+
discoveryApi: discoveryApiRef,
|
|
230
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
231
|
+
configApi: configApiRef
|
|
232
|
+
},
|
|
233
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => GitlabAuth.create({
|
|
234
|
+
discoveryApi,
|
|
235
|
+
oauthRequestApi,
|
|
236
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
237
|
+
})
|
|
238
|
+
}),
|
|
239
|
+
createApiFactory({
|
|
240
|
+
api: auth0AuthApiRef,
|
|
241
|
+
deps: {
|
|
242
|
+
discoveryApi: discoveryApiRef,
|
|
243
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
244
|
+
configApi: configApiRef
|
|
245
|
+
},
|
|
246
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => Auth0Auth.create({
|
|
247
|
+
discoveryApi,
|
|
248
|
+
oauthRequestApi,
|
|
249
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
250
|
+
})
|
|
251
|
+
}),
|
|
252
|
+
createApiFactory({
|
|
253
|
+
api: oauth2ApiRef,
|
|
254
|
+
deps: {
|
|
255
|
+
discoveryApi: discoveryApiRef,
|
|
256
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
257
|
+
configApi: configApiRef
|
|
258
|
+
},
|
|
259
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => OAuth2.create({
|
|
260
|
+
discoveryApi,
|
|
261
|
+
oauthRequestApi,
|
|
262
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
263
|
+
})
|
|
264
|
+
}),
|
|
265
|
+
createApiFactory({
|
|
266
|
+
api: samlAuthApiRef,
|
|
267
|
+
deps: {
|
|
268
|
+
discoveryApi: discoveryApiRef,
|
|
269
|
+
configApi: configApiRef
|
|
270
|
+
},
|
|
271
|
+
factory: ({discoveryApi, configApi}) => SamlAuth.create({
|
|
272
|
+
discoveryApi,
|
|
273
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
274
|
+
})
|
|
275
|
+
}),
|
|
276
|
+
createApiFactory({
|
|
277
|
+
api: oneloginAuthApiRef,
|
|
278
|
+
deps: {
|
|
279
|
+
discoveryApi: discoveryApiRef,
|
|
280
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
281
|
+
configApi: configApiRef
|
|
282
|
+
},
|
|
283
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => OneLoginAuth.create({
|
|
284
|
+
discoveryApi,
|
|
285
|
+
oauthRequestApi,
|
|
286
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
287
|
+
})
|
|
288
|
+
}),
|
|
289
|
+
createApiFactory({
|
|
290
|
+
api: oidcAuthApiRef,
|
|
291
|
+
deps: {
|
|
292
|
+
discoveryApi: discoveryApiRef,
|
|
293
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
294
|
+
configApi: configApiRef
|
|
295
|
+
},
|
|
296
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => OAuth2.create({
|
|
297
|
+
discoveryApi,
|
|
298
|
+
oauthRequestApi,
|
|
299
|
+
provider: {
|
|
300
|
+
id: "oidc",
|
|
301
|
+
title: "Your Identity Provider",
|
|
302
|
+
icon: () => null
|
|
303
|
+
},
|
|
304
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
305
|
+
})
|
|
306
|
+
}),
|
|
307
|
+
createApiFactory({
|
|
308
|
+
api: bitbucketAuthApiRef,
|
|
309
|
+
deps: {
|
|
310
|
+
discoveryApi: discoveryApiRef,
|
|
311
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
312
|
+
configApi: configApiRef
|
|
313
|
+
},
|
|
314
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => BitbucketAuth.create({
|
|
315
|
+
discoveryApi,
|
|
316
|
+
oauthRequestApi,
|
|
317
|
+
defaultScopes: ["team"],
|
|
318
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
319
|
+
})
|
|
320
|
+
}),
|
|
321
|
+
createApiFactory({
|
|
322
|
+
api: atlassianAuthApiRef,
|
|
323
|
+
deps: {
|
|
324
|
+
discoveryApi: discoveryApiRef,
|
|
325
|
+
oauthRequestApi: oauthRequestApiRef,
|
|
326
|
+
configApi: configApiRef
|
|
327
|
+
},
|
|
328
|
+
factory: ({discoveryApi, oauthRequestApi, configApi}) => {
|
|
329
|
+
return AtlassianAuth.create({
|
|
330
|
+
discoveryApi,
|
|
331
|
+
oauthRequestApi,
|
|
332
|
+
environment: configApi.getOptionalString("auth.environment")
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
})
|
|
336
|
+
];
|
|
337
|
+
|
|
139
338
|
const mockApis = [
|
|
140
339
|
createApiFactory(errorApiRef, new MockErrorApi()),
|
|
141
340
|
createApiFactory(storageApiRef, MockStorageApi.create())
|
|
142
341
|
];
|
|
143
342
|
|
|
343
|
+
const mockIcons = {
|
|
344
|
+
"kind:api": MockIcon,
|
|
345
|
+
"kind:component": MockIcon,
|
|
346
|
+
"kind:domain": MockIcon,
|
|
347
|
+
"kind:group": MockIcon,
|
|
348
|
+
"kind:location": MockIcon,
|
|
349
|
+
"kind:system": MockIcon,
|
|
350
|
+
"kind:user": MockIcon,
|
|
351
|
+
brokenImage: MockIcon,
|
|
352
|
+
catalog: MockIcon,
|
|
353
|
+
scaffolder: MockIcon,
|
|
354
|
+
techdocs: MockIcon,
|
|
355
|
+
search: MockIcon,
|
|
356
|
+
chat: MockIcon,
|
|
357
|
+
dashboard: MockIcon,
|
|
358
|
+
docs: MockIcon,
|
|
359
|
+
email: MockIcon,
|
|
360
|
+
github: MockIcon,
|
|
361
|
+
group: MockIcon,
|
|
362
|
+
help: MockIcon,
|
|
363
|
+
user: MockIcon,
|
|
364
|
+
warning: MockIcon
|
|
365
|
+
};
|
|
144
366
|
const ErrorBoundaryFallback = ({error}) => {
|
|
145
367
|
throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`);
|
|
146
368
|
};
|
|
@@ -160,8 +382,9 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
160
382
|
var _a;
|
|
161
383
|
const {routeEntries = ["/"]} = options;
|
|
162
384
|
const boundRoutes = new Map();
|
|
163
|
-
const app =
|
|
385
|
+
const app = createSpecializedApp({
|
|
164
386
|
apis: mockApis,
|
|
387
|
+
defaultApis,
|
|
165
388
|
configLoader: false,
|
|
166
389
|
components: {
|
|
167
390
|
Progress,
|
|
@@ -173,13 +396,16 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
173
396
|
children
|
|
174
397
|
})
|
|
175
398
|
},
|
|
399
|
+
icons: mockIcons,
|
|
176
400
|
plugins: [],
|
|
177
401
|
themes: [
|
|
178
402
|
{
|
|
179
403
|
id: "light",
|
|
180
|
-
theme: lightTheme,
|
|
181
404
|
title: "Test App Theme",
|
|
182
|
-
variant: "light"
|
|
405
|
+
variant: "light",
|
|
406
|
+
Provider: ({children}) => /* @__PURE__ */ React.createElement(ThemeProvider, {
|
|
407
|
+
theme: lightTheme
|
|
408
|
+
}, /* @__PURE__ */ React.createElement(CssBaseline, null, children))
|
|
183
409
|
}
|
|
184
410
|
],
|
|
185
411
|
bindRoutes: ({bind}) => {
|
|
@@ -224,11 +450,225 @@ async function renderInTestApp(Component, options = {}) {
|
|
|
224
450
|
|
|
225
451
|
const msw = {
|
|
226
452
|
setupDefaultHandlers: (worker) => {
|
|
227
|
-
|
|
228
|
-
afterAll(() => worker.close());
|
|
229
|
-
afterEach(() => worker.resetHandlers());
|
|
453
|
+
setupRequestMockHandlers(worker);
|
|
230
454
|
}
|
|
231
455
|
};
|
|
456
|
+
function setupRequestMockHandlers(worker) {
|
|
457
|
+
beforeAll(() => worker.listen({onUnhandledRequest: "error"}));
|
|
458
|
+
afterAll(() => worker.close());
|
|
459
|
+
afterEach(() => worker.resetHandlers());
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const codes = {
|
|
463
|
+
Tab: 9,
|
|
464
|
+
Enter: 10,
|
|
465
|
+
Click: 17,
|
|
466
|
+
Esc: 27
|
|
467
|
+
};
|
|
468
|
+
class Keyboard {
|
|
469
|
+
static async type(target, input) {
|
|
470
|
+
await new Keyboard(target).type(input);
|
|
471
|
+
}
|
|
472
|
+
static async typeDebug(target, input) {
|
|
473
|
+
await new Keyboard(target, {debug: true}).type(input);
|
|
474
|
+
}
|
|
475
|
+
static toReadableInput(chars) {
|
|
476
|
+
return chars.split("").map((char) => {
|
|
477
|
+
switch (char.charCodeAt(0)) {
|
|
478
|
+
case codes.Tab:
|
|
479
|
+
return "<Tab>";
|
|
480
|
+
case codes.Enter:
|
|
481
|
+
return "<Enter>";
|
|
482
|
+
case codes.Click:
|
|
483
|
+
return "<Click>";
|
|
484
|
+
case codes.Esc:
|
|
485
|
+
return "<Esc>";
|
|
486
|
+
default:
|
|
487
|
+
return char;
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
static fromReadableInput(input) {
|
|
492
|
+
return input.trim().replace(/\s*<([a-zA-Z]+)>\s*/g, (match, name) => {
|
|
493
|
+
if (name in codes) {
|
|
494
|
+
return String.fromCharCode(codes[name]);
|
|
495
|
+
}
|
|
496
|
+
throw new Error(`Unknown char name: '${name}'`);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
constructor(target, {debug = false} = {}) {
|
|
500
|
+
this.debug = debug;
|
|
501
|
+
if (target.ownerDocument) {
|
|
502
|
+
this.document = target.ownerDocument;
|
|
503
|
+
} else if (target.baseElement) {
|
|
504
|
+
this.document = target.baseElement.ownerDocument;
|
|
505
|
+
} else {
|
|
506
|
+
throw new TypeError("Keyboard(target): target must be DOM node or react-testing-library render() output");
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
toString() {
|
|
510
|
+
return `Keyboard{document=${this.document}, debug=${this.debug}}`;
|
|
511
|
+
}
|
|
512
|
+
_log(message, ...args) {
|
|
513
|
+
if (this.debug) {
|
|
514
|
+
console.log(`[Keyboard] ${message}`, ...args);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
_pretty(element) {
|
|
518
|
+
const attrs = [...element.attributes].map((attr) => `${attr.name}="${attr.value}"`).join(" ");
|
|
519
|
+
return `<${element.nodeName.toLocaleLowerCase("en-US")} ${attrs}>`;
|
|
520
|
+
}
|
|
521
|
+
get focused() {
|
|
522
|
+
return this.document.activeElement;
|
|
523
|
+
}
|
|
524
|
+
async type(input) {
|
|
525
|
+
this._log(`sending sequence '${input}' with initial focus ${this._pretty(this.focused)}`);
|
|
526
|
+
await this.send(Keyboard.fromReadableInput(input));
|
|
527
|
+
}
|
|
528
|
+
async send(chars) {
|
|
529
|
+
for (const key of chars.split("")) {
|
|
530
|
+
const charCode = key.charCodeAt(0);
|
|
531
|
+
if (charCode === codes.Tab) {
|
|
532
|
+
await this.tab();
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
const focused = this.focused;
|
|
536
|
+
if (!focused || focused === this.document.body) {
|
|
537
|
+
throw Error(`No element focused in document while trying to type '${Keyboard.toReadableInput(chars)}'`);
|
|
538
|
+
}
|
|
539
|
+
const nextValue = (focused.value || "") + key;
|
|
540
|
+
if (charCode >= 32) {
|
|
541
|
+
await this._sendKey(key, charCode, () => {
|
|
542
|
+
this._log(`sending +${key} = '${nextValue}' to ${this._pretty(focused)}`);
|
|
543
|
+
fireEvent.change(focused, {
|
|
544
|
+
target: {value: nextValue},
|
|
545
|
+
bubbles: true,
|
|
546
|
+
cancelable: true
|
|
547
|
+
});
|
|
548
|
+
});
|
|
549
|
+
} else if (charCode === codes.Enter) {
|
|
550
|
+
await this.enter(focused.value || "");
|
|
551
|
+
} else if (charCode === codes.Esc) {
|
|
552
|
+
await this.escape();
|
|
553
|
+
} else if (charCode === codes.Click) {
|
|
554
|
+
await this.click();
|
|
555
|
+
} else {
|
|
556
|
+
throw new Error(`Unsupported char code, ${charCode}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
async click() {
|
|
561
|
+
this._log(`clicking ${this._pretty(this.focused)}`);
|
|
562
|
+
await act$1(async () => fireEvent.click(this.focused));
|
|
563
|
+
}
|
|
564
|
+
async tab() {
|
|
565
|
+
await this._sendKey("Tab", codes.Tab, () => {
|
|
566
|
+
const focusable = this.document.querySelectorAll([
|
|
567
|
+
"a[href]",
|
|
568
|
+
"area[href]",
|
|
569
|
+
"input:not([disabled])",
|
|
570
|
+
"select:not([disabled])",
|
|
571
|
+
"textarea:not([disabled])",
|
|
572
|
+
"button:not([disabled])",
|
|
573
|
+
"iframe",
|
|
574
|
+
"object",
|
|
575
|
+
"embed",
|
|
576
|
+
"*[tabindex]",
|
|
577
|
+
"*[contenteditable]"
|
|
578
|
+
].join(","));
|
|
579
|
+
const tabbable = [...focusable].filter((el) => {
|
|
580
|
+
return el.tabIndex >= 0;
|
|
581
|
+
});
|
|
582
|
+
const focused = this.document.activeElement;
|
|
583
|
+
const focusedIndex = tabbable.indexOf(focused);
|
|
584
|
+
const nextFocus = tabbable[focusedIndex + 1 % tabbable.length];
|
|
585
|
+
this._log(`tabbing to ${this._pretty(nextFocus)} ${this.focused.textContent}`);
|
|
586
|
+
nextFocus.focus();
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
async enter(value) {
|
|
590
|
+
this._log(`submitting '${value}' via ${this._pretty(this.focused)}`);
|
|
591
|
+
await act$1(() => this._sendKey("Enter", codes.Enter, () => {
|
|
592
|
+
if (this.focused.type === "button") {
|
|
593
|
+
fireEvent.click(this.focused, {target: {value}});
|
|
594
|
+
} else {
|
|
595
|
+
fireEvent.submit(this.focused, {
|
|
596
|
+
target: {value},
|
|
597
|
+
bubbles: true,
|
|
598
|
+
cancelable: true
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}));
|
|
602
|
+
}
|
|
603
|
+
async escape() {
|
|
604
|
+
this._log(`escape from ${this._pretty(this.focused)}`);
|
|
605
|
+
await act$1(async () => this._sendKey("Escape", codes.Esc));
|
|
606
|
+
}
|
|
607
|
+
async _sendKey(key, charCode, action) {
|
|
608
|
+
const event = {key, charCode, keyCode: charCode, which: charCode};
|
|
609
|
+
const focused = this.focused;
|
|
610
|
+
if (fireEvent.keyDown(focused, event)) {
|
|
611
|
+
if (fireEvent.keyPress(focused, event)) {
|
|
612
|
+
if (action) {
|
|
613
|
+
action();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
fireEvent.keyUp(focused, event);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const allCategories = ["log", "warn", "error"];
|
|
622
|
+
function withLogCollector(logsToCollect, callback) {
|
|
623
|
+
const oneArg = !callback;
|
|
624
|
+
const actualCallback = oneArg ? logsToCollect : callback;
|
|
625
|
+
const categories = oneArg ? allCategories : logsToCollect;
|
|
626
|
+
const logs = {
|
|
627
|
+
log: new Array(),
|
|
628
|
+
warn: new Array(),
|
|
629
|
+
error: new Array()
|
|
630
|
+
};
|
|
631
|
+
const origLog = console.log;
|
|
632
|
+
const origWarn = console.warn;
|
|
633
|
+
const origError = console.error;
|
|
634
|
+
if (categories.includes("log")) {
|
|
635
|
+
console.log = (message) => {
|
|
636
|
+
logs.log.push(message);
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
if (categories.includes("warn")) {
|
|
640
|
+
console.warn = (message) => {
|
|
641
|
+
logs.warn.push(message);
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
if (categories.includes("error")) {
|
|
645
|
+
console.error = (message) => {
|
|
646
|
+
logs.error.push(message);
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
const restore = () => {
|
|
650
|
+
console.log = origLog;
|
|
651
|
+
console.warn = origWarn;
|
|
652
|
+
console.error = origError;
|
|
653
|
+
};
|
|
654
|
+
try {
|
|
655
|
+
const ret = actualCallback();
|
|
656
|
+
if (!ret || !ret.then) {
|
|
657
|
+
restore();
|
|
658
|
+
return logs;
|
|
659
|
+
}
|
|
660
|
+
return ret.then(() => {
|
|
661
|
+
restore();
|
|
662
|
+
return logs;
|
|
663
|
+
}, (error) => {
|
|
664
|
+
restore();
|
|
665
|
+
throw error;
|
|
666
|
+
});
|
|
667
|
+
} catch (error) {
|
|
668
|
+
restore();
|
|
669
|
+
throw error;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
232
672
|
|
|
233
|
-
export { MockAnalyticsApi, MockErrorApi, MockStorageApi, mockBreakpoint, msw, renderInTestApp, wrapInTestApp };
|
|
673
|
+
export { Keyboard, MockAnalyticsApi, MockErrorApi, MockStorageApi, mockBreakpoint, msw, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
|
234
674
|
//# 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/mockApis.ts","../src/testUtils/appWrappers.tsx","../src/testUtils/msw/index.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\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, Observable } from '@backstage/core-plugin-api';\n\ntype Options = {\n collect?: boolean;\n};\n\ntype 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\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: Options = {}) {}\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 {\n Observable,\n StorageApi,\n StorageValueChange,\n} from '@backstage/core-plugin-api';\nimport ObservableImpl from 'zen-observable';\n\nexport type MockStorageBucket = { [key: string]: any };\n\nconst bucketStorageApis = new Map<string, MockStorageApi>();\n\nexport class MockStorageApi implements StorageApi {\n private readonly namespace: string;\n private readonly data: MockStorageBucket;\n\n private constructor(namespace: string, data?: MockStorageBucket) {\n this.namespace = namespace;\n this.data = { ...data };\n }\n\n static create(data?: MockStorageBucket) {\n return new MockStorageApi('', data);\n }\n\n forBucket(name: string): StorageApi {\n if (!bucketStorageApis.has(name)) {\n bucketStorageApis.set(\n name,\n new MockStorageApi(`${this.namespace}/${name}`, this.data),\n );\n }\n return 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 * @param matchMediaOptions\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 {\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 '@backstage/test-utils-core';\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 */\ntype 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 */\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 */\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\nexport const msw = {\n setupDefaultHandlers: (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"],"names":[],"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"],"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 {\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 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 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"],"names":["act"],"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.22",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -29,16 +29,16 @@
|
|
|
29
29
|
"clean": "backstage-cli clean"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@backstage/core-app-api": "^0.1.
|
|
33
|
-
"@backstage/core-plugin-api": "^0.
|
|
34
|
-
"@backstage/
|
|
35
|
-
"@backstage/
|
|
32
|
+
"@backstage/core-app-api": "^0.1.21",
|
|
33
|
+
"@backstage/core-plugin-api": "^0.2.0",
|
|
34
|
+
"@backstage/theme": "^0.2.13",
|
|
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
41
|
"@types/react": "*",
|
|
41
|
-
"msw": "^0.29.0",
|
|
42
42
|
"react": "^16.12.0",
|
|
43
43
|
"react-dom": "^16.12.0",
|
|
44
44
|
"react-router": "6.0.0-beta.0",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"zen-observable": "^0.8.15"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@backstage/cli": "^0.
|
|
49
|
+
"@backstage/cli": "^0.9.0",
|
|
50
50
|
"@types/jest": "^26.0.7",
|
|
51
51
|
"@types/node": "^14.14.32"
|
|
52
52
|
},
|
|
53
53
|
"files": [
|
|
54
54
|
"dist"
|
|
55
55
|
],
|
|
56
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "ddfdcd2b44dc9848cf550cea5346d5f9916a36d9"
|
|
57
57
|
}
|