diff --git a/.changeset/serious-steaks-promise.md b/.changeset/serious-steaks-promise.md index cd37f4ef3e..55fee86f60 100644 --- a/.changeset/serious-steaks-promise.md +++ b/.changeset/serious-steaks-promise.md @@ -1,5 +1,6 @@ --- '@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-addons-test-utils': patch --- Use the new generic refresh user cookie provider. diff --git a/plugins/auth-react/api-report.md b/plugins/auth-react/api-report.md index f329673996..0e8a7dc8b7 100644 --- a/plugins/auth-react/api-report.md +++ b/plugins/auth-react/api-report.md @@ -3,27 +3,22 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { ApiRef } from '@backstage/core-plugin-api'; import { AsyncState } from '@react-hookz/web'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { UseAsyncActions } from '@react-hookz/web'; // @public -export type AuthApi = { - getCookie(): Promise<{ - expiresAt: string; - }>; +export type CookieAuthRefreshOptions = { + pluginId: string; + path?: string; }; // @public -export function CookieAuthRefreshProvider({ - apiRef, +export function CookieAuthRefreshProvider({ children, -}: { - apiRef: ApiRef; - children: ReactNode; -}): + ...rest +}: CookieAuthRefreshProviderProps): | string | number | boolean @@ -33,22 +28,16 @@ export function CookieAuthRefreshProvider({ | undefined; // @public -export function useCookieAuthRefresh({ - apiRef, -}: { - apiRef: ApiRef; -}): { - state: AsyncState< - | { - expiresAt: string; - } - | undefined - >; - actions: UseAsyncActions< - { - expiresAt: string; - }, - [] - >; +export type CookieAuthRefreshProviderProps = CookieAuthRefreshOptions & { + children: ReactNode; +}; + +// @public +export function useCookieAuthRefresh({ + pluginId, + path, +}: CookieAuthRefreshOptions): { + state: AsyncState; + actions: UseAsyncActions; }; ``` diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 2bfb842800..7bc248be67 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -34,8 +34,10 @@ "dependencies": { "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@material-ui/core": "^4.9.13", - "@react-hookz/web": "^24.0.4" + "@react-hookz/web": "^24.0.4", + "@types/react": "^16.13.1 || ^17.0.0 || ^18.0.0" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx index 6684fa7856..e8e5eea24d 100644 --- a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx +++ b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.test.tsx @@ -19,10 +19,15 @@ import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CookieAuthRefreshProvider } from './CookieAuthRefreshProvider'; import { TestApiProvider, renderInTestApp } from '@backstage/test-utils'; -import { createApiRef } from '@backstage/core-plugin-api'; -import { AuthApi } from '../../types'; +import { discoveryApiRef, fetchApiRef } from '@backstage/core-plugin-api'; describe('CookieAuthRefreshProvider', () => { + const discoveryApiMock = { + getBaseUrl: jest + .fn() + .mockResolvedValue('http://localhost:7000/techdocs/api'), + }; + function getExpiresAtInFuture() { const tenMinutesInMilliseconds = 10 * 60 * 1000; return new Date(Date.now() + tenMinutesInMilliseconds).toISOString(); @@ -35,14 +40,18 @@ describe('CookieAuthRefreshProvider', () => { })); it('should render a progress bar', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockReturnValue(new Promise(() => {})), + const fetchApiMock = { + fetch: jest.fn().mockReturnValue(new Promise(() => {})), }; await renderInTestApp( - - + +
Test Content
, @@ -51,16 +60,20 @@ describe('CookieAuthRefreshProvider', () => { expect(screen.getByTestId('progress')).toBeInTheDocument(); }); - it('should render a error panel', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); + it('should render an error panel', async () => { const error = new Error('Failed to get cookie'); - const apiMock = { - getCookie: jest.fn().mockRejectedValue(error), + const fetchApiMock = { + fetch: jest.fn().mockRejectedValue(error), }; await renderInTestApp( - - + +
Test Content
, @@ -70,29 +83,46 @@ describe('CookieAuthRefreshProvider', () => { }); it('should call the api again when retry is clicked', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); const error = new Error('Failed to get cookie'); - const apiMock = { - getCookie: jest.fn().mockRejectedValueOnce(error).mockResolvedValue({ - expiresAt: getExpiresAtInFuture(), - }), + const fetchApiMock = { + fetch: jest + .fn() + .mockRejectedValueOnce(error) + .mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ + expiresAt: getExpiresAtInFuture(), + }), + }), }; await renderInTestApp( - - + +
Test Content
, ); - expect(apiMock.getCookie).toHaveBeenCalledTimes(1); + await waitFor(() => + expect(fetchApiMock.fetch).toHaveBeenCalledWith( + 'http://localhost:7000/techdocs/api/cookie', + { credentials: 'include' }, + ), + ); + + expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1); expect(screen.getByText(error.message)).toBeInTheDocument(); await userEvent.click(screen.getByText('Retry')); - expect(apiMock.getCookie).toHaveBeenCalledTimes(2); + expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2); await waitFor(() => expect(screen.getByText('Test Content')).toBeInTheDocument(), @@ -100,18 +130,23 @@ describe('CookieAuthRefreshProvider', () => { }); it('should render the children', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ - expiresAt: { + const fetchApiMock = { + fetch: jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ expiresAt: getExpiresAtInFuture(), - }, + }), }), }; await renderInTestApp( - - + +
Test Content
, diff --git a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.tsx b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.tsx index 86fa5a3a6f..4265b25cf0 100644 --- a/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.tsx +++ b/plugins/auth-react/src/components/CookieAuthRefreshProvider/CookieAuthRefreshProvider.tsx @@ -16,27 +16,31 @@ import React, { ReactNode } from 'react'; import { ErrorPanel } from '@backstage/core-components'; -import { ApiRef, useApp } from '@backstage/core-plugin-api'; +import { useApp } from '@backstage/core-plugin-api'; import { Button } from '@material-ui/core'; -import { useCookieAuthRefresh } from '../../hooks'; -import { AuthApi } from '../../types'; +import { useCookieAuthRefresh, CookieAuthRefreshOptions } from '../../hooks'; + +/** + * @public + * Props for the {@link CookieAuthRefreshProvider} component. + */ +export type CookieAuthRefreshProviderProps = CookieAuthRefreshOptions & { + // The children to render when the refresh is successful + children: ReactNode; +}; /** * @public * A provider that will refresh the cookie when it is about to expire. - * It receives an `apiRef` and `children` as props, and expects that apiRef extends the `AuthApi` interface. */ -export function CookieAuthRefreshProvider({ - apiRef, +export function CookieAuthRefreshProvider({ children, -}: { - apiRef: ApiRef; - children: ReactNode; -}) { + ...rest +}: CookieAuthRefreshProviderProps) { const app = useApp(); const { Progress } = app.getComponents(); - const { state, actions } = useCookieAuthRefresh({ apiRef }); + const { state, actions } = useCookieAuthRefresh(rest); if (state.status === 'error' && state.error) { return ( diff --git a/plugins/auth-react/src/components/CookieAuthRefreshProvider/index.ts b/plugins/auth-react/src/components/CookieAuthRefreshProvider/index.ts index 968fa5c4b4..2bb172d850 100644 --- a/plugins/auth-react/src/components/CookieAuthRefreshProvider/index.ts +++ b/plugins/auth-react/src/components/CookieAuthRefreshProvider/index.ts @@ -14,4 +14,7 @@ * limitations under the License. */ -export { CookieAuthRefreshProvider } from './CookieAuthRefreshProvider'; +export { + CookieAuthRefreshProvider, + type CookieAuthRefreshProviderProps, +} from './CookieAuthRefreshProvider'; diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/index.ts b/plugins/auth-react/src/hooks/useCookieAuthRefresh/index.ts index 42b60840f8..43b1882959 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/index.ts +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/index.ts @@ -14,4 +14,7 @@ * limitations under the License. */ -export { useCookieAuthRefresh } from './useCookieAuthRefresh'; +export { + useCookieAuthRefresh, + type CookieAuthRefreshOptions, +} from './useCookieAuthRefresh'; diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx index 7a3efd5fd0..42fe6f4fca 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx @@ -16,17 +16,29 @@ import React from 'react'; import { renderHook, waitFor } from '@testing-library/react'; -import { createApiRef } from '@backstage/core-plugin-api'; +import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api'; import { TestApiProvider } from '@backstage/test-utils'; import { useCookieAuthRefresh } from './useCookieAuthRefresh'; -import { AuthApi } from '../../types'; describe('useCookieAuthRefresh', () => { + const discoveryApiMock = { + getBaseUrl: jest + .fn() + .mockResolvedValue('http://localhost:7000/techdocs/api'), + }; + const now = 1710316886171; const tenMinutesInMilliseconds = 10 * 60 * 1000; const tenMinutesFromNowInMilliseconds = now + tenMinutesInMilliseconds; const expiresAt = new Date(tenMinutesFromNowInMilliseconds).toISOString(); + const fetchApiMock = { + fetch: jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ expiresAt }), + }), + }; + type Listener = (event: { data: any }) => void; let listeners: Listener[]; @@ -34,6 +46,7 @@ describe('useCookieAuthRefresh', () => { beforeEach(() => { jest.useFakeTimers({ now }); + jest.clearAllMocks(); listeners = []; channelMock = { postMessage: jest.fn((message: any) => { @@ -58,32 +71,53 @@ describe('useCookieAuthRefresh', () => { }); it('should return a loading status when the refresh is in progress', () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn(), - }; - - const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), { - wrapper: ({ children }) => ( - {children} - ), - }); + const { result } = renderHook( + () => useCookieAuthRefresh({ pluginId: 'techdocs' }), + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); expect(result.current.state.status).toBe('loading'); }); it('should return an error status when the refresh has failed', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); const error = new Error('Failed to get cookie'); - const apiMock = { - getCookie: jest.fn().mockRejectedValue(error), - }; - const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), { - wrapper: ({ children }) => ( - {children} - ), - }); + const { result } = renderHook( + () => useCookieAuthRefresh({ pluginId: 'techdocs' }), + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); await waitFor(() => expect(result.current.state.status).toBe('error')); @@ -91,38 +125,48 @@ describe('useCookieAuthRefresh', () => { }); it('should call the api to get the cookie and use it', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ expiresAt }), - }; - - const { result } = renderHook(() => useCookieAuthRefresh({ apiRef }), { - wrapper: ({ children }) => ( - {children} - ), - }); - - expect(apiMock.getCookie).toHaveBeenCalled(); + const { result } = renderHook( + () => useCookieAuthRefresh({ pluginId: 'techdocs' }), + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); await waitFor(() => - expect(result.current.state.result).toMatchObject({ expiresAt }), + expect(fetchApiMock.fetch).toHaveBeenCalledWith( + 'http://localhost:7000/techdocs/api/cookie', + { credentials: 'include' }, + ), ); + + expect(result.current.state.result).toMatchObject({ expiresAt }); }); it('should send a message to other tabs when the cookie is refreshed', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ expiresAt }), - }; - - renderHook(() => useCookieAuthRefresh({ apiRef }), { + renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), { wrapper: ({ children }) => ( - {children} + + {children} + ), }); expect(global.BroadcastChannel).toHaveBeenCalledWith( - 'auth-test-auth-cookie-channel', + 'techdocs-auth-cookie-channel', ); await waitFor(() => @@ -139,14 +183,16 @@ describe('useCookieAuthRefresh', () => { }); it('should cancel the refresh when a message is received from another tab', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ expiresAt }), - }; - - renderHook(() => useCookieAuthRefresh({ apiRef }), { + renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), { wrapper: ({ children }) => ( - {children} + + {children} + ), }); @@ -169,37 +215,35 @@ describe('useCookieAuthRefresh', () => { jest.advanceTimersByTime(tenMinutesInMilliseconds); // should not call the api - expect(apiMock.getCookie).toHaveBeenCalledTimes(1); + expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1); // advance the timers in more 10 minutes to match the new expires at jest.advanceTimersByTime(tenMinutesInMilliseconds); // should call the api - await waitFor(() => expect(apiMock.getCookie).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2)); }); it('should cancel the refresh when the component is unmounted', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ expiresAt }), - }; - const { result, unmount } = renderHook( - () => useCookieAuthRefresh({ apiRef }), + () => useCookieAuthRefresh({ pluginId: 'techdocs' }), { wrapper: ({ children }) => ( - + {children} ), }, ); - expect(apiMock.getCookie).toHaveBeenCalledTimes(1); + await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1)); - await waitFor(() => - expect(result.current.state.result).toMatchObject({ expiresAt }), - ); + expect(result.current.state.result).toMatchObject({ expiresAt }); unmount(); @@ -213,27 +257,31 @@ describe('useCookieAuthRefresh', () => { jest.advanceTimersByTime(tenMinutesInMilliseconds); // should not call the api after unmount - await waitFor(() => expect(apiMock.getCookie).not.toHaveBeenCalledTimes(2)); + await waitFor(() => + expect(fetchApiMock.fetch).not.toHaveBeenCalledTimes(2), + ); }); it('should refresh the cookie when it is about to expire', async () => { - const apiRef = createApiRef({ id: 'auth-test' }); - const apiMock = { - getCookie: jest.fn().mockResolvedValue({ expiresAt }), - }; - - renderHook(() => useCookieAuthRefresh({ apiRef }), { + renderHook(() => useCookieAuthRefresh({ pluginId: 'techdocs' }), { wrapper: ({ children }) => ( - {children} + + {children} + ), }); - expect(apiMock.getCookie).toHaveBeenCalledTimes(1); + await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(1)); // advance the timers to the expiration date jest.advanceTimersByTime(tenMinutesInMilliseconds); // should call the api - await waitFor(() => expect(apiMock.getCookie).toHaveBeenCalledTimes(2)); + await waitFor(() => expect(fetchApiMock.fetch).toHaveBeenCalledTimes(2)); }); }); diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx index 5c3272d056..2d495d54c4 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx @@ -15,9 +15,13 @@ */ import { useEffect, useState, useCallback } from 'react'; -import { ApiRef, useApi } from '@backstage/core-plugin-api'; +import { + discoveryApiRef, + fetchApiRef, + useApi, +} from '@backstage/core-plugin-api'; import { useAsync, useMountEffect } from '@react-hookz/web'; -import { AuthApi } from '../../types'; +import { ResponseError } from '@backstage/errors'; type CookieAuthRefreshMessage = MessageEvent<{ action: string; @@ -28,22 +32,43 @@ type CookieAuthRefreshMessage = MessageEvent<{ /** * @public - * It receives an `apiRef` as options, and expects that apiRef extends the `AuthApi` interface. + * The options for the {@link useCookieAuthRefresh} hook. + */ +export type CookieAuthRefreshOptions = { + // The plugin ID to used for discovering the API origin + pluginId: string; + // The path to used for calling the refresh cookie endpoint, default to '/cookie' + path?: string; +}; + +/** + * @public + * A hook that will refresh the cookie when it is about to expire. * @remarks * This hook expects a `BroadcastChannel` to be available in the global scope. */ -export function useCookieAuthRefresh({ - apiRef, -}: { - apiRef: ApiRef; -}) { - const api = useApi(apiRef); +export function useCookieAuthRefresh({ + pluginId, + path = '/cookie', +}: CookieAuthRefreshOptions) { + const fetchApi = useApi(fetchApiRef); + const discoveryApi = useApi(discoveryApiRef); const [channel] = useState( - () => new BroadcastChannel(`${apiRef.id}-auth-cookie-channel`), + () => new BroadcastChannel(`${pluginId}-auth-cookie-channel`), ); - const [state, actions] = useAsync(async () => await api.getCookie()); + const [state, actions] = useAsync(async () => { + const apiOrigin = await discoveryApi.getBaseUrl(pluginId); + const requestUrl = `${apiOrigin}${path}`; + const response = await fetchApi.fetch(`${requestUrl}`, { + credentials: 'include', + }); + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + return await response.json(); + }); useMountEffect(actions.execute); diff --git a/plugins/auth-react/src/index.ts b/plugins/auth-react/src/index.ts index 2569454bf3..30ac7cfe72 100644 --- a/plugins/auth-react/src/index.ts +++ b/plugins/auth-react/src/index.ts @@ -25,4 +25,3 @@ export * from './components'; export * from './hooks'; -export * from './types'; diff --git a/plugins/auth-react/src/types.ts b/plugins/auth-react/src/types.ts deleted file mode 100644 index dd5eadfdee..0000000000 --- a/plugins/auth-react/src/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright 2024 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. - */ - -/** - * @public - * Defines a minimal inteface for auth apis. - */ -export type AuthApi = { getCookie(): Promise<{ expiresAt: string }> }; diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index f26dfea6fd..51a4650f99 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -24,7 +24,11 @@ import { act, render } from '@testing-library/react'; import { wrapInTestApp, TestApiProvider } from '@backstage/test-utils'; import { FlatRoutes } from '@backstage/core-app-api'; -import { ApiRef } from '@backstage/core-plugin-api'; +import { + ApiRef, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; import { TechDocsAddons, @@ -69,6 +73,22 @@ const scmIntegrationsApi = { fromConfig: jest.fn().mockReturnValue({}), }; +const discoveryApi = { + getBaseUrl: jest + .fn() + .mockResolvedValue('https://backstage.example.com/api/techdocs'), +}; + +const fetchApi = { + fetch: jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ + // Expires in 10 minutes + expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(), + }), + }), +}; + /** @ignore */ type TechDocsAddonTesterTestApiPair = TApi extends infer TImpl ? readonly [ApiRef, Partial] @@ -204,6 +224,8 @@ export class TechDocsAddonTester { */ build() { const apis: TechdocsAddonTesterApis = [ + [fetchApiRef, fetchApi], + [discoveryApiRef, discoveryApi], [techdocsApiRef, techdocsApi], [techdocsStorageApiRef, techdocsStorageApi], [searchApiRef, searchApi], diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx index 605decae12..8295ea0688 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx @@ -34,7 +34,11 @@ import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; import { FlatRoutes } from '@backstage/core-app-api'; import { Page } from '@backstage/core-components'; -import { configApiRef } from '@backstage/core-plugin-api'; +import { + configApiRef, + discoveryApiRef, + fetchApiRef, +} from '@backstage/core-plugin-api'; const mockEntityMetadata = { locationMetadata: { @@ -76,6 +80,22 @@ const techdocsStorageApiMock: jest.Mocked = { syncEntityDocs: jest.fn(), }; +const discoveryApiMock = { + getBaseUrl: jest + .fn() + .mockResolvedValue('https://localhost:7000/api/techdocs'), +}; + +const fetchApiMock = { + fetch: jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue({ + // Expires in 10 minutes + expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(), + }), + }), +}; + const PageMock = () => { const { namespace, kind, name } = useParams(); return <>{`PageMock: ${namespace}#${kind}#${name}`}; @@ -96,6 +116,8 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => { return ( ', () => { }); afterEach(() => { - jest.resetAllMocks(); + jest.clearAllMocks(); }); beforeEach(() => { diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 2a366b7da4..b1b9ecb13b 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -23,7 +23,6 @@ import { TECHDOCS_ADDONS_WRAPPER_KEY, TECHDOCS_ADDONS_KEY, TechDocsReaderPageProvider, - techdocsApiRef, } from '@backstage/plugin-techdocs-react'; import { TechDocsReaderPageRenderFunction } from '../../../types'; @@ -180,7 +179,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { // As explained above, "page" is configuration 4 and is 1 return ( - + {(page as JSX.Element) || } @@ -190,7 +189,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { // As explained above, a render function is configuration 3 and React element is 2 return ( - + {({ metadata, entityMetadata, onReady }) => (
diff --git a/yarn.lock b/yarn.lock index 3a8010a02c..07c751cc3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5117,12 +5117,14 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.9.13 "@react-hookz/web": ^24.0.4 "@testing-library/jest-dom": ^6.0.0 "@testing-library/react": ^14.0.0 "@testing-library/user-event": ^14.0.0 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 languageName: unknown