core-api: migrate ApiContext to use version bridge

Co-authored-by: Juan Lulkin <jmaiz@spotify.com>
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2021-03-08 18:10:40 +01:00
parent 1f3321fbfc
commit d5c8bc8a01
2 changed files with 63 additions and 11 deletions
@@ -14,12 +14,15 @@
* limitations under the License.
*/
import React from 'react';
import React, { Context, useContext } from 'react';
import { ApiProvider, useApi, withApis } from './ApiProvider';
import { createApiRef } from './ApiRef';
import { ApiRegistry } from './ApiRegistry';
import { render } from '@testing-library/react';
import { withLogCollector } from '@backstage/test-utils-core';
import { getGlobalSingleton } from '../../lib/globalObject';
import { ApiHolder, ApiRef } from './types';
import { VersionedValue } from '../../lib/versionedValues';
describe('ApiProvider', () => {
type Api = () => string;
@@ -175,3 +178,35 @@ describe('ApiProvider', () => {
]);
});
});
describe('v1 consumer', () => {
const ApiContext = getGlobalSingleton<
Context<VersionedValue<{ 1: ApiHolder }>>
>('api-context');
function useMockApiV1<T>(apiRef: ApiRef<T>): T {
const impl = useContext(ApiContext)?.atVersion(1)?.get(apiRef);
if (!impl) {
throw new Error('no impl');
}
return impl;
}
type Api = () => string;
const apiRef = createApiRef<Api>({ id: 'x', description: '' });
const registry = ApiRegistry.from([[apiRef, () => 'hello']]);
const MyHookConsumerV1 = () => {
const api = useMockApiV1(apiRef);
return <p>hook message: {api()}</p>;
};
it('should provide apis', () => {
const renderedHook = render(
<ApiProvider apis={registry}>
<MyHookConsumerV1 />
</ApiProvider>,
);
renderedHook.getByText('hook message: hello');
});
});
@@ -19,10 +19,16 @@ import React, {
useContext,
ReactNode,
PropsWithChildren,
Context,
} from 'react';
import PropTypes from 'prop-types';
import { ApiRef, ApiHolder, TypesToApiRefs } from './types';
import { ApiAggregator } from './ApiAggregator';
import { getGlobalSingleton, setGlobalSingleton } from '../../lib/globalObject';
import {
VersionedValue,
createVersionedValueMap,
} from '../../lib/versionedValues';
const missingHolderMessage =
'No ApiProvider available in react context. ' +
@@ -35,16 +41,24 @@ type ApiProviderProps = {
children: ReactNode;
};
const Context = createContext<ApiHolder | undefined>(undefined);
type ApiContextType = VersionedValue<{ 1: ApiHolder }> | undefined;
const ApiContext = createContext<ApiContextType>(undefined);
setGlobalSingleton('api-context', ApiContext);
export const ApiProvider = ({
apis,
children,
}: PropsWithChildren<ApiProviderProps>) => {
const parentHolder = useContext(Context);
const parentHolder = useContext(ApiContext)?.atVersion(1);
const holder = parentHolder ? new ApiAggregator(apis, parentHolder) : apis;
return <Context.Provider value={holder} children={children} />;
return (
<ApiContext.Provider
value={createVersionedValueMap({ 1: holder })}
children={children}
/>
);
};
ApiProvider.propTypes = {
@@ -53,12 +67,19 @@ ApiProvider.propTypes = {
};
export function useApiHolder(): ApiHolder {
const apiHolder = useContext(Context);
const versionedHolder = useContext(
getGlobalSingleton<Context<ApiContextType>>('api-context'),
);
if (!apiHolder) {
if (!versionedHolder) {
throw new Error(missingHolderMessage);
}
const apiHolder = versionedHolder.atVersion(1);
if (!apiHolder) {
throw new Error('ApiContext v1 not available');
}
return apiHolder;
}
@@ -77,11 +98,7 @@ export function withApis<T>(apis: TypesToApiRefs<T>) {
WrappedComponent: React.ComponentType<P>,
) {
const Hoc = (props: PropsWithChildren<Omit<P, keyof T>>) => {
const apiHolder = useContext(Context);
if (!apiHolder) {
throw new Error(missingHolderMessage);
}
const apiHolder = useApiHolder();
const impls = {} as T;