catalog-react: added MockStarredEntitiesApi + usage

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-02-28 14:29:23 +01:00
parent c077b432b8
commit 303063a655
16 changed files with 168 additions and 105 deletions
+8
View File
@@ -476,6 +476,14 @@ export const MockEntityListContextProvider: ({
value?: Partial<EntityListContextProps<DefaultEntityFilters>> | undefined;
}>) => JSX.Element;
// @public
export class MockStarredEntitiesApi implements StarredEntitiesApi {
// (undocumented)
starredEntitie$(): Observable<Set<string>>;
// (undocumented)
toggleStarred(entityRef: string): Promise<void>;
}
// @public @deprecated (undocumented)
export function reduceCatalogFilters(
filters: EntityFilter[],
@@ -0,0 +1,61 @@
/*
* 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 { MockStarredEntitiesApi } from './MockStarredEntitiesApi';
describe('MockStarredEntitiesApi', () => {
it('should toggle starred entities', async () => {
const api = new MockStarredEntitiesApi();
const updates1 = new Array<Set<string>>();
const sub1 = api
.starredEntitie$()
.subscribe(entities => updates1.push(entities));
api.toggleStarred('k:ns/e1');
api.toggleStarred('k:ns/e2');
await Promise.resolve();
expect(updates1).toEqual([
new Set(),
new Set(['k:ns/e1']),
new Set(['k:ns/e1', 'k:ns/e2']),
]);
const updates2 = new Array<Set<string>>();
const sub2 = api
.starredEntitie$()
.subscribe(entities => updates2.push(entities));
api.toggleStarred('k:ns/e2');
sub1.unsubscribe();
api.toggleStarred('k:ns/e2');
await Promise.resolve();
expect(updates1).toEqual([
new Set(),
new Set(['k:ns/e1']),
new Set(['k:ns/e1', 'k:ns/e2']),
new Set(['k:ns/e1']),
]);
expect(updates2).toEqual([
new Set(['k:ns/e1', 'k:ns/e2']),
new Set(['k:ns/e1']),
new Set(['k:ns/e1', 'k:ns/e2']),
]);
sub2.unsubscribe();
});
});
@@ -0,0 +1,54 @@
/*
* 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 { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
import { StarredEntitiesApi } from './StarredEntitiesApi';
/**
* An in-memory mock implementation of the StarredEntitiesApi.
*
* @public
*/
export class MockStarredEntitiesApi implements StarredEntitiesApi {
private readonly starredEntities = new Set<string>();
private readonly subscribers = new Set<
ZenObservable.SubscriptionObserver<Set<string>>
>();
private readonly observable = new ObservableImpl<Set<string>>(subscriber => {
subscriber.next(new Set(this.starredEntities));
this.subscribers.add(subscriber);
return () => {
this.subscribers.delete(subscriber);
};
});
async toggleStarred(entityRef: string): Promise<void> {
if (!this.starredEntities.delete(entityRef)) {
this.starredEntities.add(entityRef);
}
for (const subscription of this.subscribers) {
subscription.next(new Set(this.starredEntities));
}
}
starredEntitie$(): Observable<Set<string>> {
return this.observable;
}
}
@@ -16,3 +16,4 @@
export { starredEntitiesApiRef } from './StarredEntitiesApi';
export type { StarredEntitiesApi } from './StarredEntitiesApi';
export { MockStarredEntitiesApi } from './MockStarredEntitiesApi';
@@ -23,14 +23,13 @@ import {
identityApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import qs from 'qs';
import React, { PropsWithChildren } from 'react';
import { MemoryRouter } from 'react-router';
import { catalogApiRef } from '../api';
import { starredEntitiesApiRef } from '../apis';
import { starredEntitiesApiRef, MockStarredEntitiesApi } from '../apis';
import { EntityKindPicker, UserListPicker } from '../components';
import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters';
import { UserListFilterKind } from '../types';
@@ -96,12 +95,7 @@ const wrapper = ({
[catalogApiRef, mockCatalogApi],
[identityApiRef, mockIdentityApi],
[storageApiRef, MockStorageApi.create()],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: MockStorageApi.create(),
}),
],
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
]}
>
<EntityListProvider>
@@ -15,16 +15,18 @@
*/
import { Entity } from '@backstage/catalog-model';
import { StorageApi } from '@backstage/core-plugin-api';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
import { TestApiProvider } from '@backstage/test-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import React, { PropsWithChildren } from 'react';
import { starredEntitiesApiRef } from '../apis';
import {
starredEntitiesApiRef,
StarredEntitiesApi,
MockStarredEntitiesApi,
} from '../apis';
import { useStarredEntities } from './useStarredEntities';
describe('useStarredEntities', () => {
let mockStorage: StorageApi;
let mockApi: StarredEntitiesApi;
let wrapper: React.ComponentType;
const mockEntity: Entity = {
@@ -45,22 +47,15 @@ describe('useStarredEntities', () => {
};
beforeEach(() => {
mockStorage = MockStorageApi.create();
mockApi = new MockStarredEntitiesApi();
wrapper = ({ children }: PropsWithChildren<{}>) => (
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: mockStorage }),
],
]}
>
<TestApiProvider apis={[[starredEntitiesApiRef, mockApi]]}>
{children}
</TestApiProvider>
);
});
it('should return an empty set for when there is no items in storage', async () => {
it('should return an empty set', async () => {
const { result, waitForNextUpdate } = renderHook(
() => useStarredEntities(),
{ wrapper },
@@ -71,10 +66,11 @@ describe('useStarredEntities', () => {
expect(result.current.starredEntities.size).toBe(0);
});
it('should return a set with the current items when there are items in storage', async () => {
it('should return a set with the current items', async () => {
const expectedIds = ['i', 'am', 'some', 'test', 'ids'];
const store = mockStorage?.forBucket('starredEntities');
await store?.set('entityRefs', expectedIds);
for (const id of expectedIds) {
mockApi.toggleStarred(id);
}
const { result, waitForNextUpdate } = renderHook(
() => useStarredEntities(),
@@ -31,6 +31,7 @@ import {
catalogApiRef,
entityRouteRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
import {
mockBreakpoint,
@@ -42,7 +43,6 @@ import {
import DashboardIcon from '@material-ui/icons/Dashboard';
import { fireEvent, screen } from '@testing-library/react';
import React from 'react';
import { DefaultStarredEntitiesApi } from '../../apis';
import { createComponentRouteRef } from '../../routes';
import { CatalogTableRow } from '../CatalogTable';
import { DefaultCatalogPage } from './DefaultCatalogPage';
@@ -141,10 +141,7 @@ describe('DefaultCatalogPage', () => {
[catalogApiRef, catalogApi],
[identityApiRef, identityApi],
[storageApiRef, storageApi],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi }),
],
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
]}
>
{children}
@@ -25,15 +25,11 @@ import {
MockEntityListContextProvider,
starredEntitiesApiRef,
UserListFilter,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
import {
MockStorageApi,
renderInTestApp,
TestApiRegistry,
} from '@backstage/test-utils';
import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { act, fireEvent } from '@testing-library/react';
import * as React from 'react';
import { DefaultStarredEntitiesApi } from '../../apis';
import { CatalogTable } from './CatalogTable';
const entities: Entity[] = [
@@ -57,7 +53,7 @@ const entities: Entity[] = [
describe('CatalogTable component', () => {
const mockApis = TestApiRegistry.from([
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
new MockStarredEntitiesApi(),
]);
beforeEach(() => {
@@ -24,18 +24,17 @@ import {
EntityProvider,
entityRouteRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
import { permissionApiRef } from '@backstage/plugin-permission-react';
import {
MockPermissionApi,
MockStorageApi,
renderInTestApp,
TestApiRegistry,
} from '@backstage/test-utils';
import { act, fireEvent } from '@testing-library/react';
import React from 'react';
import { Route, Routes } from 'react-router';
import { DefaultStarredEntitiesApi } from '../../apis';
import { EntityLayout } from './EntityLayout';
const mockEntity = {
@@ -48,10 +47,7 @@ const mockEntity = {
const mockApis = TestApiRegistry.from(
[catalogApiRef, {} as CatalogApi],
[alertApiRef, {} as AlertApi],
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
],
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
[permissionApiRef, new MockPermissionApi()],
);
@@ -13,40 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
renderInTestApp,
TestApiProvider,
MockStorageApi,
} from '@backstage/test-utils';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import {
starredEntitiesApiRef,
MockStarredEntitiesApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import React from 'react';
import { Content } from './Content';
describe('StarredEntitiesContent', () => {
it('should render list of tools', async () => {
const mockStorageApi = MockStorageApi.create();
await mockStorageApi
.forBucket('starredEntities')
.set('entityRefs', [
'component:default/mock-starred-entity',
'component:default/mock-starred-entity-2',
]);
const mockedApi = new MockStarredEntitiesApi();
mockedApi.toggleStarred('component:default/mock-starred-entity');
mockedApi.toggleStarred('component:default/mock-starred-entity-2');
const { getByText } = await renderInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockStorageApi,
}),
],
]}
>
<TestApiProvider apis={[[starredEntitiesApiRef, mockedApi]]}>
<Content />
</TestApiProvider>,
{
@@ -22,9 +22,9 @@ import {
} from '@backstage/test-utils';
import {
starredEntitiesApiRef,
MockStarredEntitiesApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import { Grid } from '@material-ui/core';
import React, { ComponentType } from 'react';
@@ -44,14 +44,7 @@ export default {
(Story: ComponentType<{}>) =>
wrapInTestApp(
<TestApiProvider
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockStorageApi,
}),
],
]}
apis={[[starredEntitiesApiRef, new MockStarredEntitiesApi()]]}
>
<Story />
</TestApiProvider>,
@@ -25,9 +25,9 @@ import { wrapInTestApp, TestApiProvider, MockStorageApi} from '@backstage/test-u
import { Content, Page, InfoCard } from '@backstage/core-components';
import {
starredEntitiesApiRef,
MockStarredEntitiesApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import {
HomePageSearchBar,
SearchContextProvider,
@@ -57,9 +57,7 @@ export default {
apis={[
[
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({
storageApi: mockStorageApi,
}),
new MockStarredEntitiesApi(),
],
[searchApiRef, { query: () => Promise.resolve({ results: [] }) }],
]}
+5 -11
View File
@@ -20,19 +20,13 @@ import { scmIntegrationsApiRef } from '@backstage/integration-react';
import {
catalogApiRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
import React from 'react';
import { scaffolderApiRef, ScaffolderClient } from '../src';
import { ScaffolderPage } from '../src/plugin';
import {
discoveryApiRef,
fetchApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import {
CatalogEntityPage,
DefaultStarredEntitiesApi,
} from '@backstage/plugin-catalog';
import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api';
import { CatalogEntityPage } from '@backstage/plugin-catalog';
createDevApp()
.addPage({
@@ -46,8 +40,8 @@ createDevApp()
})
.registerApi({
api: starredEntitiesApiRef,
deps: { storageApi: storageApiRef },
factory: ({ storageApi }) => new DefaultStarredEntitiesApi({ storageApi }),
deps: {},
factory: () => new MockStarredEntitiesApi(),
})
.registerApi({
api: scaffolderApiRef,
@@ -20,11 +20,11 @@ import {
configApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import { DefaultStarredEntitiesApi } from '@backstage/plugin-catalog';
import {
CatalogApi,
catalogApiRef,
starredEntitiesApiRef,
MockStarredEntitiesApi,
} from '@backstage/plugin-catalog-react';
import {
MockStorageApi,
@@ -73,7 +73,7 @@ describe('TechDocs Home', () => {
[catalogApiRef, mockCatalogApi],
[configApiRef, configApi],
[storageApiRef, storageApi],
[starredEntitiesApiRef, new DefaultStarredEntitiesApi({ storageApi })],
[starredEntitiesApiRef, new MockStarredEntitiesApi()],
);
it('should render a TechDocs home page', async () => {