catalog-react: factory mock shortcut for catalogApiMock

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-02-05 23:36:53 +01:00
parent 0d7e331265
commit 6362ca4b17
8 changed files with 68 additions and 30 deletions
+8 -3
View File
@@ -65,6 +65,14 @@ export type ApiMock<TApi> = {
: TApi[Key];
};
// @public
export function attachMockApiFactory<TApi, TImpl extends TApi = TApi>(
apiRef: ApiRef<TApi>,
implementation: TImpl,
): TImpl & {
[mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;
};
// @public (undocumented)
export function createExtensionTester<
T extends ExtensionDefinitionParameters,
@@ -159,9 +167,6 @@ export class MockAnalyticsApi implements AnalyticsApi {
// @public
export type MockApiFactorySymbol = typeof mockApiFactorySymbol;
// @public
export const mockApiFactorySymbol: unique symbol;
// @public
export namespace mockApis {
export function alert(): MockWithApiFactory<MockAlertApi>;
@@ -19,7 +19,7 @@ export {
type MockApiFactorySymbol,
type ApiMock,
type MockWithApiFactory,
mockApiFactorySymbol,
attachMockApiFactory,
} from './utils';
/**
+32 -1
View File
@@ -20,7 +20,7 @@ import { ApiFactory } from '@backstage/frontend-plugin-api';
/**
* Symbol used to mark mock API instances with their corresponding API factory.
*
* @public
* @ignore
*/
export const mockApiFactorySymbol = Symbol.for('@backstage/mock-api');
@@ -78,6 +78,37 @@ export function mockWithApiFactory<TApi, TImpl extends TApi = TApi>(
return marked;
}
/**
* Attaches mock API factory metadata to an API instance, allowing it to be
* passed directly to test utilities without needing to explicitly provide
* the [apiRef, implementation] tuple.
*
* @public
* @example
* ```ts
* const catalogApi = attachMockApiFactory(
* catalogApiRef,
* new InMemoryCatalogClient()
* );
* // Can now be passed directly to TestApiProvider
* <TestApiProvider apis={[catalogApi]}>
* ```
*/
export function attachMockApiFactory<TApi, TImpl extends TApi = TApi>(
apiRef: ApiRef<TApi>,
implementation: TImpl,
): TImpl & { [mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}> } {
const marked = implementation as TImpl & {
[mockApiFactorySymbol]: ApiFactory<TApi, TApi, {}>;
};
(marked as any)[mockApiFactorySymbol] = {
api: apiRef,
deps: {},
factory: () => implementation,
};
return marked;
}
/**
* Retrieves the API factory from a mock API instance.
* Returns undefined if the value is not a mock API instance.
+3 -4
View File
@@ -21,7 +21,6 @@ import {
createTestEntityPage,
catalogApiMock,
} from '@backstage/plugin-catalog-react/testUtils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import catalogGraphPlugin from './alpha';
import { catalogGraphRouteRef } from './routes';
import { catalogGraphApiRef, DefaultCatalogGraphApi } from './api';
@@ -58,7 +57,7 @@ describe('catalog-graph alpha plugin', () => {
'/catalog-graph': catalogGraphRouteRef,
},
apis: [
[catalogApiRef, catalogApiMock({ entities: [entity] })],
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
],
});
@@ -95,7 +94,7 @@ describe('catalog-graph alpha plugin', () => {
'/catalog-graph': catalogGraphRouteRef,
},
apis: [
[catalogApiRef, catalogApiMock({ entities: [entity] })],
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
],
});
@@ -131,7 +130,7 @@ describe('catalog-graph alpha plugin', () => {
'/catalog-graph': catalogGraphRouteRef,
},
apis: [
[catalogApiRef, catalogApiMock({ entities: [entity] })],
catalogApiMock({ entities: [entity] }),
[catalogGraphApiRef, new DefaultCatalogGraphApi()],
],
});
@@ -11,10 +11,13 @@ import { Entity } from '@backstage/catalog-model';
import { EntityListContextProps } from '@backstage/plugin-catalog-react';
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { MockWithApiFactory } from '@backstage/frontend-test-utils';
import { PropsWithChildren } from 'react';
// @public
export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi;
export function catalogApiMock(options?: {
entities?: Entity[];
}): MockWithApiFactory<CatalogApi>;
// @public
export namespace catalogApiMock {
@@ -23,7 +23,11 @@ import { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils';
import { Entity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { CatalogApi } from '@backstage/catalog-client';
import { ApiMock } from '@backstage/frontend-test-utils';
import {
ApiMock,
attachMockApiFactory,
type MockWithApiFactory,
} from '@backstage/frontend-test-utils';
/** @internal */
function simpleMock<TApi>(
@@ -41,13 +45,13 @@ function simpleMock<TApi>(
}
}
}
return Object.assign(mock, {
factory: createApiFactory({
api: ref,
deps: {},
factory: () => mock,
}),
}) as ApiMock<TApi>;
const factory = createApiFactory({
api: ref,
deps: {},
factory: () => mock,
});
const marked = attachMockApiFactory(ref, mock);
return Object.assign(marked, { factory }) as ApiMock<TApi>;
};
}
@@ -58,8 +62,11 @@ function simpleMock<TApi>(
*
* @public
*/
export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi {
return new InMemoryCatalogClient(options);
export function catalogApiMock(options?: {
entities?: Entity[];
}): MockWithApiFactory<CatalogApi> {
const instance = new InMemoryCatalogClient(options);
return attachMockApiFactory(catalogApiRef, instance);
}
/**
@@ -23,7 +23,6 @@ import {
EntityCardBlueprint,
EntityContentBlueprint,
} from '../alpha/blueprints';
import { catalogApiRef } from '../api';
const mockEntity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
@@ -386,7 +385,7 @@ describe('createTestEntityPage', () => {
createTestEntityPage({ entity: mockEntity }),
catalogApiCard,
],
apis: [[catalogApiRef, catalogApiMock({ entities: customEntities })]],
apis: [catalogApiMock({ entities: customEntities })],
});
// Should use the user-provided catalog API with custom entities, not mockEntity
+2 -8
View File
@@ -21,7 +21,6 @@ import {
createTestEntityPage,
catalogApiMock,
} from '@backstage/plugin-catalog-react/testUtils';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import orgPlugin from './alpha';
const EntityGroupProfileCard = orgPlugin.getExtension(
@@ -230,12 +229,7 @@ describe('org plugin entity cards', () => {
createTestEntityPage({ entity: groupEntity }),
EntityMembersListCard,
],
apis: [
[
catalogApiRef,
catalogApiMock({ entities: [groupEntity, memberUser] }),
],
],
apis: [catalogApiMock({ entities: [groupEntity, memberUser] })],
});
expect(await screen.findByText('Alice Smith')).toBeInTheDocument();
@@ -261,7 +255,7 @@ describe('org plugin entity cards', () => {
createTestEntityPage({ entity: groupEntity }),
EntityMembersListCard,
],
apis: [[catalogApiRef, catalogApiMock({ entities: [groupEntity] })]],
apis: [catalogApiMock({ entities: [groupEntity] })],
});
expect(