diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index ec03e0e5dd..d792f2ed44 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -89,7 +89,7 @@ import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import * as plugins from './plugins';
-// import { techDocsPage } from './components/techdocs/TechDocsPage';
+import { techDocsPage } from './components/techdocs/TechDocsPage';
import { ApacheAirflowPage } from '@backstage/plugin-apache-airflow';
import { PermissionedRoute } from '@backstage/plugin-permission-react';
import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common';
@@ -184,7 +184,7 @@ const routes = (
} />
}
+ element={{techDocsPage}}
>
diff --git a/packages/app/src/components/techdocs/TechDocsPage.tsx b/packages/app/src/components/techdocs/TechDocsPage.tsx
index e5142472f0..170d4f8f77 100644
--- a/packages/app/src/components/techdocs/TechDocsPage.tsx
+++ b/packages/app/src/components/techdocs/TechDocsPage.tsx
@@ -17,16 +17,15 @@
import {
TechDocsReaderPageHeader,
TechDocsReaderPageContent,
- TechDocsReaderPage,
} from '@backstage/plugin-techdocs';
import React from 'react';
const DefaultTechDocsPage = () => {
return (
-
+ <>
-
+ >
);
};
diff --git a/packages/techdocs-addons/src/types.ts b/packages/techdocs-addons/src/types.ts
index 2eb8a2f57e..8e42e0a6bc 100644
--- a/packages/techdocs-addons/src/types.ts
+++ b/packages/techdocs-addons/src/types.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { Entity } from '@backstage/catalog-model';
import { ComponentType } from 'react';
import { AsyncState } from 'react-use/lib/useAsyncFn';
diff --git a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
index 56686dd081..7bfe7642ff 100644
--- a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
+++ b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx
@@ -29,14 +29,11 @@ import LightIcon from '@material-ui/icons/Brightness7';
import DarkIcon from '@material-ui/icons/Brightness4';
import { lightTheme, darkTheme } from '@backstage/theme';
-import { CompoundEntityRef } from '@backstage/catalog-model';
-
-import { Content } from '@backstage/core-components';
import {
- Reader,
TechDocsReaderPage,
TechDocsReaderPageHeader,
+ TechDocsReaderPageContent,
} from '@backstage/plugin-techdocs';
const useStyles = makeStyles((theme: Theme) => ({
@@ -123,47 +120,13 @@ const TechDocsThemeToggle = () => {
);
};
-const TechDocsPageContent = ({
- onReady,
- entityRef,
-}: {
- entityRef: CompoundEntityRef;
- onReady: () => void;
-}) => {
- const classes = useStyles();
-
- return (
-
-
-
- );
-};
-
-const DefaultTechDocsPage = () => {
- const techDocsMetadata = {
- site_name: 'Live preview environment',
- site_description: '',
- };
-
- return (
-
- {({ entityRef, onReady }) => (
- <>
-
-
-
-
- >
- )}
-
- );
-};
-
export const techDocsPage = (
-
+
+
+
+
+
+
);
diff --git a/plugins/techdocs/dev/index.tsx b/plugins/techdocs/dev/index.tsx
index d08e914736..aa4b7bde6c 100644
--- a/plugins/techdocs/dev/index.tsx
+++ b/plugins/techdocs/dev/index.tsx
@@ -19,7 +19,8 @@ import { NotFoundError } from '@backstage/errors';
import React from 'react';
import { CompoundEntityRef } from '@backstage/catalog-model';
import {
- Reader,
+ TechDocsReaderPageProvider,
+ TechDocsReaderPageContent,
SyncResult,
TechDocsStorageApi,
techdocsStorageApiRef,
@@ -112,13 +113,15 @@ function createPage({
render() {
return (
-
+ >
+
+
);
}
}
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx
index f49f7b74c1..0ba0b47458 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx
@@ -69,7 +69,7 @@ export const TechDocsReaderPage = ({
if (defaultPath) {
return defaultPath;
}
- return params['*'] ?? '';
+ return params['*'];
}, [params, defaultPath]);
const entityName = useMemo(() => {
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx
index 921c2efd7d..f27e5aeb58 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.test.tsx
@@ -15,7 +15,17 @@
*/
import React from 'react';
-import { TechDocsMetadata } from './types';
+import { renderHook, act } from '@testing-library/react-hooks';
+
+import { ThemeProvider } from '@material-ui/core';
+
+import { lightTheme } from '@backstage/theme';
+import { TestApiProvider } from '@backstage/test-utils';
+import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
+
+import { techdocsApiRef } from '../../../api';
+import { TechDocsMetadata } from '../../../types';
+
import {
useEntityMetadata,
useTechDocsMetadata,
@@ -24,14 +34,17 @@ import {
TechDocsMetadataProvider,
TechDocsReaderPageProvider,
} from './context';
-import { renderHook, act } from '@testing-library/react-hooks';
-import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
-
-const mockEntity: Entity = {
+const mockEntityMetadata: Entity = {
apiVersion: 'v1',
kind: 'Component',
- metadata: { name: 'test-component', namespace: 'default' },
+ metadata: {
+ name: 'test',
+ namespace: 'default',
+ },
+ spec: {
+ owner: 'test',
+ },
};
const mockTechDocsMetadata: TechDocsMetadata = {
@@ -46,39 +59,42 @@ const mockShadowRoot = () => {
return shadowRoot;
};
+const techdocsApiMock = {
+ getEntityMetadata: jest.fn().mockResolvedValue(mockEntityMetadata),
+ getTechDocsMetadata: jest.fn().mockResolvedValue(mockTechDocsMetadata),
+};
+
const wrapper = ({
+ path = '',
entityName = {
- namespace: mockEntity.metadata.namespace!!,
- kind: mockEntity.kind,
- name: mockEntity.metadata.name,
+ kind: mockEntityMetadata.kind,
+ name: mockEntityMetadata.metadata.name,
+ namespace: mockEntityMetadata.metadata.namespace!!,
},
children,
}: {
- entityName: CompoundEntityRef;
+ path?: string;
+ entityName?: CompoundEntityRef;
children: React.ReactNode;
}) => (
-
-
-
- {children}
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
);
describe('context', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
describe('useEntityMetadata', () => {
it('should return loading state', async () => {
const { result } = renderHook(() => useEntityMetadata());
@@ -87,11 +103,16 @@ describe('context', () => {
});
it('should return expected entity values', async () => {
- const { result } = renderHook(() => useEntityMetadata(), { wrapper });
+ const { result, waitForNextUpdate } = renderHook(
+ () => useEntityMetadata(),
+ { wrapper },
+ );
+
+ await waitForNextUpdate();
expect(result.current.value).toBeDefined();
expect(result.current.error).toBeUndefined();
- expect(result.current.value).toMatchObject(mockEntity);
+ expect(result.current.value).toMatchObject(mockEntityMetadata);
});
});
@@ -103,7 +124,12 @@ describe('context', () => {
});
it('should return expected techdocs metadata values', async () => {
- const { result } = renderHook(() => useTechDocsMetadata(), { wrapper });
+ const { result, waitForNextUpdate } = renderHook(
+ () => useTechDocsMetadata(),
+ { wrapper },
+ );
+
+ await waitForNextUpdate();
expect(result.current.value).toBeDefined();
expect(result.current.error).toBeUndefined();
@@ -112,32 +138,49 @@ describe('context', () => {
});
describe('useTechDocsReaderPage', () => {
- it('should set title', () => {
- const { result } = renderHook(() => useTechDocsReaderPage(), { wrapper });
+ it('should set title', async () => {
+ const { result, waitForNextUpdate } = renderHook(
+ () => useTechDocsReaderPage(),
+ { wrapper },
+ );
expect(result.current.title).toBe('');
act(() => result.current.setTitle('test site title'));
+
+ await waitForNextUpdate();
+
expect(result.current.title).toBe('test site title');
});
- it('should set subtitle', () => {
- const { result } = renderHook(() => useTechDocsReaderPage(), { wrapper });
+ it('should set subtitle', async () => {
+ const { result, waitForNextUpdate } = renderHook(
+ () => useTechDocsReaderPage(),
+ { wrapper },
+ );
expect(result.current.subtitle).toBe('');
act(() => result.current.setSubtitle('test site subtitle'));
+
+ await waitForNextUpdate();
+
expect(result.current.subtitle).toBe('test site subtitle');
});
it('should set shadow root', async () => {
- const { result } = renderHook(() => useTechDocsReaderPage(), { wrapper });
+ const { result, waitForNextUpdate } = renderHook(
+ () => useTechDocsReaderPage(),
+ { wrapper },
+ );
// mock shadowroot
const shadowRoot = mockShadowRoot();
act(() => result.current.setShadowRoot(shadowRoot));
+ await waitForNextUpdate();
+
expect(result.current.shadowRoot?.innerHTML).toBe(
'Shadow DOM Mock
',
);
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.tsx
index 0c580d17cd..56e0991df6 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/context.tsx
@@ -31,9 +31,8 @@ import { CompoundEntityRef } from '@backstage/catalog-model';
import { techdocsApiRef } from '../../../api';
import { TechDocsEntityMetadata, TechDocsMetadata } from '../../../types';
-type PropsWithEntityName = PropsWithChildren<
- T & { entityName: CompoundEntityRef }
->;
+type PropsWithEntityName = T &
+ PropsWithChildren<{ entityName: CompoundEntityRef }>;
const initialContextValue = {
loading: true,
@@ -129,16 +128,16 @@ export const useTechDocsReaderPage = () => {
};
type TechDocsReaderPageProviderProps = PropsWithEntityName<{
- path: string;
+ path?: string;
}>;
export const TechDocsReaderPageProvider = ({
- path,
+ path = '',
entityName,
children,
}: TechDocsReaderPageProviderProps) => {
- const metadata = useTechDocsMetadata();
- const entityMetadata = useEntityMetadata();
+ const { value: entityMetadataValue } = useEntityMetadata();
+ const { value: techdocsMetadataValue } = useTechDocsMetadata();
const [title, setTitle] = useState(defaultTechDocsReaderPageValue.title);
const [subtitle, setSubtitle] = useState(
@@ -165,8 +164,8 @@ export const TechDocsReaderPageProvider = ({
{children instanceof Function
? children({
entityRef: entityName,
- techdocsMetadataValue: metadata.value,
- entityMetadataValue: entityMetadata.value,
+ entityMetadataValue,
+ techdocsMetadataValue,
})
: children}
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx
index 007b1fcc65..7a4877d3cd 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/context.tsx
@@ -25,7 +25,8 @@ import React, {
} from 'react';
import { useNavigate } from 'react-router-dom';
-import { useTheme, Theme, lighten, alpha } from '@material-ui/core';
+import { useTheme, Theme } from '@material-ui/core';
+import { lighten, alpha } from '@material-ui/core/styles';
import { BackstageTheme } from '@backstage/theme';
import { CompoundEntityRef } from '@backstage/catalog-model';
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx
index 9245bb66c5..3fb05a400e 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx
@@ -14,41 +14,91 @@
* limitations under the License.
*/
import React from 'react';
-import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader';
-import { act } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { act, waitFor } from '@testing-library/react';
+
+import { ThemeProvider } from '@material-ui/core';
+
+import { lightTheme } from '@backstage/theme';
+import { CompoundEntityRef } from '@backstage/catalog-model';
import { entityRouteRef } from '@backstage/plugin-catalog-react';
-import { rootRouteRef } from '../../routes';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
+
+import { techdocsApiRef } from '../../../api';
+import { rootRouteRef } from '../../../routes';
+
+import {
+ TechDocsEntityProvider,
+ TechDocsMetadataProvider,
+ TechDocsReaderPageProvider,
+} from '../TechDocsReaderPage';
+
+import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader';
+
+const mockEntityMetadata = {
+ locationMetadata: {
+ type: 'github',
+ target: 'https://example.com/',
+ },
+ apiVersion: 'v1',
+ kind: 'test',
+ metadata: {
+ name: 'test-name',
+ namespace: 'test-namespace',
+ },
+ spec: {
+ owner: 'test',
+ },
+};
+
+const mockTechDocsMetadata = {
+ site_name: 'test-site-name',
+ site_description: 'test-site-desc',
+};
+
+const getEntityMetadata = jest.fn();
+const getTechDocsMetadata = jest.fn();
+
+const techdocsApiMock = {
+ getEntityMetadata,
+ getTechDocsMetadata,
+};
+
+const Wrapper = ({
+ path = '',
+ entityName = {
+ kind: mockEntityMetadata.kind,
+ name: mockEntityMetadata.metadata.name,
+ namespace: mockEntityMetadata.metadata.namespace!!,
+ },
+ children,
+}: {
+ path?: string;
+ entityName?: CompoundEntityRef;
+ children: React.ReactNode;
+}) => (
+
+
+
+
+
+ {children}
+
+
+
+
+
+);
describe('', () => {
it('should render a techdocs page header', async () => {
+ getEntityMetadata.mockResolvedValue(mockEntityMetadata);
+ getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata);
+
await act(async () => {
const rendered = await renderInTestApp(
- ,
+
+
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -58,7 +108,11 @@ describe('', () => {
);
expect(rendered.container.innerHTML).toContain('header');
- expect(rendered.getAllByText('test-site-name')).toHaveLength(2);
+
+ await waitFor(() => {
+ expect(rendered.getAllByText('test-site-name')).toHaveLength(2);
+ });
+
expect(rendered.getByText('test-site-desc')).toBeDefined();
});
});
@@ -66,13 +120,9 @@ describe('', () => {
it('should render a techdocs page header even if metadata is missing', async () => {
await act(async () => {
const rendered = await renderInTestApp(
- ,
+
+
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -86,19 +136,13 @@ describe('', () => {
});
it('should render a link back to the component page', async () => {
+ getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata);
+
await act(async () => {
const rendered = await renderInTestApp(
- ,
+
+
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -107,9 +151,11 @@ describe('', () => {
},
);
- expect(rendered.container.innerHTML).toContain(
- '/catalog/test-namespace/test/test-name',
- );
+ await waitFor(() => {
+ expect(
+ rendered.getByRole('link', { name: 'test:test-namespace/test-name' }),
+ ).toHaveAttribute('href', '/catalog/test-namespace/test/test-name');
+ });
});
});
});
diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx
index b1ca6ce50a..c417274403 100644
--- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx
@@ -43,13 +43,12 @@ import { rootRouteRef } from '../../../routes';
const skeleton = ;
-export const TechDocsReaderPageHeader: FC = props => {
- const { children } = props;
+export const TechDocsReaderPageHeader: FC = ({ children }) => {
const addons = useTechDocsAddons();
const configApi = useApi(configApiRef);
- const { value: techDocsMetadata } = useTechDocsMetadata();
const { value: entityMetadata } = useEntityMetadata();
+ const { value: techDocsMetadata } = useTechDocsMetadata();
const {
title,
@@ -61,11 +60,17 @@ export const TechDocsReaderPageHeader: FC = props => {
useEffect(() => {
if (!techDocsMetadata) return;
- setTitle(prevTitle => prevTitle || techDocsMetadata.site_name);
- setSubtitle(
- prevSubtitle =>
- prevSubtitle || techDocsMetadata.site_description || 'Home',
- );
+ setTitle(prevTitle => {
+ const { site_name } = techDocsMetadata;
+ return prevTitle || site_name;
+ });
+ setSubtitle(prevSubtitle => {
+ let { site_description } = techDocsMetadata;
+ if (site_description === 'None') {
+ site_description = 'Home';
+ }
+ return prevSubtitle || site_description;
+ });
}, [techDocsMetadata, setTitle, setSubtitle]);
const appTitle = configApi.getOptional('app.title') || 'Backstage';
diff --git a/plugins/techdocs/src/reader/components/index.ts b/plugins/techdocs/src/reader/components/index.ts
index f6294200a3..5abdf002b1 100644
--- a/plugins/techdocs/src/reader/components/index.ts
+++ b/plugins/techdocs/src/reader/components/index.ts
@@ -19,12 +19,13 @@ export type {
TechDocsReaderLayoutProps,
} from './TechDocsReaderPage';
export {
- TechDocsReaderLayout,
- useTechDocsMetadata,
- useEntityMetadata,
- useTechDocsReaderPage,
useShadowRoot,
useShadowRootElements,
+ useEntityMetadata,
+ useTechDocsMetadata,
+ useTechDocsReaderPage,
+ TechDocsReaderLayout,
+ TechDocsReaderPageProvider,
} from './TechDocsReaderPage';
export * from './TechDocsReaderPageHeader';
export * from './TechDocsReaderPageContent';