Skip to content

Commit

Permalink
Merge da2bd17 into 2a3ec15
Browse files Browse the repository at this point in the history
  • Loading branch information
yebrahim committed Nov 28, 2018
2 parents 2a3ec15 + da2bd17 commit fa457fb
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 224 deletions.
10 changes: 10 additions & 0 deletions frontend/src/Css.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@ export const commonCss = stylesheet({
position: 'absolute',
top: 'calc(50% - 15px)',
},
busyOverlay: {
backgroundColor: '#ffffffaa',
bottom: 0,
left: 0,
position: 'absolute',
right: 0,
top: 0,
zIndex: 1,
},
buttonAction: {
$nest: {
'&:disabled': {
Expand Down Expand Up @@ -254,6 +263,7 @@ export const commonCss = stylesheet({
backgroundRepeat: 'no-repeat',
backgroundSize: '100% 40px, 100% 40px, 100% 2px, 100% 2px',
overflow: 'auto',
position: 'relative',
},
textField: {
display: 'flex',
Expand Down
56 changes: 36 additions & 20 deletions frontend/src/components/CustomTable.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as React from 'react';
import CustomTable, { Column, Row, css, ExpandState } from './CustomTable';
import TestUtils from '../TestUtils';
import { shallow } from 'enzyme';

const props = {
Expand Down Expand Up @@ -63,34 +64,39 @@ describe('CustomTable', () => {
console.error = consoleErrorBackup;
});

it('renders without rows or columns', () => {
it('renders without rows or columns', async () => {
const tree = shallow(<CustomTable {...props} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders empty message on no rows', () => {
it('renders empty message on no rows', async () => {
const tree = shallow(<CustomTable {...props} emptyMessage='test empty message' />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders some columns with equal widths without rows', () => {
it('renders some columns with equal widths without rows', async () => {
const tree = shallow(<CustomTable {...props} columns={[{ label: 'col1' }, { label: 'col2' }]} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders without the checkboxes if disableSelection is true', () => {
it('renders without the checkboxes if disableSelection is true', async () => {
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns}
disableSelection={true} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders some columns with descending sort order on first column', () => {
it('renders some columns with descending sort order on first column', async () => {
const tree = shallow(<CustomTable {...props} initialSortOrder='desc'
columns={[{ label: 'col1', sortKey: 'col1sortkey' }, { label: 'col2' }]} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders columns with specified widths', () => {
it('renders columns with specified widths', async () => {
const testcolumns = [{
flex: 3,
label: 'col1',
Expand All @@ -99,6 +105,7 @@ describe('CustomTable', () => {
label: 'col2',
}];
const tree = shallow(<CustomTable {...props} columns={testcolumns} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

Expand Down Expand Up @@ -182,21 +189,24 @@ describe('CustomTable', () => {
'Rows must have the same number of cells defined in columns');
});

it('renders some rows', () => {
it('renders some rows', async () => {
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders some rows using a custom renderer', () => {
it('renders some rows using a custom renderer', async () => {
columns[0].customRenderer = () => (<span>this is custom output</span>) as any;
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
columns[0].customRenderer = undefined;
});

it('displays warning icon with tooltip if row has error', () => {
it('displays warning icon with tooltip if row has error', async () => {
rows[0].error = 'dummy error';
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
rows[0].error = undefined;
});
Expand Down Expand Up @@ -276,7 +286,7 @@ describe('CustomTable', () => {
const reloadResult = Promise.resolve('');
const spy = () => reloadResult;
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} reload={spy} />);
await reloadResult;
await TestUtils.flushPromises();
expect(tree.state()).toHaveProperty('maxPageIndex', 0);
expect(tree.find('WithStyles(IconButton)').at(0).prop('disabled')).toBeTruthy();
expect(tree.find('WithStyles(IconButton)').at(1).prop('disabled')).toBeTruthy();
Expand All @@ -296,7 +306,7 @@ describe('CustomTable', () => {
const reloadResult = Promise.resolve('some token');
const spy = jest.fn(() => reloadResult);
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} reload={spy} />);
await reloadResult;
await TestUtils.flushPromises();

tree.find('WithStyles(IconButton)').at(1).simulate('click');
expect(spy).toHaveBeenLastCalledWith({ pageToken: 'some token', pageSize: 10, orderAscending: false, sortBy: '' });
Expand All @@ -306,10 +316,10 @@ describe('CustomTable', () => {
const reloadResult = Promise.resolve('some token');
const spy = jest.fn(() => reloadResult);
const tree = shallow(<CustomTable {...props} rows={[]} columns={columns} reload={spy} />);
await reloadResult;
await TestUtils.flushPromises();

tree.find('WithStyles(IconButton)').at(1).simulate('click');
await reloadResult;
await TestUtils.flushPromises();
expect(spy).toHaveBeenLastCalledWith({ pageToken: 'some token', pageSize: 10, sortBy: '', orderAscending: false });
expect(tree.state()).toHaveProperty('currentPage', 1);
tree.setProps({ rows: [rows[1]] });
Expand All @@ -327,11 +337,12 @@ describe('CustomTable', () => {
await reloadResult;

tree.find('WithStyles(IconButton)').at(0).simulate('click');
await reloadResult;
await TestUtils.flushPromises();
expect(spy).toHaveBeenLastCalledWith({ pageToken: '', orderAscending: false, sortBy: '', pageSize: 10 });

tree.setProps({ rows });
expect(tree.find('WithStyles(IconButton)').at(0).prop('disabled')).toBeTruthy();
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

Expand All @@ -341,7 +352,7 @@ describe('CustomTable', () => {
const tree = shallow(<CustomTable {...props} rows={[]} columns={columns} reload={spy} />);

tree.find('.' + css.rowsPerPage).simulate('change', { target: { value: 1234 } });
await reloadResult;
await TestUtils.flushPromises();
expect(spy).toHaveBeenLastCalledWith({ pageSize: 1234, pageToken: '', orderAscending: false, sortBy: '' });
expect(tree.state()).toHaveProperty('tokenList', ['', 'some token']);
});
Expand All @@ -357,34 +368,38 @@ describe('CustomTable', () => {
expect(tree.state()).toHaveProperty('tokenList', ['']);
});

it('renders a collapsed row', () => {
it('renders a collapsed row', async () => {
const row = { ...rows[0] };
row.expandState = ExpandState.COLLAPSED;
const tree = shallow(<CustomTable {...props} rows={[row]} columns={columns}
getExpandComponent={() => null} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders a collapsed row when selection is disabled', () => {
it('renders a collapsed row when selection is disabled', async () => {
const row = { ...rows[0] };
row.expandState = ExpandState.COLLAPSED;
const tree = shallow(<CustomTable {...props} rows={[row]} columns={columns}
getExpandComponent={() => null} disableSelection={true} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders an expanded row', () => {
it('renders an expanded row', async () => {
const row = { ...rows[0] };
row.expandState = ExpandState.EXPANDED;
const tree = shallow(<CustomTable {...props} rows={[row]} columns={columns} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

it('renders an expanded row with expanded component below it', () => {
it('renders an expanded row with expanded component below it', async () => {
const row = { ...rows[0] };
row.expandState = ExpandState.EXPANDED;
const tree = shallow(<CustomTable {...props} rows={[row]} columns={columns}
getExpandComponent={() => <span>Hello World</span>} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});

Expand All @@ -400,8 +415,9 @@ describe('CustomTable', () => {
expect(stopPropagationSpy).toHaveBeenCalledWith();
});

it('renders a table with sorting disabled', () => {
it('renders a table with sorting disabled', async () => {
const tree = shallow(<CustomTable {...props} rows={rows} columns={columns} disableSorting={true} />);
await TestUtils.flushPromises();
expect(tree).toMatchSnapshot();
});
});
30 changes: 25 additions & 5 deletions frontend/src/components/CustomTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import ArrowRight from '@material-ui/icons/ArrowRight';
import Checkbox, { CheckboxProps } from '@material-ui/core/Checkbox';
import ChevronLeft from '@material-ui/icons/ChevronLeft';
import ChevronRight from '@material-ui/icons/ChevronRight';
import CircularProgress from '@material-ui/core/CircularProgress';
import IconButton from '@material-ui/core/IconButton';
import MenuItem from '@material-ui/core/MenuItem';
import Radio from '@material-ui/core/Radio';
Expand Down Expand Up @@ -92,6 +93,9 @@ export const css = stylesheet({
expandButtonExpanded: {
transform: 'rotate(90deg)',
},
expandableContainer: {
transition: 'margin 0.2s',
},
expandedContainer: {
borderRadius: 10,
boxShadow: '0 1px 2px 0 rgba(60,64,67,0.30), 0 1px 3px 1px rgba(60,64,67,0.15)',
Expand Down Expand Up @@ -167,6 +171,7 @@ interface CustomTableProps {

interface CustomTableState {
currentPage: number;
isBusy: boolean;
maxPageIndex: number;
sortOrder: 'asc' | 'desc';
pageSize: number;
Expand All @@ -182,6 +187,7 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo

this.state = {
currentPage: 0,
isBusy: false,
maxPageIndex: Number.MAX_SAFE_INTEGER,
pageSize: 10,
sortBy: props.initialSortColumn ||
Expand Down Expand Up @@ -283,17 +289,24 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
</div>

{/* Body */}
<div className={commonCss.scrollContainer}>
<div className={commonCss.scrollContainer} style={{ minHeight: 60 }}>
{/* Busy experience */}
{this.state.isBusy && (<React.Fragment>
<div className={commonCss.busyOverlay} />
<CircularProgress size={25} className={commonCss.absoluteCenter} style={{ zIndex: 2 }} />
</React.Fragment>)}

{/* Empty experience */}
{this.props.rows.length === 0 && !!this.props.emptyMessage && (
{this.props.rows.length === 0 && !!this.props.emptyMessage && !this.state.isBusy && (
<div className={css.emptyMessage}>{this.props.emptyMessage}</div>
)}
{this.props.rows.map((row, i) => {
if (row.otherFields.length !== this.props.columns.length) {
logger.error('Rows must have the same number of cells defined in columns');
return null;
}
return (<div className={classes(row.expandState === ExpandState.EXPANDED && css.expandedContainer)} key={i}>
return (<div className={classes(css.expandableContainer,
row.expandState === ExpandState.EXPANDED && css.expandedContainer)} key={i}>
<div role='checkbox' tabIndex={-1} className={
classes(
'tableRow',
Expand Down Expand Up @@ -364,7 +377,7 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
);
}

public reload(loadRequest?: ListRequest): Promise<string> {
public async reload(loadRequest?: ListRequest): Promise<string> {
// Override the current state with incoming request
const request: ListRequest = Object.assign({
orderAscending: this.state.sortOrder === 'asc',
Expand All @@ -383,7 +396,14 @@ export default class CustomTable extends React.Component<CustomTableProps, Custo
request.sortBy += ' desc';
}

return this.props.reload(request);
let result = '';
this.setStateSafe({ isBusy: true });
try {
result = await this.props.reload(request);
} finally {
this.setStateSafe({ isBusy: false });
}
return result;
}

private setStateSafe(newState: Partial<CustomTableState>, cb?: () => void): void {
Expand Down

0 comments on commit fa457fb

Please sign in to comment.