implement the beginnings of mockApis

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-10-08 17:03:22 +02:00
parent f91809d6f6
commit 9cc7dd6cfd
45 changed files with 632 additions and 299 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': patch
---
Minor doc string changes
+8
View File
@@ -0,0 +1,8 @@
---
'@backstage/test-utils': minor
'@backstage/frontend-test-utils': patch
---
Added a `mockApis` export, which will replace the `MockX` API implementation classes and their related types. This is analogous with the backend's `mockServices`.
Deprecated `MockConfigApi`, please use `mockApis.config` instead.
+59 -60
View File
@@ -150,7 +150,7 @@ export function mockErrorHandler(): ErrorRequestHandler<
Record<string, any>
>;
// @public (undocumented)
// @public
export namespace mockServices {
// (undocumented)
export function auth(options?: {
@@ -491,65 +491,64 @@ export class TestDatabases {
// src/next/services/mockCredentials.d.ts:116:9 - (ae-undocumented) Missing documentation for "invalidToken".
// src/next/services/mockCredentials.d.ts:117:9 - (ae-undocumented) Missing documentation for "invalidHeader".
// src/next/services/mockServices.d.ts:5:1 - (ae-undocumented) Missing documentation for "ServiceMock".
// src/next/services/mockServices.d.ts:13:1 - (ae-undocumented) Missing documentation for "mockServices".
// src/next/services/mockServices.d.ts:14:5 - (ae-undocumented) Missing documentation for "rootConfig".
// src/next/services/mockServices.d.ts:15:5 - (ae-undocumented) Missing documentation for "rootConfig".
// src/next/services/mockServices.d.ts:16:9 - (ae-undocumented) Missing documentation for "Options".
// src/next/services/mockServices.d.ts:19:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:20:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:22:5 - (ae-undocumented) Missing documentation for "rootLogger".
// src/next/services/mockServices.d.ts:23:5 - (ae-undocumented) Missing documentation for "rootLogger".
// src/next/services/mockServices.d.ts:24:9 - (ae-undocumented) Missing documentation for "Options".
// src/next/services/mockServices.d.ts:27:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:28:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:30:5 - (ae-undocumented) Missing documentation for "auth".
// src/next/services/mockServices.d.ts:34:5 - (ae-undocumented) Missing documentation for "auth".
// src/next/services/mockServices.d.ts:35:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:36:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:38:5 - (ae-undocumented) Missing documentation for "discovery".
// src/next/services/mockServices.d.ts:39:5 - (ae-undocumented) Missing documentation for "discovery".
// src/next/services/mockServices.d.ts:40:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:41:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:61:5 - (ae-undocumented) Missing documentation for "httpAuth".
// src/next/services/mockServices.d.ts:72:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:82:5 - (ae-undocumented) Missing documentation for "userInfo".
// src/next/services/mockServices.d.ts:90:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:92:5 - (ae-undocumented) Missing documentation for "cache".
// src/next/services/mockServices.d.ts:93:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:94:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:96:5 - (ae-undocumented) Missing documentation for "database".
// src/next/services/mockServices.d.ts:97:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:98:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:100:5 - (ae-undocumented) Missing documentation for "rootHealth".
// src/next/services/mockServices.d.ts:101:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:102:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:104:5 - (ae-undocumented) Missing documentation for "httpRouter".
// src/next/services/mockServices.d.ts:105:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:106:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:108:5 - (ae-undocumented) Missing documentation for "rootHttpRouter".
// src/next/services/mockServices.d.ts:109:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:110:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:112:5 - (ae-undocumented) Missing documentation for "lifecycle".
// src/next/services/mockServices.d.ts:113:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:114:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:116:5 - (ae-undocumented) Missing documentation for "logger".
// src/next/services/mockServices.d.ts:117:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:118:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:120:5 - (ae-undocumented) Missing documentation for "permissions".
// src/next/services/mockServices.d.ts:121:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:122:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:124:5 - (ae-undocumented) Missing documentation for "rootLifecycle".
// src/next/services/mockServices.d.ts:125:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:126:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:128:5 - (ae-undocumented) Missing documentation for "scheduler".
// src/next/services/mockServices.d.ts:129:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:130:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:132:5 - (ae-undocumented) Missing documentation for "urlReader".
// src/next/services/mockServices.d.ts:133:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:134:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:136:5 - (ae-undocumented) Missing documentation for "events".
// src/next/services/mockServices.d.ts:137:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:138:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:53:5 - (ae-undocumented) Missing documentation for "rootConfig".
// src/next/services/mockServices.d.ts:54:5 - (ae-undocumented) Missing documentation for "rootConfig".
// src/next/services/mockServices.d.ts:55:9 - (ae-undocumented) Missing documentation for "Options".
// src/next/services/mockServices.d.ts:58:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:59:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:61:5 - (ae-undocumented) Missing documentation for "rootLogger".
// src/next/services/mockServices.d.ts:62:5 - (ae-undocumented) Missing documentation for "rootLogger".
// src/next/services/mockServices.d.ts:63:9 - (ae-undocumented) Missing documentation for "Options".
// src/next/services/mockServices.d.ts:66:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:67:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:69:5 - (ae-undocumented) Missing documentation for "auth".
// src/next/services/mockServices.d.ts:73:5 - (ae-undocumented) Missing documentation for "auth".
// src/next/services/mockServices.d.ts:74:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:75:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:77:5 - (ae-undocumented) Missing documentation for "discovery".
// src/next/services/mockServices.d.ts:78:5 - (ae-undocumented) Missing documentation for "discovery".
// src/next/services/mockServices.d.ts:79:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:80:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:100:5 - (ae-undocumented) Missing documentation for "httpAuth".
// src/next/services/mockServices.d.ts:111:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:121:5 - (ae-undocumented) Missing documentation for "userInfo".
// src/next/services/mockServices.d.ts:129:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:131:5 - (ae-undocumented) Missing documentation for "cache".
// src/next/services/mockServices.d.ts:132:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:133:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:135:5 - (ae-undocumented) Missing documentation for "database".
// src/next/services/mockServices.d.ts:136:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:137:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:139:5 - (ae-undocumented) Missing documentation for "rootHealth".
// src/next/services/mockServices.d.ts:140:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:141:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:143:5 - (ae-undocumented) Missing documentation for "httpRouter".
// src/next/services/mockServices.d.ts:144:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:145:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:147:5 - (ae-undocumented) Missing documentation for "rootHttpRouter".
// src/next/services/mockServices.d.ts:148:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:149:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:151:5 - (ae-undocumented) Missing documentation for "lifecycle".
// src/next/services/mockServices.d.ts:152:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:153:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:155:5 - (ae-undocumented) Missing documentation for "logger".
// src/next/services/mockServices.d.ts:156:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:157:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:159:5 - (ae-undocumented) Missing documentation for "permissions".
// src/next/services/mockServices.d.ts:160:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:161:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:163:5 - (ae-undocumented) Missing documentation for "rootLifecycle".
// src/next/services/mockServices.d.ts:164:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:165:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:167:5 - (ae-undocumented) Missing documentation for "scheduler".
// src/next/services/mockServices.d.ts:168:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:169:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:171:5 - (ae-undocumented) Missing documentation for "urlReader".
// src/next/services/mockServices.d.ts:172:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:173:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/services/mockServices.d.ts:175:5 - (ae-undocumented) Missing documentation for "events".
// src/next/services/mockServices.d.ts:176:15 - (ae-undocumented) Missing documentation for "factory".
// src/next/services/mockServices.d.ts:177:15 - (ae-undocumented) Missing documentation for "mock".
// src/next/wiring/TestBackend.d.ts:5:1 - (ae-undocumented) Missing documentation for "TestBackendOptions".
// src/next/wiring/TestBackend.d.ts:6:5 - (ae-undocumented) Missing documentation for "extensionPoints".
// src/next/wiring/TestBackend.d.ts:14:5 - (ae-undocumented) Missing documentation for "features".
@@ -128,7 +128,46 @@ function simpleMock<TService>(
}
/**
* Mock implementations of the core services, to be used in tests.
*
* @public
* @remarks
*
* There are some variations among the services depending on what needs tests
* might have, but overall there are three main usage patterns:
*
* 1. Creating an actual fake service instance, often with a simplified version
* of functionality, by calling the mock service itself as a function.
*
* ```ts
* // The function often accepts parameters that control its behavior
* const foo = mockServices.foo();
* ```
*
* 2. Creating a mock service, where all methods are replaced with jest mocks, by
* calling the service's `mock` function.
*
* ```ts
* // You can optionally supply a subset of its methods to implement
* const foo = mockServices.foo.mock({
* someMethod: () => 'mocked result',
* });
* // After exercising your test, you can make assertions on the mock:
* expect(foo.someMethod).toHaveBeenCalledTimes(2);
* expect(foo.otherMethod).toHaveBeenCalledWith(testData);
* ```
*
* 3. Creating a service factory that behaves similarly to the mock as per above.
*
* ```ts
* await startTestBackend({
* features: [
* mockServices.foo.factory({
* someMethod: () => 'mocked result',
* })
* ],
* });
* ```
*/
export namespace mockServices {
export function rootConfig(options?: rootConfig.Options): RootConfigService {
@@ -17,7 +17,7 @@
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import BitbucketAuth from './BitbucketAuth';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const getSession = jest.fn();
@@ -33,7 +33,7 @@ describe('BitbucketAuth', () => {
jest.resetAllMocks();
});
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
it.each([
['team api write_repository', ['team', 'api', 'write_repository']],
@@ -17,7 +17,7 @@
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import BitbucketServerAuth from './BitbucketServerAuth';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const getSession = jest.fn();
@@ -41,7 +41,7 @@ describe('BitbucketServerAuth', () => {
['PROJECT_ADMIN', 'REPO_READ', 'ACCOUNT_WRITE'],
],
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const bitbucketServerAuth = BitbucketServerAuth.create({
configApi: configApi,
@@ -17,7 +17,7 @@
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import GithubAuth from './GithubAuth';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const getSession = jest.fn();
@@ -33,7 +33,7 @@ describe('GithubAuth', () => {
jest.resetAllMocks();
});
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
it('should forward access token request to session manager', async () => {
const githubAuth = GithubAuth.create({
@@ -17,7 +17,7 @@
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import GitlabAuth from './GitlabAuth';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const getSession = jest.fn();
@@ -40,7 +40,7 @@ describe('GitlabAuth', () => {
],
['read_repository sudo', ['read_repository', 'sudo']],
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const gitlabAuth = GitlabAuth.create({
configApi: configApi,
@@ -17,7 +17,7 @@
import GoogleAuth from './GoogleAuth';
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const PREFIX = 'https://www.googleapis.com/auth/';
@@ -59,7 +59,7 @@ describe('GoogleAuth', () => {
[`${PREFIX}profile`, [`${PREFIX}profile`]],
[`${PREFIX}openid`, [`${PREFIX}openid`]],
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const googleAuth = GoogleAuth.create({
configApi: configApi,
@@ -17,7 +17,7 @@
import OAuth2 from './OAuth2';
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const theFuture = new Date(Date.now() + 3600000);
const thePast = new Date(Date.now() - 10);
@@ -35,7 +35,7 @@ jest.mock('../../../../lib/AuthSessionManager', () => ({
},
}));
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
describe('OAuth2', () => {
it('should get refreshed access token', async () => {
@@ -17,7 +17,7 @@
import OktaAuth from './OktaAuth';
import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi';
import { UrlPatternDiscovery } from '../../DiscoveryApi';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
const PREFIX = 'okta.';
@@ -51,7 +51,7 @@ describe('OktaAuth', () => {
[`${PREFIX}profile`, [`${PREFIX}profile`]],
[`${PREFIX}openid`, [`${PREFIX}openid`]],
])(`should normalize scopes correctly - %p`, (scope, scopes) => {
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const auth = OktaAuth.create({
configApi: configApi,
oauthRequestApi: new MockOAuthApi(),
@@ -20,7 +20,7 @@ import {
createRouteRef,
} from '@backstage/core-plugin-api';
import { collectRouteIds, resolveRouteBindings } from './resolveRouteBindings';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
describe('resolveRouteBindings', () => {
it('runs happy path', () => {
@@ -30,7 +30,7 @@ describe('resolveRouteBindings', () => {
({ bind }) => {
bind(external, { myRoute: ref });
},
new MockConfigApi({}),
mockApis.config(),
[],
);
@@ -45,7 +45,7 @@ describe('resolveRouteBindings', () => {
({ bind }) => {
bind(external, { someOtherRoute: ref } as any);
},
new MockConfigApi({}),
mockApis.config(),
[],
),
).toThrow('Key someOtherRoute is not an existing external route');
@@ -56,8 +56,10 @@ describe('resolveRouteBindings', () => {
const myTarget = createRouteRef({ id: 'test' });
const result = resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
mockApis.config({
data: {
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
},
}),
[
createPlugin({
@@ -84,8 +86,10 @@ describe('resolveRouteBindings', () => {
({ bind }) => {
bind({ mySource }, { mySource: false });
},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': 'myTarget' } } },
mockApis.config({
data: {
app: { routes: { bindings: { 'test.mySource': 'myTarget' } } },
},
}),
[
createPlugin({
@@ -106,8 +110,8 @@ describe('resolveRouteBindings', () => {
({ bind }) => {
bind({ mySource }, { mySource: myTarget });
},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': false } } },
mockApis.config({
data: { app: { routes: { bindings: { 'test.mySource': false } } } },
}),
[
createPlugin({
@@ -128,7 +132,7 @@ describe('resolveRouteBindings', () => {
expect(() =>
resolveRouteBindings(
() => {},
new MockConfigApi({ app: { routes: { bindings: 'derp' } } }),
mockApis.config({ data: { app: { routes: { bindings: 'derp' } } } }),
[],
),
).toThrow(
@@ -138,8 +142,8 @@ describe('resolveRouteBindings', () => {
expect(() =>
resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': true } } },
mockApis.config({
data: { app: { routes: { bindings: { 'test.mySource': true } } } },
}),
[],
),
@@ -150,8 +154,10 @@ describe('resolveRouteBindings', () => {
expect(() =>
resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
mockApis.config({
data: {
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
},
}),
[],
),
@@ -162,8 +168,10 @@ describe('resolveRouteBindings', () => {
expect(() =>
resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
mockApis.config({
data: {
app: { routes: { bindings: { 'test.mySource': 'test.myTarget' } } },
},
}),
[
createPlugin({
@@ -198,17 +206,17 @@ describe('resolveRouteBindings', () => {
});
// defaultTarget wins only if no bind or config matches
let result = resolveRouteBindings(() => {}, new MockConfigApi({}), [
plugin,
]);
let result = resolveRouteBindings(() => {}, mockApis.config(), [plugin]);
expect(result.get(source)).toBe(target1);
// config wins over defaultTarget
result = resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.source': 'test.target2' } } },
mockApis.config({
data: {
app: { routes: { bindings: { 'test.source': 'test.target2' } } },
},
}),
[plugin],
);
@@ -220,7 +228,7 @@ describe('resolveRouteBindings', () => {
({ bind }) => {
bind(plugin.externalRoutes, { source: plugin.routes.target2 });
},
new MockConfigApi({}),
mockApis.config(),
[plugin],
);
@@ -257,17 +265,15 @@ describe('collectRouteIds', () => {
});
// resolves normally with no config
let result = resolveRouteBindings(() => {}, new MockConfigApi({}), [
plugin,
]);
let result = resolveRouteBindings(() => {}, mockApis.config(), [plugin]);
expect(result.get(source)).toBe(target1);
// can be disabled
result = resolveRouteBindings(
() => {},
new MockConfigApi({
app: { routes: { bindings: { 'test.source': false } } },
mockApis.config({
data: { app: { routes: { bindings: { 'test.source': false } } } },
}),
[plugin],
);
@@ -16,7 +16,7 @@
import { configApiRef } from '@backstage/core-plugin-api';
import {
MockConfigApi,
mockApis,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
@@ -24,17 +24,19 @@ import { act, fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { SupportButton } from './SupportButton';
const configApi = new MockConfigApi({
app: {
support: {
url: 'https://github.com',
items: [
{
title: 'Github',
icon: 'github',
links: [{ title: 'Github Issues', url: '/issues' }],
},
],
const configApi = mockApis.config({
data: {
app: {
support: {
url: 'https://github.com',
items: [
{
title: 'Github',
icon: 'github',
links: [{ title: 'Github Issues', url: '/issues' }],
},
],
},
},
},
});
@@ -28,7 +28,7 @@ import {
createFrontendPlugin,
createRouteRef,
} from '@backstage/frontend-plugin-api';
import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils';
import { mockApis, TestApiRegistry } from '@backstage/test-utils';
import appPlugin from '@backstage/plugin-app';
import { readAppExtensionsConfig } from '../tree/readAppExtensionsConfig';
@@ -92,7 +92,7 @@ function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
builtinExtensions: [
resolveExtensionDefinition(Root, { namespace: 'root' }),
],
parameters: readAppExtensionsConfig(new MockConfigApi({})),
parameters: readAppExtensionsConfig(mockApis.config()),
forbidden: new Set(['root']),
}),
);
@@ -29,7 +29,7 @@ import {
} from '@backstage/frontend-plugin-api';
import { screen, render } from '@testing-library/react';
import { createSpecializedApp } from './createSpecializedApp';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
import React from 'react';
import {
configApiRef,
@@ -98,7 +98,7 @@ describe('createSpecializedApp', () => {
it('should forward config', () => {
const app = createSpecializedApp({
config: new MockConfigApi({ test: 'foo' }),
config: mockApis.config({ data: { test: 'foo' } }),
features: [
createFrontendPlugin({
id: 'test',
@@ -26,7 +26,7 @@ import {
} from '@backstage/frontend-plugin-api';
import { screen, waitFor } from '@testing-library/react';
import { CreateAppFeatureLoader, createApp } from './createApp';
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
import { mockApis, renderWithEffects } from '@backstage/test-utils';
import React from 'react';
import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api';
import appPlugin from '@backstage/plugin-app';
@@ -35,12 +35,14 @@ describe('createApp', () => {
it('should allow themes to be installed', async () => {
const app = createApp({
configLoader: async () => ({
config: new MockConfigApi({
app: {
extensions: [
{ 'theme:app/light': false },
{ 'theme:app/dark': false },
],
config: mockApis.config({
data: {
app: {
extensions: [
{ 'theme:app/light': false },
{ 'theme:app/dark': false },
],
},
},
}),
}),
@@ -72,7 +74,7 @@ describe('createApp', () => {
it('should deduplicate features keeping the last received one', async () => {
const duplicatedFeatureId = 'test';
const app = createApp({
configLoader: async () => ({ config: new MockConfigApi({}) }),
configLoader: async () => ({ config: mockApis.config() }),
features: [
createFrontendPlugin({
id: duplicatedFeatureId,
@@ -135,7 +137,7 @@ describe('createApp', () => {
const app = createApp({
configLoader: async () => ({
config: new MockConfigApi({ key: 'config-value' }),
config: mockApis.config({ data: { key: 'config-value' } }),
}),
features: [appPlugin, loader],
});
@@ -159,7 +161,7 @@ describe('createApp', () => {
const app = createApp({
configLoader: async () => ({
config: new MockConfigApi({}),
config: mockApis.config(),
}),
features: [loader],
});
@@ -173,7 +175,7 @@ describe('createApp', () => {
it('should register feature flags', async () => {
const app = createApp({
configLoader: async () => ({ config: new MockConfigApi({}) }),
configLoader: async () => ({ config: mockApis.config() }),
features: [
appPlugin.withOverrides({
extensions: [
@@ -227,7 +229,7 @@ describe('createApp', () => {
let appTreeApi: AppTreeApi | undefined = undefined;
const app = createApp({
configLoader: async () => ({ config: new MockConfigApi({}) }),
configLoader: async () => ({ config: mockApis.config() }),
features: [
createFrontendPlugin({
id: 'my-plugin',
@@ -22,7 +22,7 @@ import {
import { render, screen, waitFor } from '@testing-library/react';
import React, { useEffect } from 'react';
import { createPublicSignInApp } from './createPublicSignInApp';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
describe('createPublicSignInApp', () => {
beforeEach(() => {
@@ -31,7 +31,7 @@ describe('createPublicSignInApp', () => {
it('should render a sign-in page', async () => {
const app = createPublicSignInApp({
configLoader: async () => ({ config: new MockConfigApi({}) }),
configLoader: async () => ({ config: mockApis.config() }),
features: [
createFrontendModule({
pluginId: 'app',
@@ -59,7 +59,7 @@ describe('createPublicSignInApp', () => {
.mockReturnValue();
const app = createPublicSignInApp({
configLoader: async () => ({ config: new MockConfigApi({}) }),
configLoader: async () => ({ config: mockApis.config() }),
features: [
createFrontendModule({
pluginId: 'app',
@@ -23,7 +23,7 @@ import { JsonObject } from '@backstage/types';
import { createExtension } from './createExtension';
import { createExtensionDataRef } from './createExtensionDataRef';
import { coreExtensionData } from './coreExtensionData';
import { MockConfigApi, renderWithEffects } from '@backstage/test-utils';
import { mockApis, renderWithEffects } from '@backstage/test-utils';
import { createExtensionInput } from './createExtensionInput';
const nameExtensionDataRef = createExtensionDataRef<string>().with({
@@ -128,7 +128,7 @@ function createTestAppRoot({
}) {
return createApp({
features: [...features],
configLoader: async () => ({ config: new MockConfigApi(config) }),
configLoader: async () => ({ config: mockApis.config({ data: config }) }),
}).createRoot();
}
@@ -50,7 +50,6 @@
},
"peerDependencies": {
"@testing-library/react": "^16.0.0",
"@types/jest": "*",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
+5 -10
View File
@@ -3,13 +3,12 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="jest" />
/// <reference types="react" />
import { AnalyticsApi } from '@backstage/frontend-plugin-api';
import { AnalyticsEvent } from '@backstage/frontend-plugin-api';
import { AnyExtensionDataRef } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/frontend-plugin-api';
import { ApiMock } from '@backstage/test-utils';
import { AppNode } from '@backstage/frontend-plugin-api';
import { AppNodeInstance } from '@backstage/frontend-plugin-api';
import { ErrorWithContext } from '@backstage/test-utils';
@@ -18,6 +17,7 @@ import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { ExtensionDefinitionParameters } from '@backstage/frontend-plugin-api';
import { FrontendFeature } from '@backstage/frontend-app-api';
import { JsonObject } from '@backstage/types';
import { mockApis } from '@backstage/test-utils';
import { MockConfigApi } from '@backstage/test-utils';
import { MockErrorApi } from '@backstage/test-utils';
import { MockErrorApiOptions } from '@backstage/test-utils';
@@ -34,14 +34,7 @@ import { TestApiProviderProps } from '@backstage/test-utils';
import { TestApiRegistry } from '@backstage/test-utils';
import { withLogCollector } from '@backstage/test-utils';
// @public
export type ApiMock<TApi> = {
factory: ApiFactory<TApi, TApi, {}>;
} & {
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
? TApi[Key] & jest.MockInstance<Return, Args>
: TApi[Key];
};
export { ApiMock };
// @public (undocumented)
export function createExtensionTester<T extends ExtensionDefinitionParameters>(
@@ -103,6 +96,8 @@ export class MockAnalyticsApi implements AnalyticsApi {
getEvents(): AnalyticsEvent[];
}
export { mockApis };
export { MockConfigApi };
export { MockErrorApi };
@@ -24,7 +24,8 @@ export {
MockPermissionApi,
MockStorageApi,
type MockStorageBucket,
mockApis,
type ApiMock,
} from '@backstage/test-utils';
export { type ApiMock } from './ApiMock';
export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi';
+5
View File
@@ -65,6 +65,7 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@testing-library/jest-dom": "^6.0.0",
"@types/jest": "*",
"@types/react": "^18.0.0",
"msw": "^1.0.0",
"react": "^18.0.2",
@@ -73,12 +74,16 @@
},
"peerDependencies": {
"@testing-library/react": "^16.0.0",
"@types/jest": "*",
"@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0",
"react-router-dom": "6.0.0-beta.0 || ^6.3.0"
},
"peerDependenciesMeta": {
"@types/jest": {
"optional": true
},
"@types/react": {
"optional": true
}
+30 -1
View File
@@ -3,8 +3,11 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
/// <reference types="jest" />
import { AnalyticsApi } from '@backstage/core-plugin-api';
import { AnalyticsEvent } from '@backstage/core-plugin-api';
import { ApiFactory } from '@backstage/core-plugin-api';
import { ApiHolder } from '@backstage/core-plugin-api';
import { ApiRef } from '@backstage/core-plugin-api';
import { AppComponents } from '@backstage/core-plugin-api';
@@ -39,6 +42,15 @@ import { RouteRef } from '@backstage/core-plugin-api';
import { StorageApi } from '@backstage/core-plugin-api';
import { StorageValueSnapshot } from '@backstage/core-plugin-api';
// @public
export type ApiMock<TApi> = {
factory: ApiFactory<TApi, TApi, {}>;
} & {
[Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
? TApi[Key] & jest.MockInstance<Return, Args>
: TApi[Key];
};
// @public
export type AsyncLogCollector = () => Promise<void>;
@@ -77,10 +89,27 @@ export class MockAnalyticsApi implements AnalyticsApi {
getEvents(): AnalyticsEvent[];
}
// @public
export namespace mockApis {
export function config(options?: { data?: JsonObject }): jest.Mocked<Config>;
export namespace config {
const factory: (
options?:
| {
data?: JsonObject | undefined;
}
| undefined,
) => ApiFactory<Config, Config, {}>;
const mock: (partialImpl?: Partial<Config> | undefined) => ApiMock<Config>;
}
}
// @public @deprecated
export function mockBreakpoint(options: { matches: boolean }): void;
// @public
// Warning: (ae-unresolved-link) The @link reference could not be resolved: The reference is ambiguous because "config" has more than one declaration; you need to add a TSDoc member reference selector
//
// @public @deprecated
export class MockConfigApi implements ConfigApi {
constructor(data: JsonObject);
get<T = JsonValue>(key?: string): T;
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { ApiFactory } from '@backstage/frontend-plugin-api';
import { ApiFactory } from '@backstage/core-plugin-api';
/**
* Represents a mocked version of an API, where you automatically have access to
@@ -23,6 +23,7 @@ import { ConfigApi } from '@backstage/core-plugin-api';
* that can be used to mock configuration using a plain object.
*
* @public
* @deprecated Use {@link mockApis.config} instead
* @example
* ```tsx
* const mockConfig = new MockConfigApi({
@@ -20,3 +20,5 @@ export * from './ErrorApi';
export * from './FetchApi';
export * from './PermissionApi';
export * from './StorageApi';
export { type ApiMock } from './ApiMock';
export { mockApis } from './mockApis';
@@ -0,0 +1,40 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mockApis } from './mockApis';
describe('mockApis', () => {
describe('config', () => {
const data = { backend: { baseUrl: 'http://test.com' } };
it('can create an instance and make assertions on it', () => {
const empty = mockApis.config();
const notEmpty = mockApis.config({ data });
expect(empty.getOptional('backend.baseUrl')).toBeUndefined();
expect(empty.getOptional).toHaveBeenCalledTimes(1);
expect(notEmpty.getOptional('backend.baseUrl')).toEqual(
'http://test.com',
);
expect(notEmpty.getOptional).toHaveBeenCalledTimes(1);
});
it('can create a mock and make assertions on it', async () => {
const mock = mockApis.config.mock({ getString: () => 'replaced' });
expect(mock.getString('a')).toEqual('replaced');
expect(mock.getString).toHaveBeenCalledTimes(1);
});
});
});
@@ -0,0 +1,188 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ConfigReader } from '@backstage/config';
import {
ApiFactory,
ApiRef,
configApiRef,
createApiFactory,
} from '@backstage/core-plugin-api';
import { JsonObject } from '@backstage/types';
import { ApiMock } from './ApiMock';
/** @internal */
function simpleInstance<TApi extends object>(
_ref: ApiRef<TApi>,
instance: TApi,
mockSkeleton: () => jest.Mocked<TApi>,
): jest.Mocked<TApi> {
const mock = mockSkeleton();
const result = Object.create(instance) as any;
for (const [key, impl] of Object.entries(mock)) {
result[key] = (impl as any).mockImplementation((instance as any)[key]);
}
return result;
}
/** @internal */
function simpleFactory<TApi, TArgs extends unknown[]>(
ref: ApiRef<TApi>,
factory: (...args: TArgs) => TApi,
): (...args: TArgs) => ApiFactory<TApi, TApi, {}> {
return (...args) =>
createApiFactory({
api: ref,
deps: {},
factory: () => factory(...args),
});
}
/** @internal */
function simpleMock<TApi>(
ref: ApiRef<TApi>,
mockFactory: () => jest.Mocked<TApi>,
): (partialImpl?: Partial<TApi>) => ApiMock<TApi> {
return partialImpl => {
const mock = mockFactory();
if (partialImpl) {
for (const [key, impl] of Object.entries(partialImpl)) {
if (typeof impl === 'function') {
(mock as any)[key].mockImplementation(impl);
} else {
(mock as any)[key] = impl;
}
}
}
return Object.assign(mock, {
factory: createApiFactory({
api: ref,
deps: {},
factory: () => mock,
}),
}) as ApiMock<TApi>;
};
}
/**
* Mock implementations of the core utility APIs, to be used in tests.
*
* @public
* @remarks
*
* There are some variations among the APIs depending on what needs tests
* might have, but overall there are three main usage patterns:
*
* 1: Creating an actual fake API instance, often with a simplified version
* of functionality, by calling the mock API itself as a function.
*
* ```ts
* // The function often accepts parameters that control its behavior
* const foo = mockApis.foo();
* ```
*
* 2: Creating a mock API, where all methods are replaced with jest mocks, by
* calling the API's `mock` function.
*
* ```ts
* // You can optionally supply a subset of its methods to implement
* const foo = mockApis.foo.mock({
* someMethod: () => 'mocked result',
* });
* // After exercising your test, you can make assertions on the mock:
* expect(foo.someMethod).toHaveBeenCalledTimes(2);
* expect(foo.otherMethod).toHaveBeenCalledWith(testData);
* ```
*
* 3: Creating an API factory that behaves similarly to the mock as per above.
*
* ```ts
* const factory = mockApis.foo.factory({
* someMethod: () => 'mocked result',
* });
* ```
*/
export namespace mockApis {
const configMockSkeleton = () => ({
has: jest.fn(),
keys: jest.fn(),
get: jest.fn(),
getOptional: jest.fn(),
getConfig: jest.fn(),
getOptionalConfig: jest.fn(),
getConfigArray: jest.fn(),
getOptionalConfigArray: jest.fn(),
getNumber: jest.fn(),
getOptionalNumber: jest.fn(),
getBoolean: jest.fn(),
getOptionalBoolean: jest.fn(),
getString: jest.fn(),
getOptionalString: jest.fn(),
getStringArray: jest.fn(),
getOptionalStringArray: jest.fn(),
});
/**
* Fake implementation of {@link @backstage/frontend-plugin-api#ConfigApi}
* with optional data supplied.
*
* @public
* @example
*
* ```tsx
* const config = mockApis.config({
* data: { app: { baseUrl: 'https://example.com' } },
* });
*
* const rendered = await renderInTestApp(
* <TestApiProvider apis={[[configApiRef, config]]}>
* <MyTestedComponent />
* </TestApiProvider>,
* );
* ```
*/
export function config(options?: { data?: JsonObject }) {
return simpleInstance(
configApiRef,
new ConfigReader(options?.data, 'mock-config'),
configMockSkeleton,
);
}
/**
* Mock helpers for {@link @backstage/frontend-plugin-api#ConfigApi}.
*
* @see {@link @backstage/frontend-plugin-api#mockApis.config}
* @public
*/
export namespace config {
/**
* Creates a factory for a fake implementation of
* {@link @backstage/frontend-plugin-api#ConfigApi} with optional
* configuration data supplied.
*
* @public
*/
export const factory = simpleFactory(configApiRef, config);
/**
* Creates a mock implementation of
* {@link @backstage/frontend-plugin-api#ConfigApi}. All methods are
* replaced with jest mock functions, and you can optionally pass in a
* subset of methods with an explicit implementation.
*
* @public
*/
export const mock = simpleMock(configApiRef, configMockSkeleton);
}
}
@@ -16,7 +16,7 @@
import { Entity } from '@backstage/catalog-model';
import { configApiRef } from '@backstage/core-plugin-api';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import { makeStyles } from '@material-ui/core/styles';
import { render, screen } from '@testing-library/react';
import { renderHook } from '@testing-library/react';
@@ -46,7 +46,7 @@ const entities: Entity[] = [
},
];
const mockConfigApi = new MockConfigApi({});
const mockConfigApi = mockApis.config();
const apis = [[configApiRef, mockConfigApi]] as const;
describe('<PreviewCatalogInfoComponent />', () => {
@@ -122,10 +122,12 @@ describe('<PreviewCatalogInfoComponent />', () => {
apis={[
[
configApiRef,
new MockConfigApi({
catalog: {
import: {
entityFilename: 'anvil.yaml',
mockApis.config({
data: {
catalog: {
import: {
entityFilename: 'anvil.yaml',
},
},
},
}),
@@ -16,7 +16,7 @@
import { configApiRef, errorApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { TestApiProvider, MockConfigApi } from '@backstage/test-utils';
import { TestApiProvider, mockApis } from '@backstage/test-utils';
import TextField from '@material-ui/core/TextField';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
@@ -43,7 +43,7 @@ describe('<StepPrepareCreatePullRequest />', () => {
post: jest.fn(),
};
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<TestApiProvider
+37 -25
View File
@@ -15,15 +15,17 @@
*/
import { readFilterConfig, createFilterByQueryParamFromConfig } from './config';
import { MockConfigApi } from '@backstage/test-utils';
import { mockApis } from '@backstage/test-utils';
describe('config', () => {
describe('readFilterConfig', () => {
it('returns filter data', async () => {
const mockConfig = new MockConfigApi({
field: 'pathname',
operator: '==',
value: '/home',
const mockConfig = mockApis.config({
data: {
field: 'pathname',
operator: '==',
value: '/home',
},
});
const res = readFilterConfig(mockConfig);
expect(res).toEqual({
@@ -34,10 +36,12 @@ describe('config', () => {
});
it('returns undefined for invalid filter', async () => {
const mockInvalidConfig = new MockConfigApi({
myField: 'pathname',
operator: '==',
value: '3',
const mockInvalidConfig = mockApis.config({
data: {
myField: 'pathname',
operator: '==',
value: '3',
},
});
const res = readFilterConfig(mockInvalidConfig);
expect(res).toEqual(undefined);
@@ -46,15 +50,19 @@ describe('config', () => {
describe('createFilterByQueryParamFromConfig', () => {
it('returns filter data', async () => {
const mockConfig1 = new MockConfigApi({
field: 'id',
operator: '==',
value: '3',
const mockConfig1 = mockApis.config({
data: {
field: 'id',
operator: '==',
value: '3',
},
});
const mockConfig2 = new MockConfigApi({
field: 'pathname',
operator: '==',
value: 'path',
const mockConfig2 = mockApis.config({
data: {
field: 'pathname',
operator: '==',
value: 'path',
},
});
const res = createFilterByQueryParamFromConfig([
mockConfig1,
@@ -67,15 +75,19 @@ describe('config', () => {
});
it('returns only valid filters', async () => {
const mockValidConfig = new MockConfigApi({
field: 'id',
operator: '==',
value: 3,
const mockValidConfig = mockApis.config({
data: {
field: 'id',
operator: '==',
value: 3,
},
});
const mockInvalidConfig = new MockConfigApi({
myField: 'pathname',
operator: '==',
value: 'path',
const mockInvalidConfig = mockApis.config({
data: {
myField: 'pathname',
operator: '==',
value: 'path',
},
});
const res = createFilterByQueryParamFromConfig([
mockValidConfig,
@@ -19,7 +19,7 @@ import { Content } from './Content';
import {
TestApiProvider,
renderInTestApp,
MockConfigApi,
mockApis,
} from '@backstage/test-utils';
import { visitsApiRef } from '../../api';
import { ContextProvider } from './Context';
@@ -138,16 +138,18 @@ describe('<Content kind="recent"/>', () => {
});
it('allows recent items to be filtered using config', async () => {
const configApiMock = new MockConfigApi({
home: {
recentVisits: {
filterBy: [
{
field: 'pathname',
operator: '==',
value: '/tech-radar',
},
],
const configApiMock = mockApis.config({
data: {
home: {
recentVisits: {
filterBy: [
{
field: 'pathname',
operator: '==',
value: '/tech-radar',
},
],
},
},
},
});
@@ -206,20 +208,22 @@ describe('<Content kind="recent"/>', () => {
});
it('allows recent items to have no filter if the filter config is not valid', async () => {
const configApiMock = new MockConfigApi({
home: {
recentVisits: {
filterBy: [
{
operator: '==',
value: '/tech-radar',
},
{
field: 'pathname',
operator: '==',
value: '/explore',
},
],
const configApiMock = mockApis.config({
data: {
home: {
recentVisits: {
filterBy: [
{
operator: '==',
value: '/tech-radar',
},
{
field: 'pathname',
operator: '==',
value: '/explore',
},
],
},
},
},
});
@@ -283,16 +287,18 @@ describe('<Content kind="top"/>', () => {
});
it('allows top items to be filtered using config', async () => {
const configApiMock = new MockConfigApi({
home: {
topVisits: {
filterBy: [
{
field: 'pathname',
operator: '==',
value: '/explore',
},
],
const configApiMock = mockApis.config({
data: {
home: {
topVisits: {
filterBy: [
{
field: 'pathname',
operator: '==',
value: '/explore',
},
],
},
},
},
});
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import { screen, render, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
@@ -35,10 +35,12 @@ const SearchContextFilterSpy = ({ name }: { name: string }) => {
};
describe('SearchFilter.Autocomplete', () => {
const configApiMock = new MockConfigApi({
search: {
query: {
pageLimit: 100,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pageLimit: 100,
},
},
},
});
@@ -22,7 +22,7 @@ import { configApiRef } from '@backstage/core-plugin-api';
import { SearchContextProvider } from '../../context';
import { SearchFilter } from './SearchFilter';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import { searchApiRef } from '../../api';
describe('SearchFilter', () => {
@@ -37,10 +37,12 @@ describe('SearchFilter', () => {
const values = ['value1', 'value2'];
const filters = { unrelated: 'unrelated' };
const configApiMock = new MockConfigApi({
search: {
query: {
pagelimit: 10,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pagelimit: 10,
},
},
},
});
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { ApiProvider } from '@backstage/core-app-api';
import { MockConfigApi, TestApiRegistry } from '@backstage/test-utils';
import { mockApis, TestApiRegistry } from '@backstage/test-utils';
import { act, renderHook, waitFor } from '@testing-library/react';
import { searchApiRef } from '../../api';
@@ -27,17 +28,19 @@ jest.useFakeTimers();
describe('SearchFilter.hooks', () => {
describe('useDefaultFilterValue', () => {
const configApiMock = new MockConfigApi({
search: {
query: {
pageLimit: 100,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pageLimit: 100,
},
},
},
});
const searchApiMock = {
query: jest.fn().mockResolvedValue({ results: [] }),
};
const mockApis = TestApiRegistry.from(
const apis = TestApiRegistry.from(
[searchApiRef, searchApiMock],
[configApiRef, configApiMock],
);
@@ -54,7 +57,7 @@ describe('SearchFilter.hooks', () => {
filters: {},
};
return (
<ApiProvider apis={mockApis}>
<ApiProvider apis={apis}>
<SearchContextProvider
initialState={{ ...emptySearchContext, ...overrides }}
>
@@ -19,7 +19,7 @@ import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import {
MockConfigApi,
mockApis,
renderWithEffects,
TestApiProvider,
} from '@backstage/test-utils';
@@ -31,10 +31,12 @@ import { SearchPagination } from './SearchPagination';
import { configApiRef } from '@backstage/core-plugin-api';
describe('SearchPagination', () => {
const configApiMock = new MockConfigApi({
search: {
query: {
pagelimit: 10,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pagelimit: 10,
},
},
},
});
@@ -22,7 +22,7 @@ import {
act,
renderHook,
} from '@testing-library/react';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import {
SearchContextProvider,
@@ -37,7 +37,7 @@ describe('SearchContext', () => {
} satisfies typeof searchApiRef.T;
const wrapper = ({ children, initialState, config = {} }: any) => {
const configApiMock = new MockConfigApi(config);
const configApiMock = mockApis.config({ data: config });
return (
<TestApiProvider
apis={[
@@ -433,7 +433,7 @@ describe('SearchContext', () => {
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => {
const configApiMock = new MockConfigApi({});
const configApiMock = mockApis.config();
return (
<TestApiProvider
apis={[
@@ -491,7 +491,7 @@ describe('SearchContext', () => {
const { result } = renderHook(() => useSearch(), {
wrapper: ({ children }) => {
const configApiMock = new MockConfigApi({});
const configApiMock = mockApis.config();
return (
<TestApiProvider
apis={[
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import { act, render, waitFor } from '@testing-library/react';
import user from '@testing-library/user-event';
import {
@@ -41,10 +41,12 @@ jest.mock('@backstage/plugin-search-react', () => ({
}));
describe('SearchType.Accordion', () => {
const configApiMock = new MockConfigApi({
search: {
query: {
pagelimit: 10,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pagelimit: 10,
},
},
},
});
@@ -15,7 +15,7 @@
*/
import React from 'react';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
import { act, render } from '@testing-library/react';
import user from '@testing-library/user-event';
import {
@@ -40,10 +40,12 @@ jest.mock('@backstage/plugin-search-react', () => ({
describe('SearchType.Tabs', () => {
const searchApiMock = { query: jest.fn().mockResolvedValue({ results: [] }) };
const configApiMock = new MockConfigApi({
search: {
query: {
pageLimit: 100,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pageLimit: 100,
},
},
},
});
@@ -23,7 +23,7 @@ import {
searchApiRef,
} from '@backstage/plugin-search-react';
import { SearchType } from './SearchType';
import { MockConfigApi, TestApiProvider } from '@backstage/test-utils';
import { mockApis, TestApiProvider } from '@backstage/test-utils';
describe('SearchType', () => {
const initialState = {
@@ -36,10 +36,12 @@ describe('SearchType', () => {
const values = ['value1', 'value2'];
const typeValues = ['preselected'];
const configApiMock = new MockConfigApi({
search: {
query: {
pagelimit: 10,
const configApiMock = mockApis.config({
data: {
search: {
query: {
pagelimit: 10,
},
},
},
});
+3 -2
View File
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { renderHook, act, waitFor } from '@testing-library/react';
@@ -21,7 +22,7 @@ import { ThemeProvider } from '@material-ui/core/styles';
import { lightTheme } from '@backstage/theme';
import {
MockAnalyticsApi,
MockConfigApi,
mockApis,
TestApiProvider,
} from '@backstage/test-utils';
import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
@@ -84,7 +85,7 @@ const wrapper = ({
<TestApiProvider
apis={[
[analyticsApiRef, analyticsApiMock],
[configApiRef, new MockConfigApi(config ?? {})],
[configApiRef, mockApis.config({ data: config ?? {} })],
[techdocsApiRef, techdocsApiMock],
]}
>
+2 -2
View File
@@ -18,7 +18,7 @@ import { UrlPatternDiscovery } from '@backstage/core-app-api';
import { IdentityApi } from '@backstage/core-plugin-api';
import { NotFoundError } from '@backstage/errors';
import { fetchEventSource } from '@microsoft/fetch-event-source';
import { MockConfigApi, MockFetchApi } from '@backstage/test-utils';
import { mockApis, MockFetchApi } from '@backstage/test-utils';
import { TechDocsStorageClient } from './client';
jest.mock('@microsoft/fetch-event-source');
@@ -34,7 +34,7 @@ const mockEntity = {
describe('TechDocsStorageClient', () => {
const mockBaseUrl = 'http://backstage:9191/api/techdocs';
const configApi = new MockConfigApi({});
const configApi = mockApis.config();
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
const identityApi: jest.Mocked<IdentityApi> = {
getCredentials: jest.fn(),
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { scmIntegrationsApiRef } from '@backstage/integration-react';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
import {
MockConfigApi,
mockApis,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
@@ -106,10 +107,8 @@ jest.mock('@backstage/core-components', () => ({
Page: jest.fn(),
}));
const configApi = new MockConfigApi({
app: {
baseUrl: 'http://localhost:3000',
},
const configApi = mockApis.config({
data: { app: { baseUrl: 'http://localhost:3000' } },
});
const Wrapper = ({ children }: { children: React.ReactNode }) => {
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import {
MockConfigApi,
mockApis,
renderInTestApp,
TestApiProvider,
} from '@backstage/test-utils';
@@ -55,16 +56,7 @@ describe('useNavigateUrl', () => {
const baseUrl = 'http://localhost:3000';
await renderInTestApp(
<TestApiProvider
apis={[
[
configApiRef,
new MockConfigApi({
app: {
baseUrl,
},
}),
],
]}
apis={[[configApiRef, mockApis.config({ data: { app: { baseUrl } } })]]}
>
<Component to={`${baseUrl}/test`} />
</TestApiProvider>,
@@ -75,16 +67,7 @@ describe('useNavigateUrl', () => {
const baseUrl = 'http://localhost:3000/instance';
await renderInTestApp(
<TestApiProvider
apis={[
[
configApiRef,
new MockConfigApi({
app: {
baseUrl,
},
}),
],
]}
apis={[[configApiRef, mockApis.config({ data: { app: { baseUrl } } })]]}
>
<Component to={`${baseUrl}/test`} />
</TestApiProvider>,
@@ -95,16 +78,7 @@ describe('useNavigateUrl', () => {
const baseUrl = 'http://localhost:3000';
await renderInTestApp(
<TestApiProvider
apis={[
[
configApiRef,
new MockConfigApi({
app: {
baseUrl,
},
}),
],
]}
apis={[[configApiRef, mockApis.config({ data: { app: { baseUrl } } })]]}
>
<Component to="/test" />
</TestApiProvider>,
+5 -2
View File
@@ -4683,7 +4683,6 @@ __metadata:
zod: ^3.22.4
peerDependencies:
"@testing-library/react": ^16.0.0
"@types/jest": "*"
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
react: ^16.13.1 || ^17.0.0 || ^18.0.0
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
@@ -8564,6 +8563,7 @@ __metadata:
"@material-ui/core": ^4.12.2
"@material-ui/icons": ^4.9.1
"@testing-library/jest-dom": ^6.0.0
"@types/jest": "*"
"@types/react": ^18.0.0
cross-fetch: ^4.0.0
i18next: ^22.4.15
@@ -8574,11 +8574,14 @@ __metadata:
zen-observable: ^0.10.0
peerDependencies:
"@testing-library/react": ^16.0.0
"@types/jest": "*"
"@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0
react: ^16.13.1 || ^17.0.0 || ^18.0.0
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
react-router-dom: 6.0.0-beta.0 || ^6.3.0
peerDependenciesMeta:
"@types/jest":
optional: true
"@types/react":
optional: true
languageName: unknown
@@ -17912,7 +17915,7 @@ __metadata:
languageName: node
linkType: hard
"@types/jest@npm:^29.5.11":
"@types/jest@npm:*, @types/jest@npm:^29.5.11":
version: 29.5.13
resolution: "@types/jest@npm:29.5.13"
dependencies: