Introduce a new StarredEntitiesApi that is used in the useStarredEntities hook
Signed-off-by: Dominik Henneke <dominik.henneke@sda-se.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': minor
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Introduce a new `StarredEntitiesApi` that is used in the `useStarredEntities` hook.
|
||||
The `@backstage/plugin-catalog` installs a default implementation that is backed by the `StorageApi`, but one can also override the `starredEntitiesApiRef`.
|
||||
@@ -14,13 +14,17 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import { EntityProvider } from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { cicdContent } from './EntityPage';
|
||||
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { EntityLayout } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
DefaultStarredEntitiesApi,
|
||||
EntityProvider,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import React from 'react';
|
||||
import { cicdContent } from './EntityPage';
|
||||
|
||||
describe('EntityPage Test', () => {
|
||||
const entity = {
|
||||
@@ -48,7 +52,10 @@ describe('EntityPage Test', () => {
|
||||
downloadJobLogsForWorkflowRun: jest.fn(),
|
||||
} as jest.Mocked<typeof githubActionsApiRef.T>;
|
||||
|
||||
const apis = ApiRegistry.with(githubActionsApiRef, mockedApi);
|
||||
const apis = ApiRegistry.with(githubActionsApiRef, mockedApi).with(
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
);
|
||||
|
||||
describe('cicdContent', () => {
|
||||
it('Should render GitHub Actions View', async () => {
|
||||
|
||||
@@ -15,30 +15,31 @@
|
||||
*/
|
||||
|
||||
import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { apiDocsConfigRef } from '../../config';
|
||||
import { ApiExplorerPage } from './ApiExplorerPage';
|
||||
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
ConfigReader,
|
||||
} from '@backstage/core-app-api';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import {
|
||||
storageApiRef,
|
||||
ConfigApi,
|
||||
configApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import DashboardIcon from '@material-ui/icons/Dashboard';
|
||||
import { CatalogTableRow } from '@backstage/plugin-catalog';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { starredEntitiesApiRef } from '@backstage/plugin-catalog-react/src/apis';
|
||||
import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
|
||||
import DashboardIcon from '@material-ui/icons/Dashboard';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { apiDocsConfigRef } from '../../config';
|
||||
import { ApiExplorerPage } from './ApiExplorerPage';
|
||||
|
||||
describe('ApiCatalogPage', () => {
|
||||
const catalogApi: Partial<CatalogApi> = {
|
||||
@@ -82,6 +83,8 @@ describe('ApiCatalogPage', () => {
|
||||
getApiDefinitionWidget: () => undefined,
|
||||
};
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
render(
|
||||
wrapInTestApp(
|
||||
@@ -89,7 +92,11 @@ describe('ApiCatalogPage', () => {
|
||||
apis={ApiRegistry.from([
|
||||
[catalogApiRef, catalogApi],
|
||||
[configApiRef, configApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[storageApiRef, storageApi],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi }),
|
||||
],
|
||||
[apiDocsConfigRef, apiDocsConfig],
|
||||
])}
|
||||
>
|
||||
|
||||
@@ -16,11 +16,13 @@ import { Entity } from '@backstage/catalog-model';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { IconButton } from '@material-ui/core';
|
||||
import { LinkProps } from '@backstage/core-components';
|
||||
import { Observable } from '@backstage/core-plugin-api';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { default as React_2 } from 'react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RouteRef } from '@backstage/core-plugin-api';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { SystemEntity } from '@backstage/catalog-model';
|
||||
import { TableColumn } from '@backstage/core-components';
|
||||
import { UserEntity } from '@backstage/catalog-model';
|
||||
@@ -131,6 +133,23 @@ export type DefaultEntityFilters = {
|
||||
text?: EntityTextFilter;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "DefaultStarredEntitiesApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
constructor(opts: { storageApi: StorageApi });
|
||||
// (undocumented)
|
||||
isStarred(entity: Entity): boolean;
|
||||
// (undocumented)
|
||||
star(entity: Entity): Promise<void>;
|
||||
// (undocumented)
|
||||
starredEntities$(): Observable<StarredEntitiesApiObservable>;
|
||||
// (undocumented)
|
||||
toggleStarred(entity: Entity): Promise<void>;
|
||||
// (undocumented)
|
||||
unstar(entity: Entity): Promise<void>;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "EntityLoadingStatus" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public @deprecated (undocumented)
|
||||
@@ -742,6 +761,27 @@ export function reduceEntityFilters(
|
||||
// @public (undocumented)
|
||||
export const rootRoute: RouteRef<undefined>;
|
||||
|
||||
// @public
|
||||
export interface StarredEntitiesApi {
|
||||
star(entity: Entity): Promise<void>;
|
||||
starredEntities$(): Observable<StarredEntitiesApiObservable>;
|
||||
toggleStarred(entity: Entity): Promise<void>;
|
||||
unstar(entity: Entity): Promise<void>;
|
||||
}
|
||||
|
||||
// Warning: (ae-missing-release-tag) "StarredEntitiesApiObservable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type StarredEntitiesApiObservable = {
|
||||
starredEntities: Set<string>;
|
||||
isStarred: (entity: Entity) => boolean;
|
||||
};
|
||||
|
||||
// Warning: (ae-missing-release-tag) "starredEntitiesApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export const starredEntitiesApiRef: ApiRef<StarredEntitiesApi>;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "UnregisterEntityDialog" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-use": "^17.2.4"
|
||||
"react-use": "^17.2.4",
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.7.15",
|
||||
@@ -56,6 +57,7 @@
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/jwt-decode": "^3.1.0",
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"react-test-renderer": "^16.13.1"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright 2021 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 { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
|
||||
describe('DefaultStarredEntitiesApi', () => {
|
||||
let mockStorage: StorageApi;
|
||||
let starredEntitiesApi: DefaultStarredEntitiesApi;
|
||||
|
||||
const mockEntity: Entity = {
|
||||
apiVersion: '1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
name: 'mock',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockStorage = MockStorageApi.create();
|
||||
starredEntitiesApi = new DefaultStarredEntitiesApi({
|
||||
storageApi: mockStorage,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('toggleStarred', () => {
|
||||
it('should star unstarred entity', async () => {
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
|
||||
await starredEntitiesApi.toggleStarred(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
});
|
||||
|
||||
it('should unstar starred entity', async () => {
|
||||
const bucket = mockStorage.forBucket('settings');
|
||||
await bucket.set('starredEntities', ['entity:Component:default:mock']);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
|
||||
await starredEntitiesApi.toggleStarred(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('star', () => {
|
||||
it('should star unstarred entity', async () => {
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
|
||||
await starredEntitiesApi.star(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
});
|
||||
|
||||
it('should keep starred entity', async () => {
|
||||
const bucket = mockStorage.forBucket('settings');
|
||||
await bucket.set('starredEntities', ['entity:Component:default:mock']);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
|
||||
await starredEntitiesApi.star(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unstar', () => {
|
||||
it('should unstar starred entity', async () => {
|
||||
const bucket = mockStorage.forBucket('settings');
|
||||
await bucket.set('starredEntities', ['entity:Component:default:mock']);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(true);
|
||||
|
||||
await starredEntitiesApi.unstar(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
});
|
||||
|
||||
it('should keep unstarred entity', async () => {
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
|
||||
await starredEntitiesApi.unstar(mockEntity);
|
||||
|
||||
expect(starredEntitiesApi.isStarred(mockEntity)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('starredEntities$', () => {
|
||||
const handler = jest.fn();
|
||||
|
||||
beforeEach(async () => {
|
||||
await new Promise<void>(resolve => {
|
||||
starredEntitiesApi.starredEntities$().subscribe({
|
||||
next: (...args) => {
|
||||
handler(...args);
|
||||
|
||||
if (handler.mock.calls.length >= 2) {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const bucket = mockStorage.forBucket('settings');
|
||||
bucket.set('starredEntities', ['entity:Component:default:mock']).then();
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive updates', async () => {
|
||||
expect(handler).toBeCalledTimes(2);
|
||||
expect(handler).toBeCalledWith({
|
||||
starredEntities: new Set(),
|
||||
isStarred: expect.any(Function),
|
||||
});
|
||||
expect(handler).toBeCalledWith({
|
||||
starredEntities: new Set(['entity:Component:default:mock']),
|
||||
isStarred: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should receive isStarred function that operates on the latest state', async () => {
|
||||
expect(handler.mock.calls[0][0].isStarred(mockEntity)).toBe(true);
|
||||
expect(handler.mock.calls[1][0].isStarred(mockEntity)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2021 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 { Observable, StorageApi } from '@backstage/core-plugin-api';
|
||||
import ObservableImpl from 'zen-observable';
|
||||
import {
|
||||
StarredEntitiesApi,
|
||||
StarredEntitiesApiObservable,
|
||||
} from './StarredEntitiesApi';
|
||||
|
||||
const buildEntityKey = (component: Entity) =>
|
||||
`entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${
|
||||
component.metadata.name
|
||||
}`;
|
||||
|
||||
export class DefaultStarredEntitiesApi implements StarredEntitiesApi {
|
||||
private readonly settingsStore: StorageApi;
|
||||
private starredEntities: Set<string>;
|
||||
|
||||
constructor(opts: { storageApi: StorageApi }) {
|
||||
this.settingsStore = opts.storageApi.forBucket('settings');
|
||||
|
||||
this.starredEntities = new Set(
|
||||
this.settingsStore.get<string[]>('starredEntities') ?? [],
|
||||
);
|
||||
|
||||
this.settingsStore.observe$<string[]>('starredEntities').subscribe({
|
||||
next: next => {
|
||||
this.starredEntities = new Set(next.newValue ?? []);
|
||||
this.notifyChanges();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async toggleStarred(entity: Entity): Promise<void> {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
|
||||
if (this.starredEntities.has(entityKey)) {
|
||||
await this.unstar(entity);
|
||||
} else {
|
||||
await this.star(entity);
|
||||
}
|
||||
}
|
||||
|
||||
async star(entity: Entity): Promise<void> {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
|
||||
this.starredEntities.add(entityKey);
|
||||
|
||||
await this.settingsStore.set(
|
||||
'starredEntities',
|
||||
Array.from(this.starredEntities),
|
||||
);
|
||||
}
|
||||
|
||||
async unstar(entity: Entity): Promise<void> {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
|
||||
this.starredEntities.delete(entityKey);
|
||||
|
||||
await this.settingsStore.set(
|
||||
'starredEntities',
|
||||
Array.from(this.starredEntities),
|
||||
);
|
||||
}
|
||||
|
||||
starredEntities$(): Observable<StarredEntitiesApiObservable> {
|
||||
return this.observable;
|
||||
}
|
||||
|
||||
isStarred(entity: Entity): boolean {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
return this.starredEntities.has(entityKey);
|
||||
}
|
||||
|
||||
private readonly subscribers = new Set<
|
||||
ZenObservable.SubscriptionObserver<StarredEntitiesApiObservable>
|
||||
>();
|
||||
|
||||
private readonly observable =
|
||||
new ObservableImpl<StarredEntitiesApiObservable>(subscriber => {
|
||||
// forward the the latest value
|
||||
subscriber.next({
|
||||
starredEntities: this.starredEntities,
|
||||
isStarred: e => this.isStarred(e),
|
||||
});
|
||||
|
||||
this.subscribers.add(subscriber);
|
||||
return () => {
|
||||
this.subscribers.delete(subscriber);
|
||||
};
|
||||
});
|
||||
|
||||
private notifyChanges() {
|
||||
for (const subscription of this.subscribers) {
|
||||
subscription.next({
|
||||
starredEntities: this.starredEntities,
|
||||
isStarred: e => this.isStarred(e),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright 2021 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 { ApiRef, createApiRef, Observable } from '@backstage/core-plugin-api';
|
||||
|
||||
export const starredEntitiesApiRef: ApiRef<StarredEntitiesApi> = createApiRef({
|
||||
id: 'catalog-react.starred-entities',
|
||||
});
|
||||
|
||||
export type StarredEntitiesApiObservable = {
|
||||
/**
|
||||
* A set of starred entities
|
||||
*/
|
||||
starredEntities: Set<string>;
|
||||
|
||||
/**
|
||||
* A function to check if an entity is starred.
|
||||
*
|
||||
* @param entity - the entity to check
|
||||
* @returns true, if the entity is starred.
|
||||
*/
|
||||
isStarred: (entity: Entity) => boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* An API to store and retrieve starred entities
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface StarredEntitiesApi {
|
||||
/**
|
||||
* Toggle the star state of the entity
|
||||
*
|
||||
* @param entity - the entity to be toggled
|
||||
*/
|
||||
toggleStarred(entity: Entity): Promise<void>;
|
||||
|
||||
/**
|
||||
* Star the entity
|
||||
*
|
||||
* @param entity - the entity to be starred
|
||||
*/
|
||||
star(entity: Entity): Promise<void>;
|
||||
|
||||
/**
|
||||
* Unstar the entity
|
||||
*
|
||||
* @param entity - the entity to be unstarred
|
||||
*/
|
||||
unstar(entity: Entity): Promise<void>;
|
||||
|
||||
/**
|
||||
* Observe the state of starred entities and receive a handler
|
||||
* to check the star state of an entity.
|
||||
*/
|
||||
starredEntities$(): Observable<StarredEntitiesApiObservable>;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export { DefaultStarredEntitiesApi } from './DefaultStarredEntitiesApi';
|
||||
export { starredEntitiesApiRef } from './StarredEntitiesApi';
|
||||
export type {
|
||||
StarredEntitiesApi,
|
||||
StarredEntitiesApiObservable,
|
||||
} from './StarredEntitiesApi';
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
export * from './StarredEntitiesApi';
|
||||
@@ -14,21 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import qs from 'qs';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
EntityListProvider,
|
||||
useEntityListProvider,
|
||||
} from './useEntityListProvider';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { UserListFilterKind } from '../types';
|
||||
import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters';
|
||||
import { EntityKindPicker, UserListPicker } from '../components';
|
||||
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import {
|
||||
ConfigApi,
|
||||
@@ -37,6 +24,19 @@ import {
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import qs from 'qs';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { catalogApiRef } from '../api';
|
||||
import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
|
||||
import { EntityKindPicker, UserListPicker } from '../components';
|
||||
import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters';
|
||||
import { UserListFilterKind } from '../types';
|
||||
import {
|
||||
EntityListProvider,
|
||||
useEntityListProvider,
|
||||
} from './useEntityListProvider';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
@@ -81,6 +81,10 @@ const apis = ApiRegistry.from([
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[identityApiRef, mockIdentityApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
],
|
||||
]);
|
||||
|
||||
const wrapper = ({
|
||||
|
||||
@@ -14,16 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { useStarredEntities } from './useStarredEntities';
|
||||
import { storageApiRef, StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockErrorApi } from '@backstage/test-utils';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry, WebStorage } from '@backstage/core-app-api';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { StorageApi } from '@backstage/core-plugin-api';
|
||||
import { MockStorageApi } from '@backstage/test-utils';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
|
||||
import { useStarredEntities } from './useStarredEntities';
|
||||
|
||||
describe('useStarredEntities', () => {
|
||||
let mockStorage: StorageApi | undefined;
|
||||
let mockStorage: StorageApi;
|
||||
let wrapper: React.ComponentType;
|
||||
|
||||
const mockEntity: Entity = {
|
||||
apiVersion: '1',
|
||||
@@ -42,41 +44,56 @@ describe('useStarredEntities', () => {
|
||||
},
|
||||
};
|
||||
|
||||
const wrapper = ({ children }: PropsWithChildren<{}>) => {
|
||||
return (
|
||||
<ApiProvider apis={ApiRegistry.with(storageApiRef, mockStorage)}>
|
||||
beforeEach(() => {
|
||||
mockStorage = MockStorageApi.create();
|
||||
wrapper = ({ children }: PropsWithChildren<{}>) => (
|
||||
<ApiProvider
|
||||
apis={ApiRegistry.with(
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: mockStorage }),
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</ApiProvider>
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockStorage = new WebStorage('@backstage', new MockErrorApi()).forBucket(
|
||||
Date.now().toString(), // TODO(blam): need something that changes every test run for now until the MockStorage is implemented
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an empty set for when there is no items in storage', async () => {
|
||||
const { result } = renderHook(() => useStarredEntities(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.starredEntities.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should return a set with the current items when there are items in storage', async () => {
|
||||
const expectedIds = ['i', 'am', 'some', 'test', 'ids'];
|
||||
const store = mockStorage?.forBucket('settings');
|
||||
await store?.set('starredEntities', expectedIds);
|
||||
|
||||
const { result } = renderHook(() => useStarredEntities(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
for (const item of expectedIds) {
|
||||
expect(result.current.starredEntities.has(item)).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it('should listen to changes when the storage is set elsewhere', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.starredEntities.size).toBe(0);
|
||||
expect(result.current.isStarredEntity(mockEntity)).toBeFalsy();
|
||||
|
||||
@@ -91,18 +108,26 @@ describe('useStarredEntities', () => {
|
||||
});
|
||||
|
||||
it('should write new entries to the local store when adding a toggling entity', async () => {
|
||||
const { result } = renderHook(() => useStarredEntities(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.toggleStarredEntity(mockEntity);
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.isStarredEntity(mockEntity)).toBeTruthy();
|
||||
expect(result.current.isStarredEntity(secondMockEntity)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should remove an existing entity when toggling entries', async () => {
|
||||
const { result } = renderHook(() => useStarredEntities(), { wrapper });
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useStarredEntities(),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.toggleStarredEntity(mockEntity);
|
||||
@@ -110,6 +135,8 @@ describe('useStarredEntities', () => {
|
||||
result.current.toggleStarredEntity(mockEntity);
|
||||
});
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current.isStarredEntity(mockEntity)).toBeFalsy();
|
||||
expect(result.current.isStarredEntity(secondMockEntity)).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -15,56 +15,24 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { storageApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { useCallback } from 'react';
|
||||
import { useObservable } from 'react-use';
|
||||
|
||||
const buildEntityKey = (component: Entity) =>
|
||||
`entity:${component.kind}:${component.metadata.namespace ?? 'default'}:${
|
||||
component.metadata.name
|
||||
}`;
|
||||
import { starredEntitiesApiRef } from '../apis';
|
||||
|
||||
export const useStarredEntities = () => {
|
||||
const storageApi = useApi(storageApiRef);
|
||||
const settingsStore = storageApi.forBucket('settings');
|
||||
const rawStarredEntityKeys =
|
||||
settingsStore.get<string[]>('starredEntities') ?? [];
|
||||
const starredEntitiesApi = useApi(starredEntitiesApiRef);
|
||||
|
||||
const [starredEntities, setStarredEntities] = useState(
|
||||
new Set(rawStarredEntityKeys),
|
||||
const { starredEntities, isStarred: isStarredEntity } = useObservable(
|
||||
starredEntitiesApi.starredEntities$(),
|
||||
{ starredEntities: new Set<string>(), isStarred: _ => false },
|
||||
);
|
||||
|
||||
const observedItems = useObservable(
|
||||
settingsStore.observe$<string[]>('starredEntities'),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (observedItems?.newValue) {
|
||||
const currentValue = observedItems?.newValue ?? [];
|
||||
setStarredEntities(new Set(currentValue));
|
||||
}
|
||||
}, [observedItems?.newValue]);
|
||||
|
||||
const toggleStarredEntity = useCallback(
|
||||
(entity: Entity) => {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
if (starredEntities.has(entityKey)) {
|
||||
starredEntities.delete(entityKey);
|
||||
} else {
|
||||
starredEntities.add(entityKey);
|
||||
}
|
||||
|
||||
settingsStore.set('starredEntities', Array.from(starredEntities));
|
||||
starredEntitiesApi.toggleStarred(entity).then();
|
||||
},
|
||||
[starredEntities, settingsStore],
|
||||
);
|
||||
|
||||
const isStarredEntity = useCallback(
|
||||
(entity: Entity) => {
|
||||
const entityKey = buildEntityKey(entity);
|
||||
return starredEntities.has(entityKey);
|
||||
},
|
||||
[starredEntities],
|
||||
[starredEntitiesApi],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
export type { CatalogApi } from '@backstage/catalog-client';
|
||||
export { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
|
||||
export { catalogApiRef } from './api';
|
||||
export * from './apis';
|
||||
export * from './components';
|
||||
export * from './hooks';
|
||||
export * from './filters';
|
||||
|
||||
@@ -20,28 +20,32 @@ import {
|
||||
RELATION_MEMBER_OF,
|
||||
RELATION_OWNED_BY,
|
||||
} from '@backstage/catalog-model';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
MockStorageApi,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
mockBreakpoint,
|
||||
} from '@backstage/test-utils';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { EntityRow } from '../CatalogTable/types';
|
||||
import { CatalogPage } from './CatalogPage';
|
||||
import DashboardIcon from '@material-ui/icons/Dashboard';
|
||||
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { TableColumn, TableProps } from '@backstage/core-components';
|
||||
import {
|
||||
IdentityApi,
|
||||
identityApiRef,
|
||||
ProfileInfo,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import {
|
||||
mockBreakpoint,
|
||||
MockStorageApi,
|
||||
renderWithEffects,
|
||||
wrapInTestApp,
|
||||
} from '@backstage/test-utils';
|
||||
import DashboardIcon from '@material-ui/icons/Dashboard';
|
||||
import { fireEvent, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { createComponentRouteRef } from '../../routes';
|
||||
import { EntityRow } from '../CatalogTable';
|
||||
import { CatalogPage } from './CatalogPage';
|
||||
|
||||
describe('CatalogPage', () => {
|
||||
const origReplaceState = window.history.replaceState;
|
||||
@@ -120,6 +124,7 @@ describe('CatalogPage', () => {
|
||||
getIdToken: async () => undefined,
|
||||
getProfile: () => testProfile,
|
||||
};
|
||||
const storageApi = MockStorageApi.create();
|
||||
|
||||
const renderWrapped = (children: React.ReactNode) =>
|
||||
renderWithEffects(
|
||||
@@ -128,7 +133,11 @@ describe('CatalogPage', () => {
|
||||
apis={ApiRegistry.from([
|
||||
[catalogApiRef, catalogApi],
|
||||
[identityApiRef, identityApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[storageApiRef, storageApi],
|
||||
[
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi }),
|
||||
],
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -15,19 +15,22 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
EDIT_URL_ANNOTATION,
|
||||
Entity,
|
||||
VIEW_URL_ANNOTATION,
|
||||
EDIT_URL_ANNOTATION,
|
||||
} from '@backstage/catalog-model';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import * as React from 'react';
|
||||
import { CatalogTable } from './CatalogTable';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import {
|
||||
entityRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
MockEntityListContextProvider,
|
||||
starredEntitiesApiRef,
|
||||
UserListFilter,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { act, fireEvent } from '@testing-library/react';
|
||||
import * as React from 'react';
|
||||
import { CatalogTable } from './CatalogTable';
|
||||
|
||||
const entities: Entity[] = [
|
||||
{
|
||||
@@ -48,6 +51,11 @@ const entities: Entity[] = [
|
||||
];
|
||||
|
||||
describe('CatalogTable component', () => {
|
||||
const mockApis = ApiRegistry.with(
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
window.open = jest.fn();
|
||||
});
|
||||
@@ -58,9 +66,11 @@ describe('CatalogTable component', () => {
|
||||
|
||||
it('should render error message', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<MockEntityListContextProvider value={{ error: new Error('error') }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>,
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider value={{ error: new Error('error') }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
@@ -75,20 +85,22 @@ describe('CatalogTable component', () => {
|
||||
|
||||
it('should display entity names when loading has finished and no error occurred', async () => {
|
||||
const rendered = await renderInTestApp(
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
entities,
|
||||
filters: {
|
||||
user: new UserListFilter(
|
||||
'owned',
|
||||
() => false,
|
||||
() => false,
|
||||
),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>,
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider
|
||||
value={{
|
||||
entities,
|
||||
filters: {
|
||||
user: new UserListFilter(
|
||||
'owned',
|
||||
() => false,
|
||||
() => false,
|
||||
),
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
@@ -112,9 +124,11 @@ describe('CatalogTable component', () => {
|
||||
};
|
||||
|
||||
const { getByTitle } = await renderInTestApp(
|
||||
<MockEntityListContextProvider value={{ entities: [entity] }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>,
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider value={{ entities: [entity] }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
@@ -142,9 +156,11 @@ describe('CatalogTable component', () => {
|
||||
};
|
||||
|
||||
const { getByTitle } = await renderInTestApp(
|
||||
<MockEntityListContextProvider value={{ entities: [entity] }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>,
|
||||
<ApiProvider apis={mockApis}>
|
||||
<MockEntityListContextProvider value={{ entities: [entity] }}>
|
||||
<CatalogTable />
|
||||
</MockEntityListContextProvider>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/catalog/:namespace/:kind/:name': entityRouteRef,
|
||||
|
||||
@@ -13,24 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CatalogApi } from '@backstage/catalog-client';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
catalogApiRef,
|
||||
EntityProvider,
|
||||
AsyncEntityProvider,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
EntityProvider,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { fireEvent } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { EntityLayout } from './EntityLayout';
|
||||
|
||||
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
|
||||
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
|
||||
|
||||
const mockEntity = {
|
||||
kind: 'MyKind',
|
||||
metadata: {
|
||||
@@ -38,10 +40,12 @@ const mockEntity = {
|
||||
},
|
||||
} as Entity;
|
||||
|
||||
const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi).with(
|
||||
alertApiRef,
|
||||
{} as AlertApi,
|
||||
);
|
||||
const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi)
|
||||
.with(alertApiRef, {} as AlertApi)
|
||||
.with(
|
||||
starredEntitiesApiRef,
|
||||
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
|
||||
);
|
||||
|
||||
describe('EntityLayout', () => {
|
||||
it('renders simplest case', async () => {
|
||||
|
||||
@@ -18,7 +18,9 @@ import { CatalogClient } from '@backstage/catalog-client';
|
||||
import {
|
||||
catalogApiRef,
|
||||
catalogRouteRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
entityRouteRef,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { CatalogClientWrapper } from './CatalogClientWrapper';
|
||||
import { createComponentRouteRef, viewTechDocRouteRef } from './routes';
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
createRoutableExtension,
|
||||
discoveryApiRef,
|
||||
identityApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
export const catalogPlugin = createPlugin({
|
||||
@@ -43,6 +46,13 @@ export const catalogPlugin = createPlugin({
|
||||
identityApi,
|
||||
}),
|
||||
}),
|
||||
|
||||
createApiFactory({
|
||||
api: starredEntitiesApiRef,
|
||||
deps: { storageApi: storageApiRef },
|
||||
factory: ({ storageApi }) =>
|
||||
new DefaultStarredEntitiesApi({ storageApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
catalogIndex: catalogRouteRef,
|
||||
|
||||
@@ -14,12 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { DefaultTechDocsHome } from './DefaultTechDocsHome';
|
||||
|
||||
import {
|
||||
ApiProvider,
|
||||
ApiRegistry,
|
||||
@@ -30,7 +24,17 @@ import {
|
||||
configApiRef,
|
||||
storageApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import {
|
||||
CatalogApi,
|
||||
catalogApiRef,
|
||||
DefaultStarredEntitiesApi,
|
||||
starredEntitiesApiRef,
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { rootDocsRouteRef } from '../../routes';
|
||||
import { DefaultTechDocsHome } from './DefaultTechDocsHome';
|
||||
|
||||
jest.mock('@backstage/plugin-catalog-react', () => {
|
||||
const actual = jest.requireActual('@backstage/plugin-catalog-react');
|
||||
@@ -63,10 +67,13 @@ describe('TechDocs Home', () => {
|
||||
},
|
||||
});
|
||||
|
||||
const storageApi = MockStorageApi.create();
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[catalogApiRef, mockCatalogApi],
|
||||
[configApiRef, configApi],
|
||||
[storageApiRef, MockStorageApi.create()],
|
||||
[starredEntitiesApiRef, new DefaultStarredEntitiesApi({ storageApi })],
|
||||
]);
|
||||
|
||||
it('should render a TechDocs home page', async () => {
|
||||
|
||||
Reference in New Issue
Block a user