diff --git a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts
index cacb8fb2b1..430fbb1006 100644
--- a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts
+++ b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts
@@ -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,
},
});
diff --git a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx
new file mode 100644
index 0000000000..3818d51a16
--- /dev/null
+++ b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx
@@ -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(
+
+
+ ,
+ );
+
+ 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(
+
+
+ ,
+ );
+
+ expect(rendered.getByText(message)).toBeInTheDocument();
+ });
+});
diff --git a/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.test.tsx b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.test.tsx
new file mode 100644
index 0000000000..eac5775d77
--- /dev/null
+++ b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.test.tsx
@@ -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(
+
+
+ ,
+ );
+
+ 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(
+
+
+ ,
+ );
+
+ 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();
+ });
+});
diff --git a/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx
index 4f6604702e..ff45e15925 100644
--- a/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx
+++ b/plugins/xcmetrics/src/components/BuildListFilterComponent/BuildListFilterComponent.tsx
@@ -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 = (
<>
- setOpen(!open)} aria-label="filter list">
+ setOpen(!open)}
+ aria-label={`${open ? 'hide' : 'show'} filters`}
+ >
Filters ({numFilters})
diff --git a/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.test.tsx b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.test.tsx
new file mode 100644
index 0000000000..74633fe786
--- /dev/null
+++ b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.test.tsx
@@ -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(
+ ,
+ );
+ expect(rendered.getByText(label)).toBeInTheDocument();
+ });
+
+ it('should accept a date', async () => {
+ const label = 'label';
+ const callback = jest.fn();
+ const rendered = await renderInTestApp(
+ ,
+ );
+ 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(
+ ,
+ );
+ const input = rendered.getByLabelText(label);
+
+ userEvent.type(input, 'test');
+ expect(callback).not.toHaveBeenCalled();
+ });
+});
diff --git a/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx
index a5b1fbb346..854fbbd87f 100644
--- a/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx
+++ b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx
@@ -74,6 +74,7 @@ export const DatePickerComponent = ({
{label}
onDateChange?.(event.target.value)}
diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx
new file mode 100644
index 0000000000..2ce634dc11
--- /dev/null
+++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx
@@ -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(
+
+
+ ,
+ );
+
+ expect(rendered.getByText('Overview')).toBeInTheDocument();
+ expect(rendered.getByText('Builds')).toBeInTheDocument();
+
+ expect(rendered.getByText('OverviewComponent')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/xcmetrics/src/plugin.test.ts b/plugins/xcmetrics/src/plugin.test.ts
index 055c647dd9..a390e8aeb7 100644
--- a/plugins/xcmetrics/src/plugin.test.ts
+++ b/plugins/xcmetrics/src/plugin.test.ts
@@ -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();
});
});