diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx new file mode 100644 index 0000000000..9e73ed1475 --- /dev/null +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -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 }) => , + }); + + 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 }) => ( + + ), + }); + + 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 }) => ( + + ), + }); + + 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 }) => ( + + ), + }); + + 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 }) => ( + + ), + }); + + 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 }) => ( + + ), + }); + + 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 }) => ( + + ), + }); + + expect(result.current.entity).toBe(entity); + expect(result.current.loading).toBe(false); + expect(result.current.error).toBe(undefined); + expect(result.current.refresh).toBe(undefined); + }); +}); diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index 4734039a4f..1f438f19a0 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -24,7 +24,6 @@ import React, { ReactNode, useEffect, createContext, - useContext, Provider, Context, } from 'react'; @@ -51,6 +50,7 @@ export const EntityContext: Context = 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 ( @@ -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; +const CompatibilityProvider = ({ + value, + children, +}: { + value: EntityLoadingStatus; + children: ReactNode; +}) => { + return ; +}; +EntityContext.Provider = CompatibilityProvider as Provider; export const useEntityFromUrl = (): EntityLoadingStatus => { const { kind, namespace, name } = useEntityCompoundName(); @@ -157,20 +167,12 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { export function useEntity() { 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'); }