tests(techdocs): fix broken tests

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2022-03-30 08:56:24 +02:00
committed by Emma Indal
parent 236089c6cd
commit e0dc1e8ccd
12 changed files with 224 additions and 165 deletions
@@ -69,7 +69,7 @@ export const TechDocsReaderPage = ({
if (defaultPath) {
return defaultPath;
}
return params['*'] ?? '';
return params['*'];
}, [params, defaultPath]);
const entityName = useMemo(() => {
@@ -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;
}) => (
<TechDocsMetadataProvider
asyncValue={{
loading: false,
error: undefined,
value: mockTechDocsMetadata,
}}
>
<TechDocsEntityProvider
asyncValue={{
loading: false,
error: undefined,
value: mockEntity,
}}
>
<TechDocsReaderPageProvider entityName={entityName}>
{children}
</TechDocsReaderPageProvider>
</TechDocsEntityProvider>
</TechDocsMetadataProvider>
<ThemeProvider theme={lightTheme}>
<TestApiProvider apis={[[techdocsApiRef, techdocsApiMock]]}>
<TechDocsMetadataProvider entityName={entityName}>
<TechDocsEntityProvider entityName={entityName}>
<TechDocsReaderPageProvider path={path} entityName={entityName}>
{children}
</TechDocsReaderPageProvider>
</TechDocsEntityProvider>
</TechDocsMetadataProvider>
</TestApiProvider>
</ThemeProvider>
);
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(
'<h1>Shadow DOM Mock</h1>',
);
@@ -31,9 +31,8 @@ import { CompoundEntityRef } from '@backstage/catalog-model';
import { techdocsApiRef } from '../../../api';
import { TechDocsEntityMetadata, TechDocsMetadata } from '../../../types';
type PropsWithEntityName<T = {}> = PropsWithChildren<
T & { entityName: CompoundEntityRef }
>;
type PropsWithEntityName<T = {}> = 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}
</Page>
@@ -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';
@@ -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;
}) => (
<ThemeProvider theme={lightTheme}>
<TestApiProvider apis={[[techdocsApiRef, techdocsApiMock]]}>
<TechDocsMetadataProvider entityName={entityName}>
<TechDocsEntityProvider entityName={entityName}>
<TechDocsReaderPageProvider path={path} entityName={entityName}>
{children}
</TechDocsReaderPageProvider>
</TechDocsEntityProvider>
</TechDocsMetadataProvider>
</TestApiProvider>
</ThemeProvider>
);
describe('<TechDocsReaderPageHeader />', () => {
it('should render a techdocs page header', async () => {
getEntityMetadata.mockResolvedValue(mockEntityMetadata);
getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata);
await act(async () => {
const rendered = await renderInTestApp(
<TechDocsReaderPageHeader
entityRef={{
kind: 'test',
name: 'test-name',
namespace: 'test-namespace',
}}
entityMetadata={{
locationMetadata: {
type: 'github',
target: 'https://example.com/',
},
apiVersion: 'v1',
kind: 'Component',
metadata: {
name: 'test',
},
spec: {
owner: 'test',
},
}}
techDocsMetadata={{
site_name: 'test-site-name',
site_description: 'test-site-desc',
}}
/>,
<Wrapper>
<TechDocsReaderPageHeader />
</Wrapper>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -58,7 +108,11 @@ describe('<TechDocsReaderPageHeader />', () => {
);
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('<TechDocsReaderPageHeader />', () => {
it('should render a techdocs page header even if metadata is missing', async () => {
await act(async () => {
const rendered = await renderInTestApp(
<TechDocsReaderPageHeader
entityRef={{
kind: 'test',
name: 'test-name',
namespace: 'test-namespace',
}}
/>,
<Wrapper>
<TechDocsReaderPageHeader />
</Wrapper>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -86,19 +136,13 @@ describe('<TechDocsReaderPageHeader />', () => {
});
it('should render a link back to the component page', async () => {
getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata);
await act(async () => {
const rendered = await renderInTestApp(
<TechDocsReaderPageHeader
entityRef={{
kind: 'test',
name: 'test-name',
namespace: 'test-namespace',
}}
techDocsMetadata={{
site_name: 'test-site-name',
site_description: 'test-site-desc',
}}
/>,
<Wrapper>
<TechDocsReaderPageHeader />
</Wrapper>,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name/*': entityRouteRef,
@@ -107,9 +151,11 @@ describe('<TechDocsReaderPageHeader />', () => {
},
);
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');
});
});
});
});
@@ -43,13 +43,12 @@ import { rootRouteRef } from '../../../routes';
const skeleton = <Skeleton animation="wave" variant="text" height={40} />;
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';
@@ -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';