@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Copyright 2021 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.
|
||||
*/
|
||||
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
import { screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { rootDocsRouteRef } from '../../../routes';
|
||||
import { InfoCardGrid } from './InfoCardGrid';
|
||||
|
||||
describe('Entity Info Card Grid', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should render multiple entities', async () => {
|
||||
await renderInTestApp(
|
||||
<InfoCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
title: 'TestTitle',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind2',
|
||||
metadata: {
|
||||
name: 'testName2',
|
||||
title: 'TestTitle2',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs2@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await screen.findByText('TestTitle')).toBeInTheDocument();
|
||||
expect(await screen.findByText('TestTitle2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle missing data gracefully', async () => {
|
||||
await renderInTestApp(
|
||||
<InfoCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await screen.findByText('testName')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render links correctly', async () => {
|
||||
await renderInTestApp(
|
||||
<InfoCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
title: 'TestTitle',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind2',
|
||||
metadata: {
|
||||
name: 'testName2',
|
||||
title: 'TestTitle2',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs2@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await screen.findByText('TestTitle')).toBeInTheDocument();
|
||||
expect(await screen.findByText('TestTitle2')).toBeInTheDocument();
|
||||
const [button1, button2] = await screen.findAllByTestId('read-docs-link');
|
||||
expect(button1.getAttribute('href')).toContain(
|
||||
'/docs/default/testkind/testname',
|
||||
);
|
||||
expect(button2.getAttribute('href')).toContain(
|
||||
'/docs/default/testkind2/testname2',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render entity title if available', async () => {
|
||||
await renderInTestApp(
|
||||
<InfoCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
title: 'TestTitle',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await screen.findByText('TestTitle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render entity name if title is not available', async () => {
|
||||
await renderInTestApp(
|
||||
<InfoCardGrid
|
||||
entities={[
|
||||
{
|
||||
apiVersion: 'version',
|
||||
kind: 'TestKind',
|
||||
metadata: {
|
||||
name: 'testName',
|
||||
},
|
||||
spec: {
|
||||
owner: 'techdocs@example.com',
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(await screen.findByText('testName')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -38,11 +38,11 @@ const useStyles = makeStyles(
|
||||
);
|
||||
|
||||
/**
|
||||
* Props for {@link InfoCardGird}
|
||||
* Props for {@link InfoCardGrid}
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type InfoCardGirdProps = {
|
||||
export type InfoCardGridProps = {
|
||||
entities: Entity[] | undefined;
|
||||
linkContent?: string | JSX.Element;
|
||||
linkDest?: (entity: Entity) => string;
|
||||
@@ -53,37 +53,40 @@ export type InfoCardGirdProps = {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const InfoCardGird = (props: InfoCardGirdProps) => {
|
||||
export const InfoCardGrid = (props: InfoCardGridProps) => {
|
||||
const { entities, linkContent, linkDest } = props;
|
||||
const classes = useStyles();
|
||||
const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef);
|
||||
const config = useApi(configApiRef);
|
||||
const linkDestination = (entity: Entity) =>
|
||||
typeof linkDest === 'function'
|
||||
? linkDest(entity)
|
||||
: getRouteToReaderPageFor({
|
||||
namespace: toLowerMaybe(
|
||||
entity.metadata.namespace ?? 'default',
|
||||
config,
|
||||
),
|
||||
kind: toLowerMaybe(entity.kind, config),
|
||||
name: toLowerMaybe(entity.metadata.name, config),
|
||||
});
|
||||
|
||||
if (!entities) return null;
|
||||
return (
|
||||
<ItemCardGrid data-testid="docs-card-container">
|
||||
<ItemCardGrid data-testid="info-card-container">
|
||||
{!entities?.length
|
||||
? null
|
||||
: entities.map(entity => (
|
||||
<InfoCard
|
||||
data-cy={entity?.metadata?.title}
|
||||
title={entity?.metadata?.title}
|
||||
key={entity.metadata.name}
|
||||
data-testid={entity?.metadata?.title}
|
||||
title={entity?.metadata?.title || entity?.metadata?.name}
|
||||
>
|
||||
<div>{entity?.metadata?.description}</div>
|
||||
<div className={classes.linkSpacer} />
|
||||
<Link
|
||||
to={
|
||||
typeof linkDest === 'function'
|
||||
? linkDest(entity)
|
||||
: getRouteToReaderPageFor({
|
||||
namespace: toLowerMaybe(
|
||||
entity.metadata.namespace ?? 'default',
|
||||
config,
|
||||
),
|
||||
kind: toLowerMaybe(entity.kind, config),
|
||||
name: toLowerMaybe(entity.metadata.name, config),
|
||||
})
|
||||
}
|
||||
to={linkDestination(entity)}
|
||||
className={classes.readMoreLink}
|
||||
data-testid="read-docs-link"
|
||||
>
|
||||
{linkContent || 'Read Docs'}
|
||||
</Link>
|
||||
|
||||
@@ -96,4 +96,133 @@ describe('TechDocsCustomHome', () => {
|
||||
await screen.findByText('Second Tab Description'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
it('should render ContentHeader based on showHeader prop', async () => {
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'First Tab',
|
||||
panels: [
|
||||
{
|
||||
title: 'First Tab',
|
||||
description: 'First Tab Description',
|
||||
panelType: 'DocsCardGrid' as PanelType,
|
||||
panelProps: { showHeader: false },
|
||||
filterPredicate: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsCustomHome tabsConfig={tabsConfig} />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Discover documentation in your ecosystem.'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
it('should render SupportButton based on hideSupport prop', async () => {
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'First Tab',
|
||||
panels: [
|
||||
{
|
||||
title: 'First Tab',
|
||||
description: 'First Tab Description',
|
||||
panelType: 'DocsCardGrid' as PanelType,
|
||||
filterPredicate: () => true,
|
||||
panelProps: { hideSupport: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsCustomHome tabsConfig={tabsConfig} />
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.queryByText('Discover documentation in your ecosystem.'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
it('should hide subtitle when hideSubtitle is true', async () => {
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'First Tab',
|
||||
panels: [
|
||||
{
|
||||
title: 'First Tab',
|
||||
description: 'First Tab Description',
|
||||
panelType: 'DocsCardGrid' as PanelType,
|
||||
filterPredicate: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsCustomHome
|
||||
tabsConfig={tabsConfig}
|
||||
title="Custom Title"
|
||||
subtitle="Custom Subtitle"
|
||||
hideSubtitle
|
||||
/>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText('Custom Title')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Custom Subtitle')).not.toBeInTheDocument();
|
||||
});
|
||||
it('should render title and subtitle', async () => {
|
||||
const tabsConfig = [
|
||||
{
|
||||
label: 'First Tab',
|
||||
panels: [
|
||||
{
|
||||
title: 'First Tab',
|
||||
description: 'First Tab Description',
|
||||
panelType: 'DocsCardGrid' as PanelType,
|
||||
filterPredicate: () => true,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
await renderInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsCustomHome
|
||||
tabsConfig={tabsConfig}
|
||||
title="Custom Title"
|
||||
subtitle="Custom Subtitle"
|
||||
/>
|
||||
</ApiProvider>,
|
||||
{
|
||||
mountedRoutes: {
|
||||
'/docs/:namespace/:kind/:name/*': rootDocsRouteRef,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(screen.getByText('Custom Title')).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Subtitle')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
} from '@backstage/plugin-catalog-react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { DocsTable } from './Tables';
|
||||
import { DocsCardGrid, InfoCardGird } from './Grids';
|
||||
import { DocsCardGrid, InfoCardGrid } from './Grids';
|
||||
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
|
||||
import { TechDocsIndexPage } from './TechDocsIndexPage';
|
||||
|
||||
@@ -47,7 +47,7 @@ const panels = {
|
||||
DocsTable: DocsTable,
|
||||
DocsCardGrid: DocsCardGrid,
|
||||
TechDocsIndexPage: TechDocsIndexPage,
|
||||
InfoCardGird: InfoCardGird,
|
||||
InfoCardGrid: InfoCardGrid,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -59,7 +59,7 @@ export type PanelType =
|
||||
| 'DocsCardGrid'
|
||||
| 'DocsTable'
|
||||
| 'TechDocsIndexPage'
|
||||
| 'InfoCardGird';
|
||||
| 'InfoCardGrid';
|
||||
|
||||
/**
|
||||
* Type representing a TechDocsCustomHome panel.
|
||||
@@ -172,7 +172,8 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => {
|
||||
error,
|
||||
} = useAsync(async () => {
|
||||
const response = await catalogApi.getEntities({
|
||||
filter: filter || {
|
||||
filter: {
|
||||
...filter,
|
||||
[`metadata.annotations.${TECHDOCS_ANNOTATION}`]: CATALOG_FILTER_EXISTS,
|
||||
},
|
||||
fields: [
|
||||
@@ -184,7 +185,9 @@ export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => {
|
||||
'spec.type',
|
||||
],
|
||||
});
|
||||
return response.items;
|
||||
return response.items.filter((entity: Entity) => {
|
||||
return !!entity.metadata.annotations?.[TECHDOCS_ANNOTATION];
|
||||
});
|
||||
});
|
||||
|
||||
const currentTabConfig = tabsConfig[selectedTab];
|
||||
|
||||
Reference in New Issue
Block a user