catalog-react: add tests for useEntity + simplify and fix

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-09-11 14:53:19 +02:00
parent 18826333a7
commit 3f26865c67
2 changed files with 154 additions and 12 deletions
@@ -0,0 +1,140 @@
/*
* 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 React from 'react';
import { renderHook } from '@testing-library/react-hooks';
import {
useEntity,
EntityProvider,
AsyncEntityProvider,
EntityContext,
} from './useEntity';
import { Entity } from '@backstage/catalog-model';
describe('EntityProvider', () => {
it('should provide no entity', async () => {
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => <EntityProvider children={children} />,
});
expect(result.current.entity).toBe(undefined);
expect(result.current.loading).toBe(true);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(undefined);
});
it('should provide an entity', async () => {
const entity = { kind: 'MyEntity' } as Entity;
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<EntityProvider entity={entity} children={children} />
),
});
expect(result.current.entity).toBe(entity);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(undefined);
});
});
describe('AsyncEntityProvider', () => {
it('should provide no entity', async () => {
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<AsyncEntityProvider loading={false} children={children} />
),
});
expect(result.current.entity).toBe(undefined);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(undefined);
});
it('should provide an entity', async () => {
const entity = { kind: 'MyEntity' } as Entity;
const refresh = () => {};
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<AsyncEntityProvider
loading={false}
entity={entity}
refresh={refresh}
children={children}
/>
),
});
expect(result.current.entity).toBe(entity);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(refresh);
});
it('should provide an error', async () => {
const error = new Error('oh no');
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<AsyncEntityProvider
loading={false}
error={error}
children={children}
/>
),
});
expect(result.current.entity).toBe(undefined);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(error);
expect(result.current.refresh).toBe(undefined);
});
});
describe('EntityContext.Provider', () => {
it('should provide no entity', async () => {
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<EntityContext.Provider
value={{ loading: false }}
children={children}
/>
),
});
expect(result.current.entity).toBe(undefined);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(undefined);
});
it('should provide an entity', async () => {
const entity = { kind: 'MyEntity' } as Entity;
const { result } = renderHook(() => useEntity(), {
wrapper: ({ children }) => (
<EntityContext.Provider
value={{ entity, loading: false }}
children={children}
/>
),
});
expect(result.current.entity).toBe(entity);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBe(undefined);
expect(result.current.refresh).toBe(undefined);
});
});
+14 -12
View File
@@ -24,7 +24,6 @@ import React, {
ReactNode,
useEffect,
createContext,
useContext,
Provider,
Context,
} from 'react';
@@ -51,6 +50,7 @@ export const EntityContext: Context<EntityLoadingStatus> =
error: undefined,
refresh: () => {},
});
// We grab this for use in the new provider, since we're overriding it later on
const OldEntityProvider = EntityContext.Provider;
// This context has support for multiple concurrent versions of this package.
@@ -85,6 +85,8 @@ export const AsyncEntityProvider = ({
refresh,
}: AsyncEntityProviderProps) => {
const value = { entity, loading, error, refresh };
// We provide both the old and the new context, since
// consumers might be doing things like `useContext(EntityContext)`
return (
<OldEntityProvider value={value}>
<NewEntityContext.Provider value={createVersionedValueMap({ 1: value })}>
@@ -120,8 +122,16 @@ export const EntityProvider = ({ entity, children }: EntityProviderProps) => (
);
// This is used for forwards compatibility with the new entity context
EntityContext.Provider =
EntityProvider as unknown as Provider<EntityLoadingStatus>;
const CompatibilityProvider = ({
value,
children,
}: {
value: EntityLoadingStatus;
children: ReactNode;
}) => {
return <AsyncEntityProvider {...value} children={children} />;
};
EntityContext.Provider = CompatibilityProvider as Provider<EntityLoadingStatus>;
export const useEntityFromUrl = (): EntityLoadingStatus => {
const { kind, namespace, name } = useEntityCompoundName();
@@ -157,20 +167,12 @@ export const useEntityFromUrl = (): EntityLoadingStatus => {
export function useEntity<T extends Entity = Entity>() {
const versionedHolder =
useVersionedContext<{ 1: EntityLoadingStatus }>('entity-context');
const oldContextValue = useContext(EntityContext);
if (!versionedHolder) {
const { entity, loading, error, refresh } = oldContextValue;
return { entity: entity as T, loading, error, refresh };
// TODO(Rugvip): Throw this once we fully migrate to the new context
// throw new Error(
// 'Component can not be used outside of the context of an Entity',
// );
throw new Error('Entity context is not available');
}
const value = versionedHolder.atVersion(1);
if (!value) {
throw new Error('EntityContext v1 not available');
}