Add tests for builds page and XcmetricsLayout

Signed-off-by: Niklas Granander <ngranander@spotify.com>
This commit is contained in:
Niklas Granander
2021-07-30 15:20:38 +02:00
parent 30305ffea8
commit 5b29c84a1c
8 changed files with 337 additions and 10 deletions
@@ -28,7 +28,7 @@ export const mockBuild = {
day: '2021-01-01',
compilationEndTimestamp: '2021-01-01T00:00:01Z',
tag: '',
projectName: 'Project',
projectName: 'ProjectName',
compilationEndTimestampMicroseconds: 1,
errorCount: 1,
id: 'buildId',
@@ -67,10 +67,13 @@ export const XcmetricsClient: XcmetricsApi = {
_perPage?: number,
) => {
return Promise.resolve({
items: [mockBuild],
items: [
mockBuild,
{ ...mockBuild, buildStatus: 'failed', projectName: 'ProjectName2' },
],
metadata: {
per: 10,
total: 1,
total: 2,
page: 1,
},
});
@@ -0,0 +1,61 @@
/*
* 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 React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { BuildListComponent } from './BuildListComponent';
import { xcmetricsApiRef } from '../../api';
jest.mock('../../api/XcmetricsClient');
const client = require('../../api/XcmetricsClient');
jest.mock('../BuildListFilterComponent', () => ({
BuildListFilterComponent: () => 'BuildListFilterComponent',
}));
describe('BuildListComponent', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
<ApiProvider
apis={ApiRegistry.with(xcmetricsApiRef, client.XcmetricsClient)}
>
<BuildListComponent />
</ApiProvider>,
);
expect(rendered.getByText('Builds')).toBeInTheDocument();
expect(
rendered.getByText(client.mockBuild.projectName),
).toBeInTheDocument();
});
it('should show errors', async () => {
const message = 'error';
client.XcmetricsClient.getFilteredBuilds = jest
.fn()
.mockRejectedValue({ message });
const rendered = await renderInTestApp(
<ApiProvider
apis={ApiRegistry.with(xcmetricsApiRef, client.XcmetricsClient)}
>
<BuildListComponent />
</ApiProvider>,
);
expect(rendered.getByText(message)).toBeInTheDocument();
});
});
@@ -0,0 +1,158 @@
/*
* 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 React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import userEvent from '@testing-library/user-event';
import { BuildListFilterComponent } from './BuildListFilterComponent';
import { BuildFilters, xcmetricsApiRef } from '../../api';
import { RenderResult } from '@testing-library/react';
jest.mock('../../api/XcmetricsClient');
const client = require('../../api/XcmetricsClient');
jest.mock('../DatePickerComponent', () => ({
DatePickerComponent: () => 'DatePickerComponent',
}));
const initialValues = {
from: '2020-07-30',
to: '2021-07-30',
};
const renderWithFiltersVisible = async (
callback?: (filters: BuildFilters) => void,
) => {
const rendered = await renderInTestApp(
<ApiProvider
apis={ApiRegistry.with(xcmetricsApiRef, client.XcmetricsClient)}
>
<BuildListFilterComponent
initialValues={initialValues}
onFilterChange={callback ?? jest.fn()}
/>
</ApiProvider>,
);
userEvent.click(rendered.getByLabelText('show filters'));
return rendered;
};
const setStatusFilter = async (rendered: RenderResult, option: string) => {
const statusSelect = rendered.getAllByTestId('select')[0];
userEvent.click(statusSelect);
userEvent.click((await rendered.findAllByText(option))[0]);
};
const setProjectFilter = async (rendered: RenderResult, option: string) => {
const statusSelect = rendered.getAllByTestId('select')[1];
userEvent.click(statusSelect);
const options = await rendered.findAllByText(option);
userEvent.click(options[options.length - 1]);
};
describe('BuildListFilterComponent', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
<ApiProvider
apis={ApiRegistry.with(xcmetricsApiRef, client.XcmetricsClient)}
>
<BuildListFilterComponent
initialValues={initialValues}
onFilterChange={jest.fn()}
/>
</ApiProvider>,
);
expect(rendered.getByText('Filters (0)')).toBeInTheDocument();
});
it('should toggle between showing and hiding filters', async () => {
const rendered = await renderWithFiltersVisible();
expect(
(await rendered.findAllByText('DatePickerComponent')).length,
).toEqual(2);
expect(await rendered.findByText('Status')).toBeInTheDocument();
expect(await rendered.findByText('Project')).toBeInTheDocument();
userEvent.click(rendered.getByLabelText('hide filters'));
expect(rendered.queryByText('DatePickerComponent')).toBeNull();
expect(rendered.queryByText('Status')).toBeNull();
expect(rendered.queryByText('Project')).toBeNull();
});
it('should load projects', async () => {
const callback = jest.fn();
const rendered = await renderWithFiltersVisible(callback);
userEvent.click((await rendered.findAllByText('All'))[1]);
expect(
await rendered.findByText(client.mockBuild.projectName),
).toBeInTheDocument();
});
it('should call back with a status when status is selected', async () => {
const callback = jest.fn();
const rendered = await renderWithFiltersVisible(callback);
await setStatusFilter(rendered, 'Succeeded');
expect(callback).toBeCalledWith({
...initialValues,
buildStatus: 'succeeded',
});
await setStatusFilter(rendered, 'All');
expect(callback).toBeCalledWith(initialValues);
});
it('should call back with a project when project is selected', async () => {
const callback = jest.fn();
const rendered = await renderWithFiltersVisible(callback);
await setProjectFilter(rendered, client.mockBuild.projectName);
expect(callback).toBeCalledWith({
...initialValues,
project: client.mockBuild.projectName,
});
await setProjectFilter(rendered, 'All');
expect(callback).toBeCalledWith(initialValues);
});
it('should display a count of active (changed) filters', async () => {
const rendered = await renderWithFiltersVisible();
await setStatusFilter(rendered, 'Failed');
await setProjectFilter(rendered, client.mockBuild.projectName);
expect(await rendered.findByText('Filters (2)')).toBeInTheDocument();
});
it('should clear all filters', async () => {
const callback = jest.fn();
const rendered = await renderWithFiltersVisible(callback);
await setStatusFilter(rendered, 'Failed');
await setProjectFilter(rendered, client.mockBuild.projectName);
callback.mockClear();
userEvent.click(await rendered.findByText('Clear all'));
expect(callback).toHaveBeenCalledWith(initialValues);
expect(await rendered.findByText('Filters (0)')).toBeInTheDocument();
});
});
@@ -52,15 +52,17 @@ export const BuildListFilterComponent = ({
useEffect(() => onFilterChange(values), [onFilterChange, values]);
const numFilters =
Number(values.from !== initialValues.from) +
Number(values.to !== initialValues.to) +
Number(!!values.buildStatus) +
Number(!!values.project);
const numFilters = Object.keys(values).reduce((sum, key) => {
const filtersKey = key as keyof BuildFilters;
return sum + Number(values[filtersKey] !== initialValues[filtersKey]);
}, 0);
const title = (
<>
<IconButton onClick={() => setOpen(!open)} aria-label="filter list">
<IconButton
onClick={() => setOpen(!open)}
aria-label={`${open ? 'hide' : 'show'} filters`}
>
<FilterList />
</IconButton>
Filters ({numFilters})
@@ -0,0 +1,53 @@
/*
* 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 React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { DatePickerComponent } from './DatePickerComponent';
import userEvent from '@testing-library/user-event';
describe('DatePickerComponent', () => {
it('should render', async () => {
const label = 'label';
const rendered = await renderInTestApp(
<DatePickerComponent label={label} />,
);
expect(rendered.getByText(label)).toBeInTheDocument();
});
it('should accept a date', async () => {
const label = 'label';
const callback = jest.fn();
const rendered = await renderInTestApp(
<DatePickerComponent label={label} onDateChange={callback} />,
);
const input = rendered.getByLabelText(label);
userEvent.type(input, '2020-02-02');
expect(callback).toBeCalledWith('2020-02-02');
});
it('should not accept non date', async () => {
const label = 'label';
const callback = jest.fn();
const rendered = await renderInTestApp(
<DatePickerComponent label={label} onDateChange={callback} />,
);
const input = rendered.getByLabelText(label);
userEvent.type(input, 'test');
expect(callback).not.toHaveBeenCalled();
});
});
@@ -74,6 +74,7 @@ export const DatePickerComponent = ({
<div className={classes.root}>
<Typography variant="button">{label}</Typography>
<BootstrapInput
inputProps={{ 'aria-label': label }}
type="date"
fullWidth
onChange={event => onDateChange?.(event.target.value)}
@@ -0,0 +1,48 @@
/*
* 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 React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { XcmetricsLayout } from './XcmetricsLayout';
import { xcmetricsApiRef } from '../../api';
jest.mock('../../api/XcmetricsClient');
const client = require('../../api/XcmetricsClient');
jest.mock('../OverviewComponent', () => ({
OverviewComponent: () => 'OverviewComponent',
}));
jest.mock('../BuildListComponent', () => ({
BuildListComponent: () => 'BuildListComponent',
}));
describe('XcmetricsLayout', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
<ApiProvider
apis={ApiRegistry.with(xcmetricsApiRef, client.XcmetricsClient)}
>
<XcmetricsLayout />
</ApiProvider>,
);
expect(rendered.getByText('Overview')).toBeInTheDocument();
expect(rendered.getByText('Builds')).toBeInTheDocument();
expect(rendered.getByText('OverviewComponent')).toBeInTheDocument();
});
});
+2 -1
View File
@@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { xcmetricsPlugin } from './plugin';
import { XcmetricsPage, xcmetricsPlugin } from './plugin';
describe('xcmetrics', () => {
it('should export plugin', () => {
expect(xcmetricsPlugin).toBeDefined();
expect(XcmetricsPage).toBeDefined();
});
});