@backstage/test-utils 0.1.23 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/index.d.ts +68 -48
- package/dist/index.esm.js +154 -236
- package/dist/index.esm.js.map +1 -1
- package/package.json +11 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @backstage/test-utils
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 2d3fd91e33: Add new `MockConfigApi` as a more discoverable and leaner method for mocking configuration.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/config@0.1.12
|
|
10
|
+
- @backstage/core-plugin-api@0.5.0
|
|
11
|
+
- @backstage/core-app-api@0.4.0
|
|
12
|
+
|
|
13
|
+
## 0.2.1
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- c36b7794f7: JSON serialize and freeze values stored by the `MockStorageApi`.
|
|
18
|
+
|
|
19
|
+
## 0.2.0
|
|
20
|
+
|
|
21
|
+
### Minor Changes
|
|
22
|
+
|
|
23
|
+
- a195284c7b: Updated `MockStorageApi` to reflect the `StorageApi` changes in `@backstage/core-plugin-api`.
|
|
24
|
+
- 771b9c07fe: Removed deprecated `Keyboard` class which has been superseded by `@testing-library/user-event#userEvent`
|
|
25
|
+
- f6722d2458: Removed deprecated `msw` definition which was replaced by calling `setupRequestMockHandlers` directly
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- Updated dependencies
|
|
30
|
+
- @backstage/core-app-api@0.3.0
|
|
31
|
+
- @backstage/core-plugin-api@0.4.0
|
|
32
|
+
|
|
33
|
+
## 0.1.24
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- cd450844f6: Moved React dependencies to `peerDependencies` and allow both React v16 and v17 to be used.
|
|
38
|
+
- dcd1a0c3f4: Minor improvement to the API reports, by not unpacking arguments directly
|
|
39
|
+
- Updated dependencies
|
|
40
|
+
- @backstage/core-plugin-api@0.3.0
|
|
41
|
+
- @backstage/core-app-api@0.2.0
|
|
42
|
+
|
|
3
43
|
## 0.1.23
|
|
4
44
|
|
|
5
45
|
### Patch Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,76 @@
|
|
|
1
|
-
import { AnalyticsApi, AnalyticsEvent, ErrorApiError, ErrorApiErrorContext, ErrorApi, StorageApi,
|
|
2
|
-
import {
|
|
1
|
+
import { AnalyticsApi, AnalyticsEvent, ConfigApi, ErrorApiError, ErrorApiErrorContext, ErrorApi, StorageApi, StorageValueSnapshot, RouteRef, ExternalRouteRef, ApiHolder, ApiRef } from '@backstage/core-plugin-api';
|
|
2
|
+
import { Config } from '@backstage/config';
|
|
3
|
+
import { JsonObject, JsonValue, Observable } from '@backstage/types';
|
|
3
4
|
import { ComponentType, ReactNode, ReactElement } from 'react';
|
|
4
5
|
import { RenderResult } from '@testing-library/react';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Mock implementation of {@link core-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.
|
|
8
9
|
* Use getEvents in tests to verify captured events.
|
|
10
|
+
*
|
|
9
11
|
* @public
|
|
10
12
|
*/
|
|
11
13
|
declare class MockAnalyticsApi implements AnalyticsApi {
|
|
12
14
|
private events;
|
|
13
|
-
captureEvent(
|
|
15
|
+
captureEvent(event: AnalyticsEvent): void;
|
|
14
16
|
getEvents(): AnalyticsEvent[];
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
/**
|
|
20
|
+
* MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader}
|
|
21
|
+
* that can be used to mock configuration using a plain object.
|
|
22
|
+
*
|
|
23
|
+
* @public
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const mockConfig = new MockConfigApi({
|
|
27
|
+
* app: { baseUrl: 'https://example.com' },
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* const rendered = await renderInTestApp(
|
|
31
|
+
* <TestApiProvider apis={[[configApiRef, mockConfig]]}>
|
|
32
|
+
* <MyTestedComponent />
|
|
33
|
+
* </TestApiProvider>,
|
|
34
|
+
* );
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare class MockConfigApi implements ConfigApi {
|
|
38
|
+
private readonly config;
|
|
39
|
+
constructor(data: JsonObject);
|
|
40
|
+
/** {@inheritdoc @backstage/config#Config.has} */
|
|
41
|
+
has(key: string): boolean;
|
|
42
|
+
/** {@inheritdoc @backstage/config#Config.keys} */
|
|
43
|
+
keys(): string[];
|
|
44
|
+
/** {@inheritdoc @backstage/config#Config.get} */
|
|
45
|
+
get<T = JsonValue>(key?: string): T;
|
|
46
|
+
/** {@inheritdoc @backstage/config#Config.getOptional} */
|
|
47
|
+
getOptional<T = JsonValue>(key?: string): T | undefined;
|
|
48
|
+
/** {@inheritdoc @backstage/config#Config.getConfig} */
|
|
49
|
+
getConfig(key: string): Config;
|
|
50
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalConfig} */
|
|
51
|
+
getOptionalConfig(key: string): Config | undefined;
|
|
52
|
+
/** {@inheritdoc @backstage/config#Config.getConfigArray} */
|
|
53
|
+
getConfigArray(key: string): Config[];
|
|
54
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */
|
|
55
|
+
getOptionalConfigArray(key: string): Config[] | undefined;
|
|
56
|
+
/** {@inheritdoc @backstage/config#Config.getNumber} */
|
|
57
|
+
getNumber(key: string): number;
|
|
58
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalNumber} */
|
|
59
|
+
getOptionalNumber(key: string): number | undefined;
|
|
60
|
+
/** {@inheritdoc @backstage/config#Config.getBoolean} */
|
|
61
|
+
getBoolean(key: string): boolean;
|
|
62
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */
|
|
63
|
+
getOptionalBoolean(key: string): boolean | undefined;
|
|
64
|
+
/** {@inheritdoc @backstage/config#Config.getString} */
|
|
65
|
+
getString(key: string): string;
|
|
66
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalString} */
|
|
67
|
+
getOptionalString(key: string): string | undefined;
|
|
68
|
+
/** {@inheritdoc @backstage/config#Config.getStringArray} */
|
|
69
|
+
getStringArray(key: string): string[];
|
|
70
|
+
/** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */
|
|
71
|
+
getOptionalStringArray(key: string): string[] | undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
17
74
|
/**
|
|
18
75
|
* Constructor arguments for {@link MockErrorApi}
|
|
19
76
|
* @public
|
|
@@ -67,9 +124,10 @@ declare class MockStorageApi implements StorageApi {
|
|
|
67
124
|
static create(data?: MockStorageBucket): MockStorageApi;
|
|
68
125
|
forBucket(name: string): StorageApi;
|
|
69
126
|
get<T>(key: string): T | undefined;
|
|
127
|
+
snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T>;
|
|
70
128
|
set<T>(key: string, data: T): Promise<void>;
|
|
71
129
|
remove(key: string): Promise<void>;
|
|
72
|
-
observe$<T>(key: string): Observable<
|
|
130
|
+
observe$<T>(key: string): Observable<StorageValueSnapshot<T>>;
|
|
73
131
|
private getKeyName;
|
|
74
132
|
private notifyChanges;
|
|
75
133
|
private subscribers;
|
|
@@ -77,8 +135,8 @@ declare class MockStorageApi implements StorageApi {
|
|
|
77
135
|
}
|
|
78
136
|
|
|
79
137
|
/**
|
|
80
|
-
* This is a mocking method suggested in the Jest
|
|
81
|
-
* It can be used to mock values
|
|
138
|
+
* This is a mocking method suggested in the Jest docs, as it is not implemented in JSDOM yet.
|
|
139
|
+
* It can be used to mock values for the MUI `useMediaQuery` hook if it is used in a tested component.
|
|
82
140
|
*
|
|
83
141
|
* For issues checkout the documentation:
|
|
84
142
|
* https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
|
|
@@ -88,8 +146,8 @@ declare class MockStorageApi implements StorageApi {
|
|
|
88
146
|
*
|
|
89
147
|
* @public
|
|
90
148
|
*/
|
|
91
|
-
declare function mockBreakpoint(
|
|
92
|
-
matches
|
|
149
|
+
declare function mockBreakpoint(options: {
|
|
150
|
+
matches: boolean;
|
|
93
151
|
}): void;
|
|
94
152
|
|
|
95
153
|
/**
|
|
@@ -141,17 +199,6 @@ declare function wrapInTestApp(Component: ComponentType | ReactNode, options?: T
|
|
|
141
199
|
*/
|
|
142
200
|
declare function renderInTestApp(Component: ComponentType | ReactNode, options?: TestAppOptions): Promise<RenderResult>;
|
|
143
201
|
|
|
144
|
-
/**
|
|
145
|
-
* @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.
|
|
146
|
-
* @public
|
|
147
|
-
*/
|
|
148
|
-
declare const msw: {
|
|
149
|
-
setupDefaultHandlers: (worker: {
|
|
150
|
-
listen: (t: any) => void;
|
|
151
|
-
close: () => void;
|
|
152
|
-
resetHandlers: () => void;
|
|
153
|
-
}) => void;
|
|
154
|
-
};
|
|
155
202
|
/**
|
|
156
203
|
* Sets up handlers for request mocking
|
|
157
204
|
* @public
|
|
@@ -163,33 +210,6 @@ declare function setupRequestMockHandlers(worker: {
|
|
|
163
210
|
resetHandlers: () => void;
|
|
164
211
|
}): void;
|
|
165
212
|
|
|
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
213
|
/**
|
|
194
214
|
* Severity levels of {@link CollectedLogs}
|
|
195
215
|
* @public */
|
|
@@ -331,6 +351,6 @@ declare class TestApiRegistry implements ApiHolder {
|
|
|
331
351
|
*
|
|
332
352
|
* @public
|
|
333
353
|
**/
|
|
334
|
-
declare const TestApiProvider: <T extends any[]>(
|
|
354
|
+
declare const TestApiProvider: <T extends any[]>(props: TestApiProviderProps<T>) => JSX.Element;
|
|
335
355
|
|
|
336
|
-
export { AsyncLogCollector, CollectedLogs, ErrorWithContext,
|
|
356
|
+
export { AsyncLogCollector, CollectedLogs, ErrorWithContext, LogCollector, LogFuncs, MockAnalyticsApi, MockConfigApi, MockErrorApi, MockErrorApiOptions, MockStorageApi, MockStorageBucket, SyncLogCollector, TestApiProvider, TestApiProviderProps, TestApiRegistry, TestAppOptions, mockBreakpoint, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
package/dist/index.esm.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConfigReader } from '@backstage/config';
|
|
1
2
|
import ObservableImpl from 'zen-observable';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import { MemoryRouter } from 'react-router';
|
|
@@ -8,26 +9,20 @@ import { CssBaseline } from '@material-ui/core';
|
|
|
8
9
|
import MockIcon from '@material-ui/icons/AcUnit';
|
|
9
10
|
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
11
|
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 '
|
|
12
|
-
import { render, fireEvent, act as act$1 } from '@testing-library/react';
|
|
12
|
+
import { act, render } from '@testing-library/react';
|
|
13
13
|
|
|
14
14
|
class MockAnalyticsApi {
|
|
15
15
|
constructor() {
|
|
16
16
|
this.events = [];
|
|
17
17
|
}
|
|
18
|
-
captureEvent({
|
|
19
|
-
action,
|
|
20
|
-
subject,
|
|
21
|
-
value,
|
|
22
|
-
attributes,
|
|
23
|
-
context
|
|
24
|
-
}) {
|
|
18
|
+
captureEvent(event) {
|
|
19
|
+
const { action, subject, value, attributes, context } = event;
|
|
25
20
|
this.events.push({
|
|
26
21
|
action,
|
|
27
22
|
subject,
|
|
28
23
|
context,
|
|
29
|
-
...value !== void 0 ? {value} : {},
|
|
30
|
-
...attributes !== void 0 ? {attributes} : {}
|
|
24
|
+
...value !== void 0 ? { value } : {},
|
|
25
|
+
...attributes !== void 0 ? { attributes } : {}
|
|
31
26
|
});
|
|
32
27
|
}
|
|
33
28
|
getEvents() {
|
|
@@ -35,9 +30,63 @@ class MockAnalyticsApi {
|
|
|
35
30
|
}
|
|
36
31
|
}
|
|
37
32
|
|
|
33
|
+
class MockConfigApi {
|
|
34
|
+
constructor(data) {
|
|
35
|
+
this.config = new ConfigReader(data);
|
|
36
|
+
}
|
|
37
|
+
has(key) {
|
|
38
|
+
return this.config.has(key);
|
|
39
|
+
}
|
|
40
|
+
keys() {
|
|
41
|
+
return this.config.keys();
|
|
42
|
+
}
|
|
43
|
+
get(key) {
|
|
44
|
+
return this.config.get(key);
|
|
45
|
+
}
|
|
46
|
+
getOptional(key) {
|
|
47
|
+
return this.config.getOptional(key);
|
|
48
|
+
}
|
|
49
|
+
getConfig(key) {
|
|
50
|
+
return this.config.getConfig(key);
|
|
51
|
+
}
|
|
52
|
+
getOptionalConfig(key) {
|
|
53
|
+
return this.config.getOptionalConfig(key);
|
|
54
|
+
}
|
|
55
|
+
getConfigArray(key) {
|
|
56
|
+
return this.config.getConfigArray(key);
|
|
57
|
+
}
|
|
58
|
+
getOptionalConfigArray(key) {
|
|
59
|
+
return this.config.getOptionalConfigArray(key);
|
|
60
|
+
}
|
|
61
|
+
getNumber(key) {
|
|
62
|
+
return this.config.getNumber(key);
|
|
63
|
+
}
|
|
64
|
+
getOptionalNumber(key) {
|
|
65
|
+
return this.config.getOptionalNumber(key);
|
|
66
|
+
}
|
|
67
|
+
getBoolean(key) {
|
|
68
|
+
return this.config.getBoolean(key);
|
|
69
|
+
}
|
|
70
|
+
getOptionalBoolean(key) {
|
|
71
|
+
return this.config.getOptionalBoolean(key);
|
|
72
|
+
}
|
|
73
|
+
getString(key) {
|
|
74
|
+
return this.config.getString(key);
|
|
75
|
+
}
|
|
76
|
+
getOptionalString(key) {
|
|
77
|
+
return this.config.getOptionalString(key);
|
|
78
|
+
}
|
|
79
|
+
getStringArray(key) {
|
|
80
|
+
return this.config.getStringArray(key);
|
|
81
|
+
}
|
|
82
|
+
getOptionalStringArray(key) {
|
|
83
|
+
return this.config.getOptionalStringArray(key);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
38
87
|
const nullObservable = {
|
|
39
|
-
subscribe: () => ({unsubscribe: () => {
|
|
40
|
-
}, closed: true}),
|
|
88
|
+
subscribe: () => ({ unsubscribe: () => {
|
|
89
|
+
}, closed: true }),
|
|
41
90
|
[Symbol.observable]() {
|
|
42
91
|
return this;
|
|
43
92
|
}
|
|
@@ -46,15 +95,15 @@ class MockErrorApi {
|
|
|
46
95
|
constructor(options = {}) {
|
|
47
96
|
this.options = options;
|
|
48
97
|
this.errors = new Array();
|
|
49
|
-
this.waiters = new Set();
|
|
98
|
+
this.waiters = /* @__PURE__ */ new Set();
|
|
50
99
|
}
|
|
51
100
|
post(error, context) {
|
|
52
101
|
if (this.options.collect) {
|
|
53
|
-
this.errors.push({error, context});
|
|
102
|
+
this.errors.push({ error, context });
|
|
54
103
|
for (const waiter of this.waiters) {
|
|
55
104
|
if (waiter.pattern.test(error.message)) {
|
|
56
105
|
this.waiters.delete(waiter);
|
|
57
|
-
waiter.resolve({error, context});
|
|
106
|
+
waiter.resolve({ error, context });
|
|
58
107
|
}
|
|
59
108
|
}
|
|
60
109
|
return;
|
|
@@ -72,14 +121,14 @@ class MockErrorApi {
|
|
|
72
121
|
setTimeout(() => {
|
|
73
122
|
reject(new Error("Timed out waiting for error"));
|
|
74
123
|
}, timeoutMs);
|
|
75
|
-
this.waiters.add({resolve, pattern});
|
|
124
|
+
this.waiters.add({ resolve, pattern });
|
|
76
125
|
});
|
|
77
126
|
}
|
|
78
127
|
}
|
|
79
128
|
|
|
80
129
|
class MockStorageApi {
|
|
81
130
|
constructor(namespace, bucketStorageApis, data) {
|
|
82
|
-
this.subscribers = new Set();
|
|
131
|
+
this.subscribers = /* @__PURE__ */ new Set();
|
|
83
132
|
this.observable = new ObservableImpl((subscriber) => {
|
|
84
133
|
this.subscribers.add(subscriber);
|
|
85
134
|
return () => {
|
|
@@ -88,10 +137,10 @@ class MockStorageApi {
|
|
|
88
137
|
});
|
|
89
138
|
this.namespace = namespace;
|
|
90
139
|
this.bucketStorageApis = bucketStorageApis;
|
|
91
|
-
this.data = {...data};
|
|
140
|
+
this.data = { ...data };
|
|
92
141
|
}
|
|
93
142
|
static create(data) {
|
|
94
|
-
return new MockStorageApi("", new Map(), data);
|
|
143
|
+
return new MockStorageApi("", /* @__PURE__ */ new Map(), data);
|
|
95
144
|
}
|
|
96
145
|
forBucket(name) {
|
|
97
146
|
if (!this.bucketStorageApis.has(name)) {
|
|
@@ -100,18 +149,51 @@ class MockStorageApi {
|
|
|
100
149
|
return this.bucketStorageApis.get(name);
|
|
101
150
|
}
|
|
102
151
|
get(key) {
|
|
103
|
-
return this.
|
|
152
|
+
return this.snapshot(key).value;
|
|
153
|
+
}
|
|
154
|
+
snapshot(key) {
|
|
155
|
+
if (this.data.hasOwnProperty(this.getKeyName(key))) {
|
|
156
|
+
const data = this.data[this.getKeyName(key)];
|
|
157
|
+
return {
|
|
158
|
+
key,
|
|
159
|
+
presence: "present",
|
|
160
|
+
value: data,
|
|
161
|
+
newValue: data
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
key,
|
|
166
|
+
presence: "absent",
|
|
167
|
+
value: void 0,
|
|
168
|
+
newValue: void 0
|
|
169
|
+
};
|
|
104
170
|
}
|
|
105
171
|
async set(key, data) {
|
|
106
|
-
|
|
107
|
-
|
|
172
|
+
const serialized = JSON.parse(JSON.stringify(data), (_key, value) => {
|
|
173
|
+
if (typeof value === "object" && value !== null) {
|
|
174
|
+
Object.freeze(value);
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
});
|
|
178
|
+
this.data[this.getKeyName(key)] = serialized;
|
|
179
|
+
this.notifyChanges({
|
|
180
|
+
key,
|
|
181
|
+
presence: "present",
|
|
182
|
+
value: serialized,
|
|
183
|
+
newValue: serialized
|
|
184
|
+
});
|
|
108
185
|
}
|
|
109
186
|
async remove(key) {
|
|
110
187
|
delete this.data[this.getKeyName(key)];
|
|
111
|
-
this.notifyChanges({
|
|
188
|
+
this.notifyChanges({
|
|
189
|
+
key,
|
|
190
|
+
presence: "absent",
|
|
191
|
+
value: void 0,
|
|
192
|
+
newValue: void 0
|
|
193
|
+
});
|
|
112
194
|
}
|
|
113
195
|
observe$(key) {
|
|
114
|
-
return this.observable.filter(({key: messageKey}) => messageKey === key);
|
|
196
|
+
return this.observable.filter(({ key: messageKey }) => messageKey === key);
|
|
115
197
|
}
|
|
116
198
|
getKeyName(key) {
|
|
117
199
|
return `${this.namespace}/${encodeURIComponent(key)}`;
|
|
@@ -123,19 +205,22 @@ class MockStorageApi {
|
|
|
123
205
|
}
|
|
124
206
|
}
|
|
125
207
|
|
|
126
|
-
function mockBreakpoint(
|
|
208
|
+
function mockBreakpoint(options) {
|
|
127
209
|
Object.defineProperty(window, "matchMedia", {
|
|
128
210
|
writable: true,
|
|
129
|
-
value: jest.fn().mockImplementation((query) =>
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
211
|
+
value: jest.fn().mockImplementation((query) => {
|
|
212
|
+
var _a;
|
|
213
|
+
return {
|
|
214
|
+
matches: (_a = options.matches) != null ? _a : false,
|
|
215
|
+
media: query,
|
|
216
|
+
onchange: null,
|
|
217
|
+
addListener: jest.fn(),
|
|
218
|
+
removeListener: jest.fn(),
|
|
219
|
+
addEventListener: jest.fn(),
|
|
220
|
+
removeEventListener: jest.fn(),
|
|
221
|
+
dispatchEvent: jest.fn()
|
|
222
|
+
};
|
|
223
|
+
})
|
|
139
224
|
});
|
|
140
225
|
}
|
|
141
226
|
|
|
@@ -150,24 +235,24 @@ async function renderWithEffects(nodes) {
|
|
|
150
235
|
const defaultApis = [
|
|
151
236
|
createApiFactory({
|
|
152
237
|
api: discoveryApiRef,
|
|
153
|
-
deps: {configApi: configApiRef},
|
|
154
|
-
factory: ({configApi}) => UrlPatternDiscovery.compile(`${configApi.getString("backend.baseUrl")}/api/{{ pluginId }}`)
|
|
238
|
+
deps: { configApi: configApiRef },
|
|
239
|
+
factory: ({ configApi }) => UrlPatternDiscovery.compile(`${configApi.getString("backend.baseUrl")}/api/{{ pluginId }}`)
|
|
155
240
|
}),
|
|
156
241
|
createApiFactory(alertApiRef, new AlertApiForwarder()),
|
|
157
242
|
createApiFactory(analyticsApiRef, new NoOpAnalyticsApi()),
|
|
158
243
|
createApiFactory({
|
|
159
244
|
api: errorApiRef,
|
|
160
|
-
deps: {alertApi: alertApiRef},
|
|
161
|
-
factory: ({alertApi}) => {
|
|
245
|
+
deps: { alertApi: alertApiRef },
|
|
246
|
+
factory: ({ alertApi }) => {
|
|
162
247
|
const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
|
|
163
|
-
UnhandledErrorForwarder.forward(errorApi, {hidden: false});
|
|
248
|
+
UnhandledErrorForwarder.forward(errorApi, { hidden: false });
|
|
164
249
|
return errorApi;
|
|
165
250
|
}
|
|
166
251
|
}),
|
|
167
252
|
createApiFactory({
|
|
168
253
|
api: storageApiRef,
|
|
169
|
-
deps: {errorApi: errorApiRef},
|
|
170
|
-
factory: ({errorApi}) => WebStorage.create({errorApi})
|
|
254
|
+
deps: { errorApi: errorApiRef },
|
|
255
|
+
factory: ({ errorApi }) => WebStorage.create({ errorApi })
|
|
171
256
|
}),
|
|
172
257
|
createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),
|
|
173
258
|
createApiFactory({
|
|
@@ -177,7 +262,7 @@ const defaultApis = [
|
|
|
177
262
|
oauthRequestApi: oauthRequestApiRef,
|
|
178
263
|
configApi: configApiRef
|
|
179
264
|
},
|
|
180
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => GoogleAuth.create({
|
|
265
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GoogleAuth.create({
|
|
181
266
|
discoveryApi,
|
|
182
267
|
oauthRequestApi,
|
|
183
268
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -190,7 +275,7 @@ const defaultApis = [
|
|
|
190
275
|
oauthRequestApi: oauthRequestApiRef,
|
|
191
276
|
configApi: configApiRef
|
|
192
277
|
},
|
|
193
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => MicrosoftAuth.create({
|
|
278
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => MicrosoftAuth.create({
|
|
194
279
|
discoveryApi,
|
|
195
280
|
oauthRequestApi,
|
|
196
281
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -203,7 +288,7 @@ const defaultApis = [
|
|
|
203
288
|
oauthRequestApi: oauthRequestApiRef,
|
|
204
289
|
configApi: configApiRef
|
|
205
290
|
},
|
|
206
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => GithubAuth.create({
|
|
291
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GithubAuth.create({
|
|
207
292
|
discoveryApi,
|
|
208
293
|
oauthRequestApi,
|
|
209
294
|
defaultScopes: ["read:user"],
|
|
@@ -217,7 +302,7 @@ const defaultApis = [
|
|
|
217
302
|
oauthRequestApi: oauthRequestApiRef,
|
|
218
303
|
configApi: configApiRef
|
|
219
304
|
},
|
|
220
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => OktaAuth.create({
|
|
305
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OktaAuth.create({
|
|
221
306
|
discoveryApi,
|
|
222
307
|
oauthRequestApi,
|
|
223
308
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -230,7 +315,7 @@ const defaultApis = [
|
|
|
230
315
|
oauthRequestApi: oauthRequestApiRef,
|
|
231
316
|
configApi: configApiRef
|
|
232
317
|
},
|
|
233
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => GitlabAuth.create({
|
|
318
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => GitlabAuth.create({
|
|
234
319
|
discoveryApi,
|
|
235
320
|
oauthRequestApi,
|
|
236
321
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -243,7 +328,7 @@ const defaultApis = [
|
|
|
243
328
|
oauthRequestApi: oauthRequestApiRef,
|
|
244
329
|
configApi: configApiRef
|
|
245
330
|
},
|
|
246
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => Auth0Auth.create({
|
|
331
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => Auth0Auth.create({
|
|
247
332
|
discoveryApi,
|
|
248
333
|
oauthRequestApi,
|
|
249
334
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -256,7 +341,7 @@ const defaultApis = [
|
|
|
256
341
|
oauthRequestApi: oauthRequestApiRef,
|
|
257
342
|
configApi: configApiRef
|
|
258
343
|
},
|
|
259
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => OAuth2.create({
|
|
344
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
|
|
260
345
|
discoveryApi,
|
|
261
346
|
oauthRequestApi,
|
|
262
347
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -268,7 +353,7 @@ const defaultApis = [
|
|
|
268
353
|
discoveryApi: discoveryApiRef,
|
|
269
354
|
configApi: configApiRef
|
|
270
355
|
},
|
|
271
|
-
factory: ({discoveryApi, configApi}) => SamlAuth.create({
|
|
356
|
+
factory: ({ discoveryApi, configApi }) => SamlAuth.create({
|
|
272
357
|
discoveryApi,
|
|
273
358
|
environment: configApi.getOptionalString("auth.environment")
|
|
274
359
|
})
|
|
@@ -280,7 +365,7 @@ const defaultApis = [
|
|
|
280
365
|
oauthRequestApi: oauthRequestApiRef,
|
|
281
366
|
configApi: configApiRef
|
|
282
367
|
},
|
|
283
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => OneLoginAuth.create({
|
|
368
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OneLoginAuth.create({
|
|
284
369
|
discoveryApi,
|
|
285
370
|
oauthRequestApi,
|
|
286
371
|
environment: configApi.getOptionalString("auth.environment")
|
|
@@ -293,7 +378,7 @@ const defaultApis = [
|
|
|
293
378
|
oauthRequestApi: oauthRequestApiRef,
|
|
294
379
|
configApi: configApiRef
|
|
295
380
|
},
|
|
296
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => OAuth2.create({
|
|
381
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
|
|
297
382
|
discoveryApi,
|
|
298
383
|
oauthRequestApi,
|
|
299
384
|
provider: {
|
|
@@ -311,7 +396,7 @@ const defaultApis = [
|
|
|
311
396
|
oauthRequestApi: oauthRequestApiRef,
|
|
312
397
|
configApi: configApiRef
|
|
313
398
|
},
|
|
314
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => BitbucketAuth.create({
|
|
399
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => BitbucketAuth.create({
|
|
315
400
|
discoveryApi,
|
|
316
401
|
oauthRequestApi,
|
|
317
402
|
defaultScopes: ["team"],
|
|
@@ -325,7 +410,7 @@ const defaultApis = [
|
|
|
325
410
|
oauthRequestApi: oauthRequestApiRef,
|
|
326
411
|
configApi: configApiRef
|
|
327
412
|
},
|
|
328
|
-
factory: ({discoveryApi, oauthRequestApi, configApi}) => {
|
|
413
|
+
factory: ({ discoveryApi, oauthRequestApi, configApi }) => {
|
|
329
414
|
return AtlassianAuth.create({
|
|
330
415
|
discoveryApi,
|
|
331
416
|
oauthRequestApi,
|
|
@@ -363,13 +448,13 @@ const mockIcons = {
|
|
|
363
448
|
user: MockIcon,
|
|
364
449
|
warning: MockIcon
|
|
365
450
|
};
|
|
366
|
-
const ErrorBoundaryFallback = ({error}) => {
|
|
451
|
+
const ErrorBoundaryFallback = ({ error }) => {
|
|
367
452
|
throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`);
|
|
368
453
|
};
|
|
369
454
|
const NotFoundErrorPage = () => {
|
|
370
455
|
throw new Error("Reached NotFound Page");
|
|
371
456
|
};
|
|
372
|
-
const BootErrorPage = ({step, error}) => {
|
|
457
|
+
const BootErrorPage = ({ step, error }) => {
|
|
373
458
|
throw new Error(`Reached BootError Page at step ${step} with error ${error}`);
|
|
374
459
|
};
|
|
375
460
|
const Progress = () => /* @__PURE__ */ React.createElement("div", {
|
|
@@ -380,8 +465,8 @@ function isExternalRouteRef(routeRef) {
|
|
|
380
465
|
}
|
|
381
466
|
function wrapInTestApp(Component, options = {}) {
|
|
382
467
|
var _a;
|
|
383
|
-
const {routeEntries = ["/"]} = options;
|
|
384
|
-
const boundRoutes = new Map();
|
|
468
|
+
const { routeEntries = ["/"] } = options;
|
|
469
|
+
const boundRoutes = /* @__PURE__ */ new Map();
|
|
385
470
|
const app = createSpecializedApp({
|
|
386
471
|
apis: mockApis,
|
|
387
472
|
defaultApis,
|
|
@@ -391,7 +476,7 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
391
476
|
BootErrorPage,
|
|
392
477
|
NotFoundErrorPage,
|
|
393
478
|
ErrorBoundaryFallback,
|
|
394
|
-
Router: ({children}) => /* @__PURE__ */ React.createElement(MemoryRouter, {
|
|
479
|
+
Router: ({ children }) => /* @__PURE__ */ React.createElement(MemoryRouter, {
|
|
395
480
|
initialEntries: routeEntries,
|
|
396
481
|
children
|
|
397
482
|
})
|
|
@@ -403,14 +488,14 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
403
488
|
id: "light",
|
|
404
489
|
title: "Test App Theme",
|
|
405
490
|
variant: "light",
|
|
406
|
-
Provider: ({children}) => /* @__PURE__ */ React.createElement(ThemeProvider, {
|
|
491
|
+
Provider: ({ children }) => /* @__PURE__ */ React.createElement(ThemeProvider, {
|
|
407
492
|
theme: lightTheme
|
|
408
493
|
}, /* @__PURE__ */ React.createElement(CssBaseline, null, children))
|
|
409
494
|
}
|
|
410
495
|
],
|
|
411
|
-
bindRoutes: ({bind}) => {
|
|
496
|
+
bindRoutes: ({ bind }) => {
|
|
412
497
|
for (const [externalRef, absoluteRef] of boundRoutes) {
|
|
413
|
-
bind({ref: externalRef}, {
|
|
498
|
+
bind({ ref: externalRef }, {
|
|
414
499
|
ref: absoluteRef
|
|
415
500
|
});
|
|
416
501
|
}
|
|
@@ -425,7 +510,7 @@ function wrapInTestApp(Component, options = {}) {
|
|
|
425
510
|
const routeElements = Object.entries((_a = options.mountedRoutes) != null ? _a : {}).map(([path, routeRef]) => {
|
|
426
511
|
const Page = () => /* @__PURE__ */ React.createElement("div", null, "Mounted at ", path);
|
|
427
512
|
if (isExternalRouteRef(routeRef)) {
|
|
428
|
-
const absoluteRef = createRouteRef({id: "id"});
|
|
513
|
+
const absoluteRef = createRouteRef({ id: "id" });
|
|
429
514
|
boundRoutes.set(routeRef, absoluteRef);
|
|
430
515
|
attachComponentData(Page, "core.mountPoint", absoluteRef);
|
|
431
516
|
} else {
|
|
@@ -448,176 +533,12 @@ async function renderInTestApp(Component, options = {}) {
|
|
|
448
533
|
return renderWithEffects(wrapInTestApp(Component, options));
|
|
449
534
|
}
|
|
450
535
|
|
|
451
|
-
const msw = {
|
|
452
|
-
setupDefaultHandlers: (worker) => {
|
|
453
|
-
setupRequestMockHandlers(worker);
|
|
454
|
-
}
|
|
455
|
-
};
|
|
456
536
|
function setupRequestMockHandlers(worker) {
|
|
457
|
-
beforeAll(() => worker.listen({onUnhandledRequest: "error"}));
|
|
537
|
+
beforeAll(() => worker.listen({ onUnhandledRequest: "error" }));
|
|
458
538
|
afterAll(() => worker.close());
|
|
459
539
|
afterEach(() => worker.resetHandlers());
|
|
460
540
|
}
|
|
461
541
|
|
|
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
542
|
const allCategories = ["log", "warn", "error"];
|
|
622
543
|
function withLogCollector(logsToCollect, callback) {
|
|
623
544
|
const oneArg = !callback;
|
|
@@ -681,15 +602,12 @@ class TestApiRegistry {
|
|
|
681
602
|
return this.apis.get(api.id);
|
|
682
603
|
}
|
|
683
604
|
}
|
|
684
|
-
const TestApiProvider = ({
|
|
685
|
-
apis,
|
|
686
|
-
children
|
|
687
|
-
}) => {
|
|
605
|
+
const TestApiProvider = (props) => {
|
|
688
606
|
return /* @__PURE__ */ React.createElement(ApiProvider, {
|
|
689
|
-
apis: TestApiRegistry.from(...apis),
|
|
690
|
-
children
|
|
607
|
+
apis: TestApiRegistry.from(...props.apis),
|
|
608
|
+
children: props.children
|
|
691
609
|
});
|
|
692
610
|
};
|
|
693
611
|
|
|
694
|
-
export {
|
|
612
|
+
export { MockAnalyticsApi, MockConfigApi, MockErrorApi, MockStorageApi, TestApiProvider, TestApiRegistry, mockBreakpoint, renderInTestApp, renderWithEffects, setupRequestMockHandlers, withLogCollector, wrapInTestApp };
|
|
695
613
|
//# 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/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 * @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","/*\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 apis,\n children,\n}: TestApiProviderProps<T>) => {\n return (\n <ApiProvider apis={TestApiRegistry.from(...apis)} children={children} />\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/ConfigApi/MockConfigApi.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/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 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject, JsonValue } from '@backstage/types';\nimport { ConfigApi } from '@backstage/core-plugin-api';\n\n/**\n * MockConfigApi is a thin wrapper around {@link @backstage/config#ConfigReader}\n * that can be used to mock configuration using a plain object.\n *\n * @public\n * @example\n * ```tsx\n * const mockConfig = new MockConfigApi({\n * app: { baseUrl: 'https://example.com' },\n * });\n *\n * const rendered = await renderInTestApp(\n * <TestApiProvider apis={[[configApiRef, mockConfig]]}>\n * <MyTestedComponent />\n * </TestApiProvider>,\n * );\n * ```\n */\nexport class MockConfigApi implements ConfigApi {\n private readonly config: ConfigReader;\n\n // NOTE: not extending in order to avoid inheriting the static `.fromConfigs`\n constructor(data: JsonObject) {\n this.config = new ConfigReader(data);\n }\n\n /** {@inheritdoc @backstage/config#Config.has} */\n has(key: string): boolean {\n return this.config.has(key);\n }\n /** {@inheritdoc @backstage/config#Config.keys} */\n keys(): string[] {\n return this.config.keys();\n }\n /** {@inheritdoc @backstage/config#Config.get} */\n get<T = JsonValue>(key?: string): T {\n return this.config.get(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptional} */\n getOptional<T = JsonValue>(key?: string): T | undefined {\n return this.config.getOptional(key);\n }\n /** {@inheritdoc @backstage/config#Config.getConfig} */\n getConfig(key: string): Config {\n return this.config.getConfig(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalConfig} */\n getOptionalConfig(key: string): Config | undefined {\n return this.config.getOptionalConfig(key);\n }\n /** {@inheritdoc @backstage/config#Config.getConfigArray} */\n getConfigArray(key: string): Config[] {\n return this.config.getConfigArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalConfigArray} */\n getOptionalConfigArray(key: string): Config[] | undefined {\n return this.config.getOptionalConfigArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getNumber} */\n getNumber(key: string): number {\n return this.config.getNumber(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalNumber} */\n getOptionalNumber(key: string): number | undefined {\n return this.config.getOptionalNumber(key);\n }\n /** {@inheritdoc @backstage/config#Config.getBoolean} */\n getBoolean(key: string): boolean {\n return this.config.getBoolean(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalBoolean} */\n getOptionalBoolean(key: string): boolean | undefined {\n return this.config.getOptionalBoolean(key);\n }\n /** {@inheritdoc @backstage/config#Config.getString} */\n getString(key: string): string {\n return this.config.getString(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalString} */\n getOptionalString(key: string): string | undefined {\n return this.config.getOptionalString(key);\n }\n /** {@inheritdoc @backstage/config#Config.getStringArray} */\n getStringArray(key: string): string[] {\n return this.config.getStringArray(key);\n }\n /** {@inheritdoc @backstage/config#Config.getOptionalStringArray} */\n getOptionalStringArray(key: string): string[] | undefined {\n return this.config.getOptionalStringArray(key);\n }\n}\n","/*\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, StorageValueSnapshot } from '@backstage/core-plugin-api';\nimport { JsonValue, 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.snapshot(key).value as T | undefined;\n }\n\n snapshot<T extends JsonValue>(key: string): StorageValueSnapshot<T> {\n if (this.data.hasOwnProperty(this.getKeyName(key))) {\n const data = this.data[this.getKeyName(key)];\n return {\n key,\n presence: 'present',\n value: data,\n newValue: data,\n };\n }\n return {\n key,\n presence: 'absent',\n value: undefined,\n newValue: undefined,\n };\n }\n\n async set<T>(key: string, data: T): Promise<void> {\n const serialized = JSON.parse(JSON.stringify(data), (_key, value) => {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n }\n return value;\n });\n this.data[this.getKeyName(key)] = serialized;\n this.notifyChanges({\n key,\n presence: 'present',\n value: serialized,\n newValue: serialized,\n });\n }\n\n async remove(key: string): Promise<void> {\n delete this.data[this.getKeyName(key)];\n this.notifyChanges({\n key,\n presence: 'absent',\n value: undefined,\n newValue: undefined,\n });\n }\n\n observe$<T>(key: string): Observable<StorageValueSnapshot<T>> {\n return this.observable.filter(({ key: messageKey }) => messageKey === key);\n }\n\n private getKeyName(key: string) {\n return `${this.namespace}/${encodeURIComponent(key)}`;\n }\n\n private notifyChanges<T>(message: StorageValueSnapshot<T>) {\n for (const subscription of this.subscribers) {\n subscription.next(message);\n }\n }\n\n private subscribers = new Set<\n ZenObservable.SubscriptionObserver<StorageValueSnapshot<JsonValue>>\n >();\n\n private readonly observable = new ObservableImpl<\n StorageValueSnapshot<JsonValue>\n >(subscriber => {\n this.subscribers.add(subscriber);\n return () => {\n this.subscribers.delete(subscriber);\n };\n });\n}\n","/*\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 * 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\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 * */\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 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, { 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.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public",
|
|
@@ -29,29 +29,31 @@
|
|
|
29
29
|
"clean": "backstage-cli clean"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@backstage/
|
|
33
|
-
"@backstage/core-
|
|
34
|
-
"@backstage/
|
|
32
|
+
"@backstage/config": "^0.1.12",
|
|
33
|
+
"@backstage/core-app-api": "^0.4.0",
|
|
34
|
+
"@backstage/core-plugin-api": "^0.5.0",
|
|
35
|
+
"@backstage/theme": "^0.2.14",
|
|
35
36
|
"@backstage/types": "^0.1.1",
|
|
36
37
|
"@material-ui/core": "^4.12.2",
|
|
37
38
|
"@material-ui/icons": "^4.11.2",
|
|
38
39
|
"@testing-library/jest-dom": "^5.10.1",
|
|
39
40
|
"@testing-library/react": "^11.2.5",
|
|
40
41
|
"@testing-library/user-event": "^13.1.8",
|
|
41
|
-
"@types/react": "*",
|
|
42
|
-
"react": "^16.12.0",
|
|
43
|
-
"react-dom": "^16.12.0",
|
|
44
42
|
"react-router": "6.0.0-beta.0",
|
|
45
43
|
"react-router-dom": "6.0.0-beta.0",
|
|
46
44
|
"zen-observable": "^0.8.15"
|
|
47
45
|
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"@types/react": "^16.13.1 || ^17.0.0",
|
|
48
|
+
"react": "^16.13.1 || ^17.0.0"
|
|
49
|
+
},
|
|
48
50
|
"devDependencies": {
|
|
49
|
-
"@backstage/cli": "^0.
|
|
51
|
+
"@backstage/cli": "^0.11.0",
|
|
50
52
|
"@types/jest": "^26.0.7",
|
|
51
53
|
"@types/node": "^14.14.32"
|
|
52
54
|
},
|
|
53
55
|
"files": [
|
|
54
56
|
"dist"
|
|
55
57
|
],
|
|
56
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "da66c61bdd63cdb3f0f0cd2e26dc9e6454d93c7b"
|
|
57
59
|
}
|