Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PipelineSelector, RecurringRunsManager, and 404Page tests #319

Merged
merged 7 commits into from
Nov 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 21 additions & 10 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"@types/d3": "^5.0.0",
"@types/d3-dsv": "^1.0.33",
"@types/dagre": "^0.7.40",
"@types/enzyme": "^3.1.14",
"@types/enzyme": "^3.1.15",
"@types/enzyme-adapter-react-16": "^1.0.3",
"@types/express": "^4.16.0",
"@types/http-proxy-middleware": "^0.17.5",
Expand All @@ -66,7 +66,7 @@
"@types/react-virtualized": "^9.18.7",
"backstopjs": "^3.5.16",
"coveralls": "^3.0.2",
"enzyme": "^3.6.0",
"enzyme": "^3.7.0",
"enzyme-adapter-react-16": "^1.5.0",
"enzyme-to-json": "^3.3.4",
"react-router-test-context": "^0.1.0",
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/pages/404.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2018 Google LLC
*
* 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
*
* https://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 * as React from 'react';
import Page404 from './404';
import { PageProps } from './Page';
import { shallow } from 'enzyme';

describe('404', () => {
function generateProps(): PageProps {
return {
history: {} as any,
location: { pathname: 'some bad page' } as any,
match: {} as any,
toolbarProps: {} as any,
updateBanner: jest.fn(),
updateDialog: jest.fn(),
updateSnackbar: jest.fn(),
updateToolbar: jest.fn(),
};
}

it('renders a 404 page', () => {
expect(shallow(<Page404 {...generateProps()} />)).toMatchSnapshot();
});
});
105 changes: 105 additions & 0 deletions frontend/src/pages/PipelineSelector.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright 2018 Google LLC
*
* 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
*
* https://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 * as React from 'react';
import PipelineSelector, { PipelineSelectorProps } from './PipelineSelector';
import TestUtils from '../TestUtils';
import { ApiPipeline } from '../apis/pipeline';
import { ListRequest, Apis } from '../lib/Apis';
import { shallow } from 'enzyme';

describe('PipelineSelector', () => {
class TestPipelineSelector extends PipelineSelector {
public async _loadPipelines(request: ListRequest): Promise<string> {
return super._loadPipelines(request);
}
public _pipelineSelectionChanged(selectedIds: string[]): void {
return super._pipelineSelectionChanged(selectedIds);
}
}

const updateDialogSpy = jest.fn();
const pipelineSelectionChangedCbSpy = jest.fn();
const listPipelinesSpy = jest.spyOn(Apis.pipelineServiceApi, 'listPipelines');
const PIPELINES: ApiPipeline[] = [{
created_at: new Date(2018, 10, 9, 8, 7, 6),
description: 'test pipeline description',
name: 'test pipeline name',
}];

function generateProps(): PipelineSelectorProps {
return {
history: {} as any,
location: '' as any,
match: {} as any,
pipelineSelectionChanged: pipelineSelectionChangedCbSpy,
updateDialog: updateDialogSpy,
};
}

beforeEach(() => {
listPipelinesSpy.mockReset();
listPipelinesSpy.mockImplementation(() => ({ pipelines: PIPELINES }));
updateDialogSpy.mockReset();
pipelineSelectionChangedCbSpy.mockReset();
});

it('calls API to load pipelines', async () => {
const tree = shallow(<TestPipelineSelector {...generateProps()} />);
await (tree.instance() as TestPipelineSelector)._loadPipelines({});
expect(listPipelinesSpy).toHaveBeenCalledTimes(1);
expect(listPipelinesSpy).toHaveBeenLastCalledWith(undefined, undefined, undefined);
expect(tree.state('pipelines')).toEqual(PIPELINES);
expect(tree).toMatchSnapshot();
tree.unmount();
});

it('shows error dialog if listing fails', async () => {
TestUtils.makeErrorResponseOnce(listPipelinesSpy, 'woops!');
jest.spyOn(console, 'error').mockImplementation();
const tree = shallow(<TestPipelineSelector {...generateProps()} />);
await (tree.instance() as TestPipelineSelector)._loadPipelines({});
expect(listPipelinesSpy).toHaveBeenCalledTimes(1);
expect(updateDialogSpy).toHaveBeenLastCalledWith(expect.objectContaining({
content: 'List pipelines request failed with:\nwoops!',
title: 'Error retrieving pipelines',
}));
expect(tree.state('pipelines')).toEqual([]);
tree.unmount();
});

it('calls selection callback when a pipeline is selected', async () => {
const tree = shallow(<TestPipelineSelector {...generateProps()} />);
await (tree.instance() as TestPipelineSelector)._loadPipelines({});
expect(tree.state('selectedIds')).toEqual([]);
(tree.instance() as TestPipelineSelector)._pipelineSelectionChanged(['pipeline-id']);
expect(pipelineSelectionChangedCbSpy).toHaveBeenLastCalledWith('pipeline-id');
expect(tree.state('selectedIds')).toEqual(['pipeline-id']);
tree.unmount();
});

it('logs error if more than one pipeline is selected', async () => {
const tree = shallow(<TestPipelineSelector {...generateProps()} />);
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
await (tree.instance() as TestPipelineSelector)._loadPipelines({});
expect(tree.state('selectedIds')).toEqual([]);
(tree.instance() as TestPipelineSelector)._pipelineSelectionChanged(['pipeline-id', 'pipeline2-id']);
expect(pipelineSelectionChangedCbSpy).not.toHaveBeenCalled();
expect(tree.state('selectedIds')).toEqual([]);
expect(consoleSpy).toHaveBeenCalled();
tree.unmount();
});
});
16 changes: 4 additions & 12 deletions frontend/src/pages/PipelineSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { logger, formatDateString, errorToMessage } from '../lib/Utils';
import { ApiPipeline } from '../apis/pipeline';
import { DialogProps } from '../components/Router';

interface PipelineSelectorProps extends RouteComponentProps {
export interface PipelineSelectorProps extends RouteComponentProps {
pipelineSelectionChanged: (selectedPipelineId: string) => void;
updateDialog: (dialogProps: DialogProps) => void;
}
Expand All @@ -36,7 +36,6 @@ interface PipelineSelectorState {

class PipelineSelector extends React.Component<PipelineSelectorProps, PipelineSelectorState> {
protected _isMounted = true;
private _tableRef = React.createRef<CustomTable>();

constructor(props: any) {
super(props);
Expand Down Expand Up @@ -74,8 +73,7 @@ class PipelineSelector extends React.Component<PipelineSelectorProps, PipelineSe
<Toolbar actions={toolbarActions} breadcrumbs={[]} pageTitle='Choose a pipeline' />
<CustomTable columns={columns} rows={rows} selectedIds={selectedIds} useRadioButtons={true}
updateSelection={this._pipelineSelectionChanged.bind(this)}
initialSortColumn={PipelineSortKeys.CREATED_AT} ref={this._tableRef}
reload={this._loadPipelines.bind(this)}
initialSortColumn={PipelineSortKeys.CREATED_AT} reload={this._loadPipelines.bind(this)}
emptyMessage={'No pipelines found. Upload a pipeline and then try again.'} />
</React.Fragment>
);
Expand All @@ -85,19 +83,13 @@ class PipelineSelector extends React.Component<PipelineSelectorProps, PipelineSe
this._isMounted = false;
}

public async refresh(): Promise<void> {
if (this._tableRef.current) {
await this._tableRef.current.reload();
}
}

protected setStateSafe(newState: Partial<PipelineSelectorState>, cb?: () => void): void {
if (this._isMounted) {
this.setState(newState as any, cb);
}
}

private _pipelineSelectionChanged(selectedIds: string[]): void {
protected _pipelineSelectionChanged(selectedIds: string[]): void {
if (!Array.isArray(selectedIds) || selectedIds.length !== 1) {
logger.error(`${selectedIds.length} pipelines were selected somehow`, selectedIds);
return;
Expand All @@ -106,7 +98,7 @@ class PipelineSelector extends React.Component<PipelineSelectorProps, PipelineSe
this.setStateSafe({ selectedIds });
}

private async _loadPipelines(request: ListRequest): Promise<string> {
protected async _loadPipelines(request: ListRequest): Promise<string> {
let pipelines: ApiPipeline[] = [];
let nextPageToken = '';
try {
Expand Down
Loading