From 00958737363f80d957a73ae3533bd260f0172597 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 9 Mar 2021 11:16:10 +0100 Subject: [PATCH] core-api: migrate AppContext to use version bridge Co-authored-by: Juan Lulkin Signed-off-by: Patrik Oldsberg --- packages/core-api/package.json | 1 + packages/core-api/src/app/AppContext.test.tsx | 79 +++++++++++++++++++ packages/core-api/src/app/AppContext.tsx | 41 +++++++--- 3 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 packages/core-api/src/app/AppContext.test.tsx diff --git a/packages/core-api/package.json b/packages/core-api/package.json index cdc21264d3..dbeb2021df 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -47,6 +47,7 @@ "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", + "@testing-library/react-hooks": "^3.3.0", "@testing-library/user-event": "^12.0.7", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", diff --git a/packages/core-api/src/app/AppContext.test.tsx b/packages/core-api/src/app/AppContext.test.tsx new file mode 100644 index 0000000000..55d98c18c0 --- /dev/null +++ b/packages/core-api/src/app/AppContext.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2020 Spotify AB + * + * 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, { useContext, Context } from 'react'; +import { renderHook } from '@testing-library/react-hooks'; +import { VersionedValue } from '../lib/versionedValues'; +import { getGlobalSingleton } from '../lib/globalObject'; +import { AppContext as AppContextV1 } from './types'; +import { AppContextProvider } from './AppContext'; + +describe('v1 consumer', () => { + const AppContext = getGlobalSingleton< + Context> + >('app-context'); + + function useMockAppV1(): AppContextV1 { + const impl = useContext(AppContext)?.atVersion(1); + if (!impl) { + throw new Error('no impl'); + } + return impl; + } + + it('should provide an app context', () => { + const mockContext: AppContextV1 = { + getComponents: jest.fn(), + getSystemIcon: jest.fn(), + getPlugins: jest.fn(), + getProvider: jest.fn(), + getRouter: jest.fn(), + getRoutes: jest.fn(), + }; + + const renderedHook = renderHook(() => useMockAppV1(), { + wrapper: ({ children }) => ( + + ), + }); + const result = renderedHook.result.current; + + expect(mockContext.getComponents).toHaveBeenCalledTimes(0); + result.getComponents(); + expect(mockContext.getComponents).toHaveBeenCalledTimes(1); + + expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(0); + result.getSystemIcon('icon'); + expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(1); + expect(mockContext.getSystemIcon).toHaveBeenCalledWith('icon'); + + expect(mockContext.getPlugins).toHaveBeenCalledTimes(0); + result.getPlugins(); + expect(mockContext.getPlugins).toHaveBeenCalledTimes(1); + + expect(mockContext.getProvider).toHaveBeenCalledTimes(0); + result.getProvider(); + expect(mockContext.getProvider).toHaveBeenCalledTimes(1); + + expect(mockContext.getRouter).toHaveBeenCalledTimes(0); + result.getRouter(); + expect(mockContext.getRouter).toHaveBeenCalledTimes(1); + + expect(mockContext.getRoutes).toHaveBeenCalledTimes(0); + result.getRoutes(); + expect(mockContext.getRoutes).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/core-api/src/app/AppContext.tsx b/packages/core-api/src/app/AppContext.tsx index c7c405a959..7b73d71c6b 100644 --- a/packages/core-api/src/app/AppContext.tsx +++ b/packages/core-api/src/app/AppContext.tsx @@ -14,26 +14,47 @@ * limitations under the License. */ -import React, { createContext, PropsWithChildren, useContext } from 'react'; -import { AppContext } from './types'; +import React, { + createContext, + PropsWithChildren, + useContext, + Context, +} from 'react'; +import { + VersionedValue, + createVersionedValueMap, +} from '../lib/versionedValues'; +import { getGlobalSingleton, setGlobalSingleton } from '../lib/globalObject'; +import { AppContext as AppContextV1 } from './types'; -const Context = createContext(undefined); +type AppContextType = VersionedValue<{ 1: AppContextV1 }> | undefined; +const AppContext = createContext(undefined); + +setGlobalSingleton('app-context', AppContext); type Props = { - appContext: AppContext; + appContext: AppContextV1; }; export const AppContextProvider = ({ appContext, children, -}: PropsWithChildren) => ( - -); +}: PropsWithChildren) => { + const versionedValue = createVersionedValueMap({ 1: appContext }); -export const useApp = (): AppContext => { - const appContext = useContext(Context); - if (!appContext) { + return ; +}; + +export const useApp = (): AppContextV1 => { + const versionedContext = useContext( + getGlobalSingleton>('app-context'), + ); + if (!versionedContext) { throw new Error('No app context available'); } + const appContext = versionedContext.atVersion(1); + if (!appContext) { + throw new Error('AppContext v1 not available'); + } return appContext; };