diff --git a/.changeset/great-eagles-repair.md b/.changeset/great-eagles-repair.md
new file mode 100644
index 0000000000..886fb5df7a
--- /dev/null
+++ b/.changeset/great-eagles-repair.md
@@ -0,0 +1,5 @@
+---
+'@backstage/frontend-test-utils': patch
+---
+
+Added an `ApiMock`, analogous to `ServiceMock` from the backend test utils.
diff --git a/.changeset/healthy-years-search.md b/.changeset/healthy-years-search.md
new file mode 100644
index 0000000000..a613d11eea
--- /dev/null
+++ b/.changeset/healthy-years-search.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-catalog-react': minor
+---
+
+Add catalog service mocks under the `/testUtils` subpath export.
+
+You can now use e.g. `const catalog = catalogApiMock.mock()` in your test and then do assertions on `catalog.getEntities` without awkward type casting.
diff --git a/.changeset/lovely-bees-walk.md b/.changeset/lovely-bees-walk.md
new file mode 100644
index 0000000000..223acf1769
--- /dev/null
+++ b/.changeset/lovely-bees-walk.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-node': patch
+---
+
+Documentation for the `testUtils` named export
diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json
index 27bb592879..49869b7df9 100644
--- a/packages/frontend-test-utils/package.json
+++ b/packages/frontend-test-utils/package.json
@@ -50,6 +50,7 @@
},
"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",
diff --git a/packages/frontend-test-utils/report.api.md b/packages/frontend-test-utils/report.api.md
index 1337e5eb4f..3229da9898 100644
--- a/packages/frontend-test-utils/report.api.md
+++ b/packages/frontend-test-utils/report.api.md
@@ -3,11 +3,13 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
+///
///
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 { AppNode } from '@backstage/frontend-plugin-api';
import { AppNodeInstance } from '@backstage/frontend-plugin-api';
import { ErrorWithContext } from '@backstage/test-utils';
@@ -32,6 +34,15 @@ import { TestApiProviderProps } from '@backstage/test-utils';
import { TestApiRegistry } from '@backstage/test-utils';
import { withLogCollector } from '@backstage/test-utils';
+// @public
+export type ApiMock = {
+ factory: ApiFactory;
+} & {
+ [Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
+ ? TApi[Key] & jest.MockInstance
+ : TApi[Key];
+};
+
// @public (undocumented)
export function createExtensionTester(
subject: ExtensionDefinition,
diff --git a/packages/frontend-test-utils/src/apis/ApiMock.ts b/packages/frontend-test-utils/src/apis/ApiMock.ts
new file mode 100644
index 0000000000..d11689f98a
--- /dev/null
+++ b/packages/frontend-test-utils/src/apis/ApiMock.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { ApiFactory } from '@backstage/frontend-plugin-api';
+
+/**
+ * Represents a mocked version of an API, where you automatically have access to
+ * the mocked versions of all of its methods along with a factory that returns
+ * that same mock.
+ *
+ * @public
+ */
+export type ApiMock = {
+ factory: ApiFactory;
+} & {
+ [Key in keyof TApi]: TApi[Key] extends (...args: infer Args) => infer Return
+ ? TApi[Key] & jest.MockInstance
+ : TApi[Key];
+};
diff --git a/packages/frontend-test-utils/src/apis/index.ts b/packages/frontend-test-utils/src/apis/index.ts
index 1230be9be0..564c327aef 100644
--- a/packages/frontend-test-utils/src/apis/index.ts
+++ b/packages/frontend-test-utils/src/apis/index.ts
@@ -26,4 +26,5 @@ export {
type MockStorageBucket,
} from '@backstage/test-utils';
+export { type ApiMock } from './ApiMock';
export { MockAnalyticsApi } from './AnalyticsApi/MockAnalyticsApi';
diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json
index 3c7052b78b..00bc98b251 100644
--- a/plugins/catalog-node/package.json
+++ b/plugins/catalog-node/package.json
@@ -16,6 +16,9 @@
"publishConfig": {
"access": "public"
},
+ "keywords": [
+ "backstage"
+ ],
"homepage": "https://backstage.io",
"repository": {
"type": "git",
diff --git a/plugins/catalog-node/report-testUtils.api.md b/plugins/catalog-node/report-testUtils.api.md
index c1dfb0bd7a..ec50d835be 100644
--- a/plugins/catalog-node/report-testUtils.api.md
+++ b/plugins/catalog-node/report-testUtils.api.md
@@ -22,6 +22,4 @@ export namespace catalogServiceMock {
partialImpl?: Partial | undefined,
) => ServiceMock;
}
-
-// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/catalog-node/src/testUtils.ts b/plugins/catalog-node/src/testUtils.ts
index de60594701..fd1134daf4 100644
--- a/plugins/catalog-node/src/testUtils.ts
+++ b/plugins/catalog-node/src/testUtils.ts
@@ -14,4 +14,10 @@
* limitations under the License.
*/
+/**
+ * Backend test helpers for the Catalog plugin.
+ *
+ * @packageDocumentation
+ */
+
export { catalogServiceMock } from './testUtils/catalogServiceMock';
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index c959e22b9e..672ca2e0ed 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -30,6 +30,7 @@
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha/index.ts",
+ "./testUtils": "./src/testUtils.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
@@ -39,6 +40,9 @@
"alpha": [
"src/alpha/index.ts"
],
+ "testUtils": [
+ "src/testUtils.ts"
+ ],
"package.json": [
"package.json"
]
@@ -64,6 +68,7 @@
"@backstage/core-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
+ "@backstage/frontend-test-utils": "workspace:^",
"@backstage/integration-react": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
@@ -85,7 +90,6 @@
"devDependencies": {
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
- "@backstage/frontend-test-utils": "workspace:^",
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/test-utils": "workspace:^",
diff --git a/plugins/catalog-react/report-testUtils.api.md b/plugins/catalog-react/report-testUtils.api.md
new file mode 100644
index 0000000000..331ef45988
--- /dev/null
+++ b/plugins/catalog-react/report-testUtils.api.md
@@ -0,0 +1,36 @@
+## API Report File for "@backstage/plugin-catalog-react"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { ApiFactory } from '@backstage/frontend-plugin-api';
+import { ApiMock } from '@backstage/frontend-test-utils';
+import { CatalogApi } from '@backstage/catalog-client';
+import { DefaultEntityFilters } from '@backstage/plugin-catalog-react';
+import { Entity } from '@backstage/catalog-model';
+import { EntityListContextProps } from '@backstage/plugin-catalog-react';
+import { PropsWithChildren } from 'react';
+import { default as React_2 } from 'react';
+
+// @public
+export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi;
+
+// @public
+export namespace catalogApiMock {
+ const factory: (options?: {
+ entities?: Entity[];
+ }) => ApiFactory;
+ const mock: (
+ partialImpl?: Partial | undefined,
+ ) => ApiMock;
+}
+
+// @public
+export function MockEntityListContextProvider<
+ T extends DefaultEntityFilters = DefaultEntityFilters,
+>(
+ props: PropsWithChildren<{
+ value?: Partial>;
+ }>,
+): React_2.JSX.Element;
+```
diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md
index 0fbb27e70e..515a3526be 100644
--- a/plugins/catalog-react/report.api.md
+++ b/plugins/catalog-react/report.api.md
@@ -695,7 +695,7 @@ export function MissingAnnotationEmptyState(props: {
// @public (undocumented)
export type MissingAnnotationEmptyStateClassKey = 'code';
-// @public (undocumented)
+// @public @deprecated (undocumented)
export function MockEntityListContextProvider<
T extends DefaultEntityFilters = DefaultEntityFilters,
>(
@@ -887,6 +887,7 @@ export function useStarredEntity(
// src/components/UserListPicker/UserListPicker.d.ts:5:1 - (ae-undocumented) Missing documentation for "CatalogReactUserListPickerClassKey".
// src/components/UserListPicker/UserListPicker.d.ts:15:1 - (ae-undocumented) Missing documentation for "UserListPickerProps".
// src/components/UserListPicker/UserListPicker.d.ts:20:22 - (ae-undocumented) Missing documentation for "UserListPicker".
+// src/deprecated.d.ts:7:1 - (ae-undocumented) Missing documentation for "MockEntityListContextProvider".
// src/filters.d.ts:8:5 - (ae-undocumented) Missing documentation for "value".
// src/filters.d.ts:10:5 - (ae-undocumented) Missing documentation for "getCatalogFilters".
// src/filters.d.ts:11:5 - (ae-undocumented) Missing documentation for "toQueryValue".
@@ -948,7 +949,6 @@ export function useStarredEntity(
// src/hooks/useStarredEntity.d.ts:3:1 - (ae-undocumented) Missing documentation for "useStarredEntity".
// src/overridableComponents.d.ts:6:1 - (ae-undocumented) Missing documentation for "CatalogReactComponentsNameToClassKey".
// src/overridableComponents.d.ts:19:1 - (ae-undocumented) Missing documentation for "BackstageOverrides".
-// src/testUtils/providers.d.ts:4:1 - (ae-undocumented) Missing documentation for "MockEntityListContextProvider".
// src/types.d.ts:3:1 - (ae-undocumented) Missing documentation for "EntityFilter".
// src/types.d.ts:25:1 - (ae-undocumented) Missing documentation for "UserListFilterKind".
// src/types.d.ts:27:1 - (ae-undocumented) Missing documentation for "EntityListPagination".
diff --git a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx
index 26dee40ec3..3e6ed3c2ed 100644
--- a/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityAutocompletePicker/EntityAutocompletePicker.test.tsx
@@ -16,7 +16,7 @@
import { fireEvent, render, waitFor, screen } from '@testing-library/react';
import React from 'react';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityAutocompletePicker } from './EntityAutocompletePicker';
import { TestApiProvider } from '@backstage/test-utils';
import { catalogApiRef } from '../../api';
diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
index 612c515d28..4633862f2c 100644
--- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.test.tsx
@@ -24,7 +24,7 @@ import { capitalize } from 'lodash';
import { default as React } from 'react';
import { catalogApiRef } from '../../api';
import { EntityKindFilter } from '../../filters';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityKindPicker } from './EntityKindPicker';
const entities: Entity[] = [
diff --git a/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.test.tsx b/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.test.tsx
index fd779be1a2..701e1047c9 100644
--- a/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.test.tsx
@@ -16,7 +16,7 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityLifecycleFilter } from '../../filters';
import { EntityLifecyclePicker } from './EntityLifecyclePicker';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
diff --git a/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.test.tsx b/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.test.tsx
index 0fd336da42..5253dfae28 100644
--- a/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityNamespacePicker/EntityNamespacePicker.test.tsx
@@ -16,7 +16,7 @@
import { fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityNamespaceFilter } from '../../filters';
import { EntityNamespacePicker } from './EntityNamespacePicker';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx
index 8942dadca3..f8f2497b0f 100644
--- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx
@@ -17,7 +17,7 @@
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import React from 'react';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityOwnerFilter } from '../../filters';
import { EntityOwnerPicker } from './EntityOwnerPicker';
import { ApiProvider } from '@backstage/core-app-api';
diff --git a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.test.tsx b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.test.tsx
index 329df71fae..16fb3130f3 100644
--- a/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityProcessingStatusPicker/EntityProcessingStatusPicker.test.tsx
@@ -17,7 +17,7 @@
import { fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { EntityErrorFilter, EntityOrphanFilter } from '../../filters';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityProcessingStatusPicker } from './EntityProcessingStatusPicker';
import { renderInTestApp } from '@backstage/test-utils';
diff --git a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.test.tsx b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.test.tsx
index cbfeffa009..63047c163b 100644
--- a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.test.tsx
+++ b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { fireEvent, waitFor, screen } from '@testing-library/react';
import { EntitySearchBar } from './EntitySearchBar';
import { EntityTextFilter } from '../../filters';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { renderInTestApp } from '@backstage/test-utils';
describe('EntitySearchBar', () => {
diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx
index 62f6991034..9b7fa2d2a3 100644
--- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.test.tsx
@@ -16,7 +16,7 @@
import { fireEvent, waitFor, screen, act } from '@testing-library/react';
import React from 'react';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { EntityTagFilter } from '../../filters';
import { EntityTagPicker } from './EntityTagPicker';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
index e558a36869..f7954f2604 100644
--- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { fireEvent, waitFor, screen, within } from '@testing-library/react';
import { Entity } from '@backstage/catalog-model';
import { EntityTypePicker } from './EntityTypePicker';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { catalogApiRef } from '../../api';
import { EntityKindFilter, EntityTypeFilter } from '../../filters';
import { alertApiRef } from '@backstage/core-plugin-api';
diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
index f0da550d0b..a3230a0cc4 100644
--- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
+++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-import { CatalogApi, Location } from '@backstage/catalog-client';
+import { Location } from '@backstage/catalog-client';
import { Entity, ANNOTATION_ORIGIN_LOCATION } from '@backstage/catalog-model';
import { catalogApiRef } from '../../api';
import { renderHook, waitFor } from '@testing-library/react';
import React from 'react';
import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState';
import { TestApiProvider } from '@backstage/test-utils';
+import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
function defer(): { promise: Promise; resolve: (value: T) => void } {
let resolve: (value: T) => void = () => {};
@@ -31,13 +32,7 @@ function defer(): { promise: Promise; resolve: (value: T) => void } {
}
describe('useUnregisterEntityDialogState', () => {
- const catalogApiMock = {
- getLocationByRef: jest.fn(),
- getEntities: jest.fn(),
- removeLocationById: jest.fn(),
- removeEntityByUid: jest.fn(),
- };
- const catalogApi = catalogApiMock as Partial as CatalogApi;
+ const catalogApi = catalogApiMock.mock();
const Wrapper = (props: { children?: React.ReactNode }) => (
@@ -58,8 +53,8 @@ describe('useUnregisterEntityDialogState', () => {
resolveLocation = deferredLocation.resolve;
resolveColocatedEntities = deferredColocatedEntities.resolve;
- catalogApiMock.getLocationByRef.mockReturnValue(deferredLocation.promise);
- catalogApiMock.getEntities.mockReturnValue(
+ catalogApi.getLocationByRef.mockReturnValue(deferredLocation.promise);
+ catalogApi.getEntities.mockReturnValue(
deferredColocatedEntities.promise.then(items => ({ items })),
);
diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
index 0b101e8174..5efb7339cf 100644
--- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
+++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
@@ -18,7 +18,7 @@ import React from 'react';
import { fireEvent, waitFor, screen } from '@testing-library/react';
import { UserEntity } from '@backstage/catalog-model';
import { UserListPicker, UserListPickerProps } from './UserListPicker';
-import { MockEntityListContextProvider } from '../../testUtils/providers';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import {
EntityKindFilter,
EntityNamespaceFilter,
diff --git a/plugins/catalog-react/src/deprecated.tsx b/plugins/catalog-react/src/deprecated.tsx
new file mode 100644
index 0000000000..8e088502e8
--- /dev/null
+++ b/plugins/catalog-react/src/deprecated.tsx
@@ -0,0 +1,93 @@
+/*
+ * 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 React, {
+ PropsWithChildren,
+ useCallback,
+ useMemo,
+ useState,
+} from 'react';
+import {
+ DefaultEntityFilters,
+ EntityListContext,
+ EntityListContextProps,
+} from './hooks/useEntityListProvider';
+
+/**
+ * @public
+ * @deprecated Moved to `@backstage/plugin-catalog-react/testUtils`
+ */
+export function MockEntityListContextProvider<
+ T extends DefaultEntityFilters = DefaultEntityFilters,
+>(
+ props: PropsWithChildren<{
+ value?: Partial>;
+ }>,
+) {
+ const { children, value } = props;
+
+ // Provides a default implementation that stores filter state, for testing components that
+ // reflect filter state.
+ const [filters, setFilters] = useState(value?.filters ?? ({} as T));
+
+ const updateFilters = useCallback(
+ (update: Partial | ((prevFilters: T) => Partial)) => {
+ setFilters(prevFilters => {
+ const newFilters =
+ typeof update === 'function' ? update(prevFilters) : update;
+ return { ...prevFilters, ...newFilters };
+ });
+ },
+ [],
+ );
+
+ // Memoize the default values since pickers have useEffect triggers on these; naively defaulting
+ // below with `?? ` breaks referential equality on subsequent updates.
+ const defaultValues = useMemo(
+ () => ({
+ entities: [],
+ backendEntities: [],
+ queryParameters: {},
+ }),
+ [],
+ );
+
+ const resolvedValue: EntityListContextProps = useMemo(
+ () => ({
+ entities: value?.entities ?? defaultValues.entities,
+ backendEntities: value?.backendEntities ?? defaultValues.backendEntities,
+ updateFilters: value?.updateFilters ?? updateFilters,
+ filters,
+ loading: value?.loading ?? false,
+ queryParameters: value?.queryParameters ?? defaultValues.queryParameters,
+ error: value?.error,
+ totalItems:
+ value?.totalItems ?? (value?.entities ?? defaultValues.entities).length,
+ limit: value?.limit ?? 20,
+ offset: value?.offset,
+ setLimit: value?.setLimit ?? (() => {}),
+ setOffset: value?.setOffset,
+ paginationMode: value?.paginationMode ?? 'none',
+ }),
+ [value, defaultValues, filters, updateFilters],
+ );
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/plugins/catalog-react/src/index.ts b/plugins/catalog-react/src/index.ts
index b511116f84..2ea1eb6f53 100644
--- a/plugins/catalog-react/src/index.ts
+++ b/plugins/catalog-react/src/index.ts
@@ -28,8 +28,8 @@ export * from './components';
export * from './hooks';
export * from './filters';
export { entityRouteParams, entityRouteRef } from './routes';
-export * from './testUtils';
export * from './types';
export * from './overridableComponents';
export { getEntityRelations, getEntitySourceLocation } from './utils';
export type { EntitySourceLocation } from './utils';
+export * from './deprecated';
diff --git a/plugins/catalog-react/src/testUtils/index.ts b/plugins/catalog-react/src/testUtils.ts
similarity index 66%
rename from plugins/catalog-react/src/testUtils/index.ts
rename to plugins/catalog-react/src/testUtils.ts
index 2bfec07c81..ffabf4f10c 100644
--- a/plugins/catalog-react/src/testUtils/index.ts
+++ b/plugins/catalog-react/src/testUtils.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 The Backstage Authors
+ * 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.
@@ -13,4 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { MockEntityListContextProvider } from './providers';
+
+/**
+ * Frontend test helpers for the Catalog plugin.
+ *
+ * @packageDocumentation
+ */
+
+export { catalogApiMock } from './testUtils/catalogApiMock';
+export { MockEntityListContextProvider } from './testUtils/MockEntityListContextProvider';
diff --git a/plugins/catalog-react/src/testUtils/providers.tsx b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx
similarity index 95%
rename from plugins/catalog-react/src/testUtils/providers.tsx
rename to plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx
index 9bb46dc6e5..fc01b5847b 100644
--- a/plugins/catalog-react/src/testUtils/providers.tsx
+++ b/plugins/catalog-react/src/testUtils/MockEntityListContextProvider.tsx
@@ -24,9 +24,13 @@ import {
DefaultEntityFilters,
EntityListContext,
EntityListContextProps,
-} from '../hooks/useEntityListProvider';
+} from '@backstage/plugin-catalog-react';
-/** @public */
+/**
+ * Simplifies testing of code that uses the entity list hooks.
+ *
+ * @public
+ */
export function MockEntityListContextProvider<
T extends DefaultEntityFilters = DefaultEntityFilters,
>(
diff --git a/plugins/catalog-react/src/testUtils/catalogApiMock.test.ts b/plugins/catalog-react/src/testUtils/catalogApiMock.test.ts
new file mode 100644
index 0000000000..3501f808f2
--- /dev/null
+++ b/plugins/catalog-react/src/testUtils/catalogApiMock.test.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { Entity } from '@backstage/catalog-model';
+import { catalogApiMock } from './catalogApiMock';
+
+const entity1: Entity = {
+ apiVersion: 'v1',
+ kind: 'CustomKind',
+ metadata: {
+ namespace: 'default',
+ name: 'e1',
+ uid: 'u1',
+ },
+};
+
+const entity2: Entity = {
+ apiVersion: 'v1',
+ kind: 'CustomKind',
+ metadata: {
+ namespace: 'default',
+ name: 'e2',
+ uid: 'u2',
+ },
+};
+
+const entities = [entity1, entity2];
+
+describe('catalogApiMock', () => {
+ it('exports the expected functionality', async () => {
+ const emptyFake = catalogApiMock();
+ const notEmptyFake = catalogApiMock({ entities });
+
+ await expect(emptyFake.getEntities()).resolves.toEqual({ items: [] });
+ await expect(notEmptyFake.getEntities()).resolves.toEqual({
+ items: entities,
+ });
+
+ const mock = catalogApiMock.mock();
+ expect(mock.getEntities).toHaveBeenCalledTimes(0);
+ expect(mock.getEntities()).toBeUndefined();
+ mock.getEntities.mockResolvedValue({ items: entities });
+ await expect(mock.getEntities()).resolves.toEqual({ items: entities });
+
+ const mock2 = catalogApiMock.mock({
+ getEntities: async () => ({ items: [entity1] }),
+ });
+ await expect(mock2.getEntities()).resolves.toEqual({ items: [entity1] });
+ });
+});
diff --git a/plugins/catalog-react/src/testUtils/catalogApiMock.ts b/plugins/catalog-react/src/testUtils/catalogApiMock.ts
new file mode 100644
index 0000000000..8cf3b740b0
--- /dev/null
+++ b/plugins/catalog-react/src/testUtils/catalogApiMock.ts
@@ -0,0 +1,104 @@
+/*
+ * 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 {
+ ApiFactory,
+ ApiRef,
+ createApiFactory,
+} from '@backstage/frontend-plugin-api';
+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';
+
+/** @internal */
+function simpleMock(
+ ref: ApiRef,
+ mockFactory: () => jest.Mocked,
+): (partialImpl?: Partial) => ApiMock {
+ 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;
+ };
+}
+
+/**
+ * Creates a fake catalog client that handles entities in memory storage. Note
+ * that this client may be severely limited in functionality, and advanced
+ * functions may not be available at all.
+ *
+ * @public
+ */
+export function catalogApiMock(options?: { entities?: Entity[] }): CatalogApi {
+ return new InMemoryCatalogClient(options);
+}
+
+/**
+ * A collection of mock functionality for the catalog service.
+ *
+ * @public
+ */
+export namespace catalogApiMock {
+ /**
+ * Creates a fake catalog client that handles entities in memory storage. Note
+ * that this client may be severely limited in functionality, and advanced
+ * functions may not be available at all.
+ */
+ export const factory = (options?: {
+ entities?: Entity[];
+ }): ApiFactory =>
+ createApiFactory({
+ api: catalogApiRef,
+ deps: {},
+ factory: () => new InMemoryCatalogClient(options),
+ });
+ /**
+ * Creates a catalog client whose methods are mock functions, possibly with
+ * some of them overloaded by the caller.
+ */
+ export const mock = simpleMock(catalogApiRef, () => ({
+ getEntities: jest.fn(),
+ getEntitiesByRefs: jest.fn(),
+ queryEntities: jest.fn(),
+ getEntityAncestors: jest.fn(),
+ getEntityByRef: jest.fn(),
+ removeEntityByUid: jest.fn(),
+ refreshEntity: jest.fn(),
+ getEntityFacets: jest.fn(),
+ getLocationById: jest.fn(),
+ getLocationByRef: jest.fn(),
+ addLocation: jest.fn(),
+ removeLocationById: jest.fn(),
+ getLocationByEntity: jest.fn(),
+ validateEntity: jest.fn(),
+ }));
+}
diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
index d6a1fb433a..a169e9139a 100644
--- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
+++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
@@ -21,8 +21,8 @@ import { Entity } from '@backstage/catalog-model';
import {
catalogApiRef,
EntityKindFilter,
- MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { ApiProvider } from '@backstage/core-app-api';
import {
MockErrorApi,
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
index fea2580d2f..5b7f28e26a 100644
--- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
@@ -23,11 +23,11 @@ import { ApiProvider } from '@backstage/core-app-api';
import {
EntityKindFilter,
entityRouteRef,
- MockEntityListContextProvider,
MockStarredEntitiesApi,
starredEntitiesApiRef,
UserListFilter,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { act, fireEvent, screen } from '@testing-library/react';
import * as React from 'react';
diff --git a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx
index be250fddbf..adc69794ae 100644
--- a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.test.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import React, { ReactNode } from 'react';
import { fireEvent, screen, waitFor } from '@testing-library/react';
import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable';
@@ -22,8 +23,8 @@ import {
DefaultEntityFilters,
EntityKindFilter,
EntityListContextProps,
- MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
describe('CursorPaginatedCatalogTable', () => {
const data = new Array(100).fill(0).map((_, index) => {
diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx
index 3c8f51a6ad..aad9b7cb3d 100644
--- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx
+++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.test.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import React, { ReactNode } from 'react';
import { fireEvent, screen } from '@testing-library/react';
import { CatalogTableRow } from './types';
@@ -20,8 +21,8 @@ import { renderInTestApp } from '@backstage/test-utils';
import {
DefaultEntityFilters,
EntityListContextProps,
- MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable';
describe('OffsetPaginatedCatalogTable', () => {
diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts
index 65a8dec226..b7331479ef 100644
--- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts
+++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { CompoundEntityRef, Entity } from '@backstage/catalog-model';
import { useGetEntities } from './useGetEntities';
-import { CatalogApi } from '@backstage/catalog-client';
import { renderHook, waitFor } from '@testing-library/react';
-import { getEntityRelations } from '@backstage/plugin-catalog-react';
+import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
const givenParentGroup = 'team.squad1';
const givenLeafGroup = 'team.squad2';
@@ -31,15 +31,14 @@ const givenUserEntity = {
},
} as Partial as Entity;
-const getEntitiesByRefsMock = jest.fn();
-const catalogApiMock: Pick = {
+const catalogApi = catalogApiMock.mock({
getEntities: jest.fn(async () => Promise.resolve({ items: [] })),
- getEntitiesByRefs: getEntitiesByRefsMock,
-};
+});
-jest.mock('@backstage/core-plugin-api', () => ({
- useApi: jest.fn(() => catalogApiMock),
-}));
+jest.mock('@backstage/core-plugin-api', () => {
+ const actual = jest.requireActual('@backstage/core-plugin-api');
+ return { ...actual, useApi: jest.fn(() => catalogApi) };
+});
const getEntityRelationsMock: jest.Mock<
CompoundEntityRef[],
@@ -50,7 +49,7 @@ jest.mock('@backstage/plugin-catalog-react', () => {
catalogApiRef: {},
getEntityRelations: jest.fn(entity => {
return getEntityRelationsMock(entity);
- }) as typeof getEntityRelations,
+ }) as any,
};
});
@@ -77,16 +76,17 @@ describe('useGetEntities', () => {
};
beforeEach(() => {
- getEntitiesByRefsMock.mockImplementation(async ({ entityRefs: [ref] }) =>
- ref.includes(givenParentGroup)
- ? { items: [givenParentGroupEntity] }
- : { items: [givenLeafGroupEntity] },
+ catalogApi.getEntitiesByRefs.mockImplementation(
+ async ({ entityRefs: [ref] }) =>
+ ref.includes(givenParentGroup)
+ ? { items: [givenParentGroupEntity] }
+ : { items: [givenLeafGroupEntity] },
);
});
afterEach(() => {
getEntityRelationsMock.mockRestore();
- getEntitiesByRefsMock.mockRestore();
+ catalogApi.getEntitiesByRefs.mockRestore();
});
describe('when given entity is a group', () => {
@@ -98,7 +98,7 @@ describe('useGetEntities', () => {
it('should aggregate child ownership', async () => {
await whenHookIsCalledWith(givenParentGroupEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(
`group:default/${givenParentGroup}`,
`group:default/${givenLeafGroup}`,
@@ -108,7 +108,7 @@ describe('useGetEntities', () => {
it('should retrieve child with their relations', async () => {
await whenHookIsCalledWith(givenParentGroupEntity);
- expect(catalogApiMock.getEntitiesByRefs).toHaveBeenCalledWith({
+ expect(catalogApi.getEntitiesByRefs).toHaveBeenCalledWith({
entityRefs: [`group:default/${givenLeafGroup}`],
fields: ['kind', 'metadata.namespace', 'metadata.name', 'relations'],
});
@@ -121,8 +121,8 @@ describe('useGetEntities', () => {
);
beforeEach(() => {
- getEntitiesByRefsMock.mockRestore();
- getEntitiesByRefsMock.mockImplementation(
+ catalogApi.getEntitiesByRefs.mockRestore();
+ catalogApi.getEntitiesByRefs.mockImplementation(
async ({ entityRefs: [ref] }) => {
if (ref.includes(givenParentGroup)) {
return { items: [givenParentGroupEntity] };
@@ -152,7 +152,7 @@ describe('useGetEntities', () => {
});
await whenHookIsCalledWith(givenParentGroupEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(
`group:default/${givenParentGroup}`,
`group:default/${givenIntermediateGroup}`,
@@ -177,7 +177,7 @@ describe('useGetEntities', () => {
});
await whenHookIsCalledWith(givenParentGroupEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(
`group:default/${givenParentGroup}`,
`group:default/${givenIntermediateGroup}`,
@@ -195,7 +195,7 @@ describe('useGetEntities', () => {
]);
await whenHookIsCalledWith(givenUserEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(
`group:default/${givenLeafGroup}`,
`user:default/${givenUser}`,
@@ -219,14 +219,14 @@ describe('useGetEntities', () => {
it('given group entity should return directly owned entities', async () => {
await whenHookIsCalledWith(givenLeafGroupEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(`group:default/${givenLeafGroup}`),
);
});
it('given user entity should return directly owned entities', async () => {
await whenHookIsCalledWith(givenUserEntity);
- expect(catalogApiMock.getEntities).toHaveBeenCalledWith(
+ expect(catalogApi.getEntities).toHaveBeenCalledWith(
ownersFilter(`user:default/${givenUser}`),
);
});
@@ -255,7 +255,7 @@ describe('useGetEntities', () => {
? manyGroups.map(group => createGroupRefFromName(group.metadata.name))
: [],
);
- (catalogApiMock.getEntities as jest.Mock).mockClear();
+ catalogApi.getEntities.mockClear();
});
it('should handle 500+ relations without exceeding URL length limits', async () => {
@@ -270,14 +270,13 @@ describe('useGetEntities', () => {
timeout: 5000,
});
- const callArgs = (catalogApiMock.getEntities as jest.Mock).mock
- .calls[0][0];
+ const callArgs = catalogApi.getEntities.mock.calls[0][0];
expect(
- callArgs.filter[0]['relations.ownedBy'].length,
+ (callArgs!.filter as any)[0]['relations.ownedBy'].length,
).toBeLessThanOrEqual(100);
- const owners = callArgs.filter[0]['relations.ownedBy'];
+ const owners = (callArgs!.filter as any)[0]['relations.ownedBy'];
expect(Array.isArray(owners)).toBeTruthy();
expect(owners.length).toBeLessThanOrEqual(100);
@@ -309,7 +308,7 @@ describe('useGetEntities', () => {
)
: [],
);
- (catalogApiMock.getEntities as jest.Mock).mockClear();
+ catalogApi.getEntities.mockClear();
});
it('should batch the request to avoid exceeding header size limits', async () => {
@@ -323,8 +322,7 @@ describe('useGetEntities', () => {
await waitFor(() => expect(result.current.loading).toBe(false), {
timeout: 5000,
});
- const callArgs = (catalogApiMock.getEntities as jest.Mock).mock
- .calls[0][0];
+ const callArgs = catalogApi.getEntities.mock.calls[0][0];
const url = new URL(
`http://localhost/api/catalog/entities?${new URLSearchParams(
@@ -334,7 +332,7 @@ describe('useGetEntities', () => {
const headerSize = url.href.length;
expect(headerSize).toBeLessThanOrEqual(16384);
- const owners = callArgs.filter[0]['relations.ownedBy'];
+ const owners = (callArgs!.filter as any)[0]['relations.ownedBy'];
expect(Array.isArray(owners)).toBeTruthy();
expect(owners.length).toBeLessThanOrEqual(100);
});
diff --git a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
index 3a2aa1469f..49f5c1feb0 100644
--- a/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
+++ b/plugins/scaffolder-react/src/next/components/Workflow/Workflow.test.tsx
@@ -25,7 +25,8 @@ import React from 'react';
import { Workflow } from './Workflow';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import { ScaffolderApi, scaffolderApiRef } from '../../../api';
-import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
+import { catalogApiRef } from '@backstage/plugin-catalog-react';
+import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
const scaffolderApiMock: jest.Mocked = {
cancelTask: jest.fn(),
@@ -38,14 +39,13 @@ const scaffolderApiMock: jest.Mocked = {
listTasks: jest.fn(),
autocomplete: jest.fn(),
};
-const catalogApiMock: jest.Mocked = {
- getEntityByRef: jest.fn(),
-} as any;
+
+const catalogApi = catalogApiMock.mock();
const analyticsMock = new MockAnalyticsApi();
const apis = TestApiRegistry.from(
[scaffolderApiRef, scaffolderApiMock],
- [catalogApiRef, catalogApiMock],
+ [catalogApiRef, catalogApi],
[analyticsApiRef, analyticsMock],
);
diff --git a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx
index 689a96b95d..750efd70d0 100644
--- a/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx
+++ b/plugins/scaffolder/src/alpha/components/TemplateWizardPage/TemplateWizardPage.test.tsx
@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import { ApiProvider } from '@backstage/core-app-api';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
@@ -30,8 +31,8 @@ import {
import { TemplateWizardPage } from './TemplateWizardPage';
import { rootRouteRef } from '../../../routes';
import { ANNOTATION_EDIT_URL } from '@backstage/catalog-model';
-import { CatalogApi } from '@backstage/catalog-client';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
+import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
jest.mock('react-router-dom', () => {
return {
@@ -54,16 +55,14 @@ const scaffolderApiMock: jest.Mocked = {
autocomplete: jest.fn(),
};
-const catalogApiMock: jest.Mocked = {
- getEntityByRef: jest.fn(),
-} as any;
+const catalogApi = catalogApiMock.mock();
const analyticsMock = new MockAnalyticsApi();
const apis = TestApiRegistry.from(
[scaffolderApiRef, scaffolderApiMock],
- [catalogApiRef, catalogApiMock],
+ [catalogApiRef, catalogApi],
[analyticsApiRef, analyticsMock],
- [catalogApiRef, catalogApiMock],
+ [catalogApiRef, catalogApi],
);
const entityRefResponse = {
@@ -100,7 +99,7 @@ describe('TemplateWizardPage', () => {
],
title: 'React JSON Schema Form Test',
});
- catalogApiMock.getEntityByRef.mockResolvedValue(entityRefResponse);
+ catalogApi.getEntityByRef.mockResolvedValue(entityRefResponse);
const { findByRole, getByRole } = await renderInTestApp(
@@ -147,7 +146,7 @@ describe('TemplateWizardPage', () => {
});
describe('scaffolder page context menu', () => {
it('should render if editUrl is set to url', async () => {
- catalogApiMock.getEntityByRef.mockResolvedValue({
+ catalogApi.getEntityByRef.mockResolvedValue({
apiVersion: 'v1',
kind: 'service',
metadata: {
@@ -177,7 +176,7 @@ describe('TemplateWizardPage', () => {
expect(queryByTestId('menu-button')).toBeInTheDocument();
});
it('should not render if editUrl is undefined', async () => {
- catalogApiMock.getEntityByRef.mockResolvedValue({
+ catalogApi.getEntityByRef.mockResolvedValue({
apiVersion: 'v1',
kind: 'service',
metadata: {
diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
index 4c4b88e471..e7b12f42c0 100644
--- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
+++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
@@ -22,8 +22,8 @@ import { TemplateTypePicker } from './TemplateTypePicker';
import {
catalogApiRef,
EntityKindFilter,
- MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
import { ApiProvider } from '@backstage/core-app-api';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx
index acaf01b318..1e65efb587 100644
--- a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx
+++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx
@@ -24,9 +24,9 @@ import {
CatalogApi,
catalogApiRef,
starredEntitiesApiRef,
- MockEntityListContextProvider,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
+import { MockEntityListContextProvider } from '@backstage/plugin-catalog-react/testUtils';
import {
MockStorageApi,
renderInTestApp,
diff --git a/yarn.lock b/yarn.lock
index f5184e9149..cd65c170d6 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4674,6 +4674,7 @@ __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