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