funcaitonal store (#146)

This commit is contained in:
Allen
2017-12-09 14:56:09 +08:00
committed by GitHub
parent ff31b2fca5
commit 574a3146fc
24 changed files with 501 additions and 431 deletions

View File

@@ -0,0 +1,16 @@
export const getByCurrPage = store => (page, sizePerPage, pageStartIndex) => {
const getNormalizedPage = () => {
const offset = Math.abs(1 - pageStartIndex);
return page + offset;
};
const end = (getNormalizedPage() * sizePerPage) - 1;
const start = end - (sizePerPage - 1);
const dataSize = store.data.length;
const result = [];
for (let i = start; i <= end; i += 1) {
result.push(store.data[i]);
if (i + 1 === dataSize) break;
}
return result;
};

View File

@@ -5,6 +5,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Const from './const';
import { getByCurrPage } from './page';
const wrapperFactory = baseElement =>
class PaginationWrapper extends Component {
@@ -98,7 +99,7 @@ const wrapperFactory = baseElement =>
const data = this.isRemote() ?
this.props.data :
store.getByCurrPage(currPage, currSizePerPage, pageStartIndex);
getByCurrPage(store)(currPage, currSizePerPage, pageStartIndex);
const base = baseElement({
...this.props,
@@ -110,7 +111,7 @@ const wrapperFactory = baseElement =>
base,
<Pagination
key="pagination"
dataSize={ options.totalSize || store.getDataSize() }
dataSize={ options.totalSize || store.data.length }
currPage={ currPage }
currSizePerPage={ currSizePerPage }
onPageChange={ this.handleChangePage }

View File

@@ -0,0 +1,38 @@
import Store from 'react-bootstrap-table2/src/store';
import { getByCurrPage } from '../src/page';
describe('Page Functions', () => {
let data;
let store;
describe('getByCurrPage', () => {
beforeEach(() => {
data = [];
for (let i = 0; i < 100; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
store = new Store('id');
store.data = data;
});
it('should always return correct data', () => {
[
// [page, sizePerPage, pageStartIndex]
[1, 10, 1],
[1, 25, 1],
[1, 30, 1],
[3, 30, 1],
[4, 30, 1],
[10, 10, 1],
[0, 10, 0],
[1, 10, 0],
[9, 10, 0]
].forEach(([page, sizePerPage, pageStartIndex]) => {
const rows = getByCurrPage(store)(page, sizePerPage, pageStartIndex);
expect(rows).toBeDefined();
expect(Array.isArray(rows)).toBeTruthy();
expect(rows.every(row => !!row)).toBeTruthy();
});
});
});
});

View File

@@ -4,7 +4,7 @@ import { shallow } from 'enzyme';
import BootstrapTable from 'react-bootstrap-table2/src/bootstrap-table';
import Store from 'react-bootstrap-table2/src/store/base';
import Store from 'react-bootstrap-table2/src/store';
import paginator from '../src';
import wrapperFactory from '../src/wrapper';
import Pagination from '../src/pagination';
@@ -23,19 +23,23 @@ describe('Wrapper', () => {
let wrapper;
let instance;
const createTableProps = (props = {}) => ({
keyField: 'id',
columns: [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name'
}],
data,
pagination: paginator(props.options),
store: new Store({ data })
});
const createTableProps = (props = {}) => {
const tableProps = {
keyField: 'id',
columns: [{
dataField: 'id',
text: 'ID'
}, {
dataField: 'name',
text: 'Name'
}],
data,
pagination: paginator(props.options),
store: new Store('id')
};
tableProps.store.data = data;
return tableProps;
};
const pureTable = props => (<BootstrapTable { ...props } />);
@@ -76,7 +80,7 @@ describe('Wrapper', () => {
it('should rendering Pagination correctly', () => {
const pagination = wrapper.find(Pagination);
expect(pagination.length).toBe(1);
expect(pagination.prop('dataSize')).toEqual(props.store.getDataSize());
expect(pagination.prop('dataSize')).toEqual(props.store.data.length);
expect(pagination.prop('currPage')).toEqual(instance.state.currPage);
expect(pagination.prop('currSizePerPage')).toEqual(instance.state.currSizePerPage);
expect(pagination.prop('onPageChange')).toEqual(instance.handleChangePage);

View File

@@ -9,6 +9,7 @@ import Caption from './caption';
import Body from './body';
import PropsBaseResolver from './props-resolver';
import Const from './const';
import { isSelectedAll } from './store/selection';
class BootstrapTable extends PropsBaseResolver(Component) {
constructor(props) {
@@ -73,7 +74,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
const headerCellSelectionInfo = this.resolveSelectRowPropsForHeader({
onAllRowsSelect: this.props.onAllRowsSelect,
selected: store.selected,
allRowsSelected: store.isAllRowsSelected()
allRowsSelected: isSelectedAll(store)
});
return (
@@ -96,7 +97,7 @@ class BootstrapTable extends PropsBaseResolver(Component) {
noDataIndication={ noDataIndication }
cellEdit={ cellEditInfo }
selectRow={ cellSelectionInfo }
selectedRowKeys={ store.getSelectedRowKeys() }
selectedRowKeys={ store.selected }
rowStyle={ rowStyle }
rowClasses={ rowClasses }
rowEvents={ rowEvents }

View File

@@ -1,7 +1,7 @@
/* eslint no-return-assign: 0 */
/* eslint react/prop-types: 0 */
import React, { Component } from 'react';
import Store from './store/base';
import Store from './store';
import {
wrapWithCellEdit,
@@ -16,13 +16,14 @@ const withDataStore = Base =>
class BootstrapTableContainer extends Component {
constructor(props) {
super(props);
this.store = new Store(props);
this.store = new Store(props.keyField);
this.store.data = props.data;
this.handleUpdateCell = this.handleUpdateCell.bind(this);
this.onRemotePageChange = this.onRemotePageChange.bind(this);
}
componentWillReceiveProps(nextProps) {
this.store.set(nextProps.data);
this.store.data = nextProps.data;
}
onRemotePageChange(page, sizePerPage) {

View File

@@ -5,6 +5,13 @@ import PropTypes from 'prop-types';
import { selectionElement } from '../table-factory';
import Const from '../const';
import {
isAnySelectedRow,
selectableKeys,
unSelectableKeys,
getSelectedRows
} from '../store/selection';
import { getRowByRowId } from '../store/rows';
class RowSelectionWrapper extends Component {
constructor(props) {
@@ -12,7 +19,7 @@ class RowSelectionWrapper extends Component {
this.handleRowSelect = this.handleRowSelect.bind(this);
this.handleAllRowsSelect = this.handleAllRowsSelect.bind(this);
this.state = {
selectedRowKeys: props.store.getSelectedRowKeys()
selectedRowKeys: props.store.selected
};
}
@@ -25,7 +32,7 @@ class RowSelectionWrapper extends Component {
const { selectRow: { mode, onSelect }, store } = this.props;
const { ROW_SELECT_SINGLE } = Const;
let currSelected = [...store.getSelectedRowKeys()];
let currSelected = [...store.selected];
if (mode === ROW_SELECT_SINGLE) { // when select mode is radio
currSelected = [rowKey];
@@ -35,10 +42,10 @@ class RowSelectionWrapper extends Component {
currSelected = currSelected.filter(value => value !== rowKey);
}
store.setSelectedRowKeys(currSelected);
store.selected = currSelected;
if (onSelect) {
const row = store.getRowByRowId(rowKey);
const row = getRowByRowId(store)(rowKey);
onSelect(row, checked, rowIndex);
}
@@ -56,20 +63,20 @@ class RowSelectionWrapper extends Component {
onSelectAll,
nonSelectable
} } = this.props;
const selected = store.isAnySelectedRow(nonSelectable);
const selected = isAnySelectedRow(store)(nonSelectable);
// set next status of all row selected by store.selected or customizing by user.
const result = option || !selected;
const currSelected = result ?
store.selectAllRows(nonSelectable) :
store.cleanSelectedRows(nonSelectable);
selectableKeys(store)(nonSelectable) :
unSelectableKeys(store)(nonSelectable);
store.setSelectedRowKeys(currSelected);
store.selected = currSelected;
if (onSelectAll) {
onSelectAll(result, store.getSelectedRows());
onSelectAll(result, getSelectedRows(store));
}
this.setState(() => ({

View File

@@ -30,9 +30,7 @@ class SortWrapper extends Component {
const { store } = this.props;
store.sortBy(column);
this.table.setState({
data: store.get()
});
this.table.setState({ data: store.data });
}
render() {
@@ -40,7 +38,7 @@ class SortWrapper extends Component {
...this.props,
ref: node => this.table = node,
onSort: this.handleSort,
data: this.props.store.get()
data: this.props.store.data
});
}
}

View File

@@ -1,110 +0,0 @@
/* eslint class-methods-use-this: 0 */
import { sort } from './sort';
import Const from '../const';
import _ from '../utils';
export default class Store {
constructor(props) {
const { data, keyField } = props;
this.keyField = keyField;
this.set(data);
this.sortOrder = undefined;
this.sortField = undefined;
this.selected = [];
}
isEmpty() {
return this.data.length === 0;
}
sortBy({ dataField, sortFunc }, order) {
if (order) {
this.sortOrder = order;
} else if (dataField !== this.sortField) {
this.sortOrder = Const.SORT_DESC;
} else {
this.sortOrder = this.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC;
}
this.data = sort(dataField, this.data, this.sortOrder, sortFunc);
this.sortField = dataField;
}
edit(rowId, dataField, newValue) {
const row = this.getRowByRowId(rowId);
if (row) _.set(row, dataField, newValue);
}
get() {
return this.data;
}
getByCurrPage(page, sizePerPage, pageStartIndex) {
const getNormalizedPage = () => {
const offset = Math.abs(1 - pageStartIndex);
return page + offset;
};
const end = (getNormalizedPage() * sizePerPage) - 1;
const start = end - (sizePerPage - 1);
const dataSize = this.getDataSize();
const result = [];
for (let i = start; i <= end; i += 1) {
result.push(this.data[i]);
if (i + 1 === dataSize) break;
}
return result;
}
set(data) {
this.data = data ? JSON.parse(JSON.stringify(data)) : [];
}
getDataSize() {
return this.data.length;
}
getRowByRowId(rowId) {
return this.get().find(row => _.get(row, this.keyField) === rowId);
}
setSelectedRowKeys(selectedKeys) {
this.selected = selectedKeys;
}
getSelectedRows() {
return this.selected.map(k => this.getRowByRowId(k));
}
getSelectedRowKeys() {
return this.selected;
}
selectAllRows(nonSelectableRows = []) {
if (nonSelectableRows.length === 0) {
return this.data.map(row => _.get(row, this.keyField));
}
return this.data
.filter(row => !nonSelectableRows.includes(_.get(row, this.keyField)))
.map(row => _.get(row, this.keyField));
}
cleanSelectedRows(nonSelectableRows = []) {
if (nonSelectableRows.length === 0) {
return [];
}
return this.selected.filter(x => nonSelectableRows.includes(x));
}
isAllRowsSelected() {
return this.data.length === this.selected.length;
}
isAnySelectedRow(nonSelectableRows = []) {
if (nonSelectableRows.length === 0) {
return this.selected.length > 0;
}
return this.selected.filter(x => !nonSelectableRows.includes(x)).length;
}
}

View File

@@ -0,0 +1,41 @@
/* eslint no-underscore-dangle: 0 */
import _ from '../utils';
import { sort, nextOrder } from './sort';
import { getRowByRowId } from './rows';
export default class Store {
constructor(keyField) {
this._data = [];
this._keyField = keyField;
this._sortOrder = undefined;
this._sortField = undefined;
this._selected = [];
}
edit(rowId, dataField, newValue) {
const row = getRowByRowId(this)(rowId);
if (row) _.set(row, dataField, newValue);
}
sortBy({ dataField, sortFunc }, order) {
this.sortOrder = nextOrder(this)(dataField, order);
this.sortField = dataField;
this.data = sort(this)(sortFunc);
}
get data() { return this._data; }
set data(data) { this._data = (data ? JSON.parse(JSON.stringify(data)) : []); }
get keyField() { return this._keyField; }
set keyField(keyField) { this._keyField = keyField; }
get sortOrder() { return this._sortOrder; }
set sortOrder(sortOrder) { this._sortOrder = sortOrder; }
get sortField() { return this._sortField; }
set sortField(sortField) { this._sortField = sortField; }
get selected() { return this._selected; }
set selected(selected) { this._selected = selected; }
}

View File

@@ -0,0 +1,4 @@
export const matchRow = (keyField, id) => row => row[keyField] === id;
export const getRowByRowId = ({ data, keyField }) => id => data.find(matchRow(keyField, id));

View File

@@ -0,0 +1,33 @@
import _ from '../utils';
import { getRowByRowId } from './rows';
export const isSelectedAll = ({ data, selected }) => data.length === selected.length;
export const isAnySelectedRow = ({ selected }) => (skips = []) => {
if (skips.length === 0) {
return selected.length > 0;
}
return selected.filter(x => !skips.includes(x)).length;
};
export const selectableKeys = ({ data, keyField }) => (skips = []) => {
if (skips.length === 0) {
return data.map(row => _.get(row, keyField));
}
return data
.filter(row => !skips.includes(_.get(row, keyField)))
.map(row => _.get(row, keyField));
};
export const unSelectableKeys = ({ selected }) => (skips = []) => {
if (skips.length === 0) {
return [];
}
return selected.filter(x => skips.includes(x));
};
export const getSelectedRows = (store) => {
const getRow = getRowByRowId(store);
return store.selected.map(k => getRow(k));
};

View File

@@ -14,19 +14,19 @@ function comparator(a, b) {
return result;
}
const sort = (dataField, data, order, sortFunc) => {
export const sort = ({ data, sortOrder, sortField }) => (sortFunc) => {
const _data = [...data];
_data.sort((a, b) => {
let result;
let valueA = _.get(a, dataField);
let valueB = _.get(b, dataField);
let valueA = _.get(a, sortField);
let valueB = _.get(b, sortField);
valueA = _.isDefined(valueA) ? valueA : '';
valueB = _.isDefined(valueB) ? valueB : '';
if (sortFunc) {
result = sortFunc(valueA, valueB, order, dataField);
result = sortFunc(valueA, valueB, sortOrder, sortField);
} else {
if (order === Const.SORT_DESC) {
if (sortOrder === Const.SORT_DESC) {
result = comparator(valueA, valueB);
} else {
result = comparator(valueB, valueA);
@@ -37,4 +37,11 @@ const sort = (dataField, data, order, sortFunc) => {
return _data;
};
export { sort };
export const nextOrder = store => (field, order) => {
if (order) return order;
if (field !== store.sortField) {
return Const.SORT_DESC;
}
return store.sortOrder === Const.SORT_DESC ? Const.SORT_ASC : Const.SORT_DESC;
};

View File

@@ -3,7 +3,7 @@ import sinon from 'sinon';
import { shallow } from 'enzyme';
import Caption from '../src/caption';
import Store from '../src/store/base';
import Store from '../src/store';
import Header from '../src/header';
import Body from '../src/body';
import BootstrapTable from '../src/bootstrap-table';
@@ -27,7 +27,8 @@ describe('BootstrapTable', () => {
name: 'B'
}];
const store = new Store({ data });
const store = new Store('id');
store.data = data;
describe('simplest table', () => {
beforeEach(() => {
@@ -44,7 +45,7 @@ describe('BootstrapTable', () => {
it('should have correct default state', () => {
expect(wrapper.state().data).toBeDefined();
expect(wrapper.state().data).toEqual(store.get());
expect(wrapper.state().data).toEqual(store.data);
});
it('should have table-bordered class as default', () => {

View File

@@ -2,7 +2,7 @@ import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import Store from '../../src/store/base';
import Store from '../../src/store';
import BootstrapTable from '../../src/bootstrap-table';
import CellEditWrapper from '../../src/cell-edit/wrapper';
@@ -31,7 +31,8 @@ describe('CellEditWrapper', () => {
const keyField = 'id';
const store = new Store({ data, keyField });
const store = new Store(keyField);
store.data = data;
beforeEach(() => {
wrapper = shallow(

View File

@@ -6,6 +6,7 @@ import BootstrapTable from '../src';
import SortWrapper from '../src/sort/wrapper';
import CellEditWrapper from '../src/cell-edit/wrapper';
import RowSelectionWrapper from '../src/row-selection/wrapper';
import { getRowByRowId } from '../src/store/rows';
describe('withDataStore', () => {
let wrapper;
@@ -42,7 +43,7 @@ describe('withDataStore', () => {
it('should creating store successfully', () => {
const store = wrapper.instance().store;
expect(store).toBeDefined();
expect(store.get()).toEqual(data);
expect(store.data).toEqual(data);
expect(store.keyField).toEqual(keyField);
});
});
@@ -90,7 +91,7 @@ describe('withDataStore', () => {
it('should update store data directly', () => {
const store = wrapper.instance().store;
const row = store.getRowByRowId(rowId);
const row = getRowByRowId(store)(rowId);
expect(row[dataField]).toEqual(newValue);
});
});
@@ -120,7 +121,7 @@ describe('withDataStore', () => {
it('shouldn\'t update store data', () => {
const store = wrapper.instance().store;
const row = store.getRowByRowId(rowId);
const row = getRowByRowId(store)(rowId);
expect(row[dataField]).not.toEqual(newValue);
});
});

View File

@@ -2,7 +2,7 @@ import React from 'react';
import sinon from 'sinon';
import { shallow } from 'enzyme';
import Store from '../../src/store/base';
import Store from '../../src/store';
import BootstrapTable from '../../src/bootstrap-table';
import RowSelectionWrapper from '../../src/row-selection/wrapper';
@@ -33,7 +33,8 @@ describe('RowSelectionWrapper', () => {
const keyField = 'id';
const store = new Store({ data, keyField });
const store = new Store(keyField);
store.data = data;
beforeEach(() => {
wrapper = shallow(

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { shallow, mount } from 'enzyme';
import Const from '../../src/const';
import Store from '../../src/store/base';
import Store from '../../src/store';
import BootstrapTable from '../../src/bootstrap-table';
import SortWrapper from '../../src/sort/wrapper';
@@ -30,7 +30,8 @@ describe('SortWrapper', () => {
const keyField = 'id';
let store = new Store({ data, keyField });
let store = new Store(keyField);
store.data = data;
beforeEach(() => {
wrapper = shallow(
@@ -56,7 +57,8 @@ describe('SortWrapper', () => {
const sortColumn = columns[0];
beforeEach(() => {
store = new Store({ data, keyField });
store = new Store(keyField);
store.data = data;
wrapper = mount(
<SortWrapper
keyField={ keyField }
@@ -101,7 +103,7 @@ describe('SortWrapper', () => {
});
it('should render table with correct default sorted', () => {
expect(wrapper.props().data).toEqual(store.get());
expect(wrapper.props().data).toEqual(store.data);
});
it('should update store.sortField correctly', () => {

View File

@@ -1,245 +0,0 @@
import Base from '../../src/store/base';
import Const from '../../src/const';
import _ from '../../src/utils';
describe('Store Base', () => {
let store;
let data;
beforeEach(() => {
data = [
{ id: 3, name: 'name2' },
{ id: 2, name: 'ABC' },
{ id: 4, name: '123tester' },
{ id: 1, name: '!@#' }
];
store = new Base({ data, keyField: 'id' });
});
describe('initialize', () => {
it('should have correct initialize data', () => {
expect(store.sortOrder).toBeUndefined();
expect(store.sortField).toBeUndefined();
expect(store.data.length).toEqual(data.length);
});
});
describe('isEmpty', () => {
beforeEach(() => {
store = new Base({ data: [] });
});
it('should have correct initialize data', () => {
expect(store.isEmpty()).toBeTruthy();
});
});
describe('sortBy', () => {
let dataField;
beforeEach(() => {
dataField = 'name';
});
it('should change sortField by dataField param', () => {
store.sortBy({ dataField });
expect(store.sortField).toEqual(dataField);
});
it('should change sortOrder correctly when sortBy same dataField', () => {
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_ASC);
});
it('should change sortOrder correctly when sortBy different dataField', () => {
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'id';
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'name';
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
it('should have correct result after sortBy', () => {
store.sortBy({ dataField });
const result = store.data.map(e => e[dataField]).sort((a, b) => b - a);
store.get().forEach((e, i) => {
expect(e[dataField]).toEqual(result[i]);
});
});
it('should force assign sortOrder correctly if second argument is passed', () => {
store.sortBy({ dataField }, Const.SORT_DESC);
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
});
describe('getRowByRowId', () => {
it('should return data if specified id existing', () => {
const result = store.getRowByRowId(3);
expect(result).toBeDefined();
});
it('should return undefined if specified id existing', () => {
const result = store.getRowByRowId(88);
expect(result).toBeUndefined();
});
});
describe('getSelectedRows', () => {
const selected = [1, 4];
beforeEach(() => {
store.setSelectedRowKeys(selected);
});
it('should return all selected rows by store.selected', () => {
const result = store.getSelectedRows();
expect(result).toBeDefined();
expect(result.length).toEqual(2);
result.forEach((r) => {
expect(r).toBeDefined();
expect(selected.includes(r[store.keyField])).toBeTruthy();
});
});
});
describe('setSelectedRowKeys', () => {
const selected = [1, 4];
it('should set store.selected correctly', () => {
store.setSelectedRowKeys(selected);
expect(store.getSelectedRowKeys()).toEqual(selected);
});
});
describe('edit', () => {
it('should update a specified field correctly', () => {
const newValue = 'newValue';
const dataField = 'name';
const rowId = 2;
store.edit(rowId, dataField, newValue);
const row = store.data.find(d => d[store.keyField] === rowId);
expect(row[dataField]).toEqual(newValue);
});
it('should not throw any error even if rowId is not existing', () => {
expect(() => {
store.edit('123', 'name', 'value');
}).not.toThrow();
});
it('should throwing error if dataField is not existing', () => {
expect(() => {
store.edit(2, 'non_exist_field', 'value');
}).toThrow();
});
});
describe('selectAllRows', () => {
it('should return all row keys', () => {
const rowKeys = store.selectAllRows();
expect(Array.isArray(rowKeys)).toBeTruthy();
expect(rowKeys).toEqual(data.map(x => x[store.keyField]));
});
it('should return correct row keys when nonSelectableRows args is not empty', () => {
const nonSelectableRows = [1, 3];
const rowKeys = store.selectAllRows(nonSelectableRows);
expect(Array.isArray(rowKeys)).toBeTruthy();
expect(rowKeys).toEqual(
data
.filter(x => !nonSelectableRows.includes(_.get(x, store.keyField)))
.map(x => x[store.keyField])
);
});
});
describe('isAllRowsSelected', () => {
it('should return true when all rows is selected', () => {
store.selected = data.map(row => _.get(row, store.keyField));
expect(store.isAllRowsSelected()).toBeTruthy();
});
it('should return false when not all of rows is selected', () => {
store.selected = [1];
expect(store.isAllRowsSelected()).toBeFalsy();
});
});
describe('isAnySelectedRow', () => {
describe('if store.selected not empty', () => {
it('should return true', () => {
store.selected = data.map(row => _.get(row, store.keyField));
expect(store.isAnySelectedRow()).toBeTruthy();
});
describe('when nonSelectableRows given and not all of nonselectable rows are match current store.selected', () => {
const nonSelectableRows = [1, 3];
beforeEach(() => {
store.selected = [1, 4];
});
it('should return true', () => {
expect(store.isAnySelectedRow(nonSelectableRows)).toBeTruthy();
});
});
describe('when nonSelectableRows given and all of nonselectable rows are match current store.selected', () => {
const nonSelectableRows = [1, 3];
beforeEach(() => {
store.selected = nonSelectableRows;
});
it('should return false', () => {
expect(store.isAnySelectedRow(nonSelectableRows)).toBeFalsy();
});
});
});
it('should return false if store.selected is empty', () => {
store.selected = [];
expect(store.isAnySelectedRow()).not.toBeTruthy();
});
});
describe('getByCurrPage', () => {
beforeEach(() => {
data = [];
for (let i = 0; i < 100; i += 1) {
data.push({ id: i, name: `test_name${i}` });
}
store = new Base({ data, keyField: 'id' });
});
it('should always return correct data', () => {
[
// [page, sizePerPage, pageStartIndex]
[1, 10, 1],
[1, 25, 1],
[1, 30, 1],
[3, 30, 1],
[4, 30, 1],
[10, 10, 1],
[0, 10, 0],
[1, 10, 0],
[9, 10, 0]
].forEach(([page, sizePerPage, pageStartIndex]) => {
const rows = store.getByCurrPage(page, sizePerPage, pageStartIndex);
expect(rows).toBeDefined();
expect(Array.isArray(rows)).toBeTruthy();
expect(rows.every(row => !!row)).toBeTruthy();
});
});
});
});

View File

@@ -0,0 +1,96 @@
import Store from '../../src/store';
import Const from '../../src/const';
describe('Store Base', () => {
let store;
let data;
beforeEach(() => {
data = [
{ id: 3, name: 'name2' },
{ id: 2, name: 'ABC' },
{ id: 4, name: '123tester' },
{ id: 1, name: '!@#' }
];
store = new Store('id');
store.data = data;
});
describe('initialize', () => {
it('should have correct initialize data', () => {
expect(store.sortOrder).toBeUndefined();
expect(store.sortField).toBeUndefined();
expect(store.data.length).toEqual(data.length);
});
});
describe('sortBy', () => {
let dataField;
beforeEach(() => {
dataField = 'name';
});
it('should change sortField by dataField param', () => {
store.sortBy({ dataField });
expect(store.sortField).toEqual(dataField);
});
it('should change sortOrder correctly when sortBy same dataField', () => {
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_ASC);
});
it('should change sortOrder correctly when sortBy different dataField', () => {
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'id';
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
dataField = 'name';
store.sortBy({ dataField });
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
it('should have correct result after sortBy', () => {
store.sortBy({ dataField });
const result = store.data.map(e => e[dataField]).sort((a, b) => b - a);
store.data.forEach((e, i) => {
expect(e[dataField]).toEqual(result[i]);
});
});
it('should force assign sortOrder correctly if second argument is passed', () => {
store.sortBy({ dataField }, Const.SORT_DESC);
expect(store.sortOrder).toEqual(Const.SORT_DESC);
});
});
describe('edit', () => {
it('should update a specified field correctly', () => {
const newValue = 'newValue';
const dataField = 'name';
const rowId = 2;
store.edit(rowId, dataField, newValue);
const row = store.data.find(d => d[store.keyField] === rowId);
expect(row[dataField]).toEqual(newValue);
});
it('should not throw any error even if rowId is not existing', () => {
expect(() => {
store.edit('123', 'name', 'value');
}).not.toThrow();
});
it('should throwing error if dataField is not existing', () => {
expect(() => {
store.edit(2, 'non_exist_field', 'value');
}).toThrow();
});
});
});

View File

@@ -0,0 +1,30 @@
import Store from '../../src/store';
import { getRowByRowId } from '../../src/store/rows';
describe('Rows Function', () => {
const data = [
{ id: 3, name: 'name2' },
{ id: 2, name: 'ABC' },
{ id: 4, name: '123tester' },
{ id: 1, name: '!@#' }
];
const keyField = 'id';
let store;
let fn;
beforeEach(() => {
store = new Store(keyField);
store.data = data;
fn = getRowByRowId(store);
});
describe('getRowByRowId', () => {
it('should returning correct row', () => {
expect(fn(2)).toEqual(data[1]);
});
it('should returning undefined if not existing', () => {
expect(fn(20)).not.toBeDefined();
});
});
});

View File

@@ -0,0 +1,104 @@
import Store from '../../src/store';
import {
isSelectedAll,
isAnySelectedRow,
selectableKeys,
unSelectableKeys,
getSelectedRows
} from '../../src/store/selection';
describe('Selection Function', () => {
const data = [
{ id: 3, name: 'name2' },
{ id: 2, name: 'ABC' },
{ id: 4, name: '123tester' },
{ id: 1, name: '!@#' }
];
const keyField = 'id';
let store;
let skip;
let fn;
beforeEach(() => {
store = new Store(keyField);
store.data = data;
});
describe('isSelectedAll', () => {
it('should returning false when store.selected is not cover all rows', () => {
expect(isSelectedAll(store)).toBeFalsy();
store.selected = [data[0][keyField]];
expect(isSelectedAll(store)).toBeFalsy();
});
it('should returning true when store.selected is cover all rows', () => {
store.selected = data.map(d => d[keyField]);
expect(isSelectedAll(store)).toBeTruthy();
});
});
describe('isAnySelectedRow', () => {
it('should returning false if any store.selected is empty', () => {
fn = isAnySelectedRow(store);
expect(fn()).toBeFalsy();
});
it('should returning false if store.selected is have same key as skips', () => {
fn = isAnySelectedRow(store);
skip = [data[0][keyField]];
store.selected = [data[0][keyField]];
expect(fn(skip)).toBeFalsy();
});
it('should returning true if store.selected is not empty', () => {
store.selected = [data[0][keyField]];
fn = isAnySelectedRow(store);
expect(fn()).toBeTruthy();
});
it('should returning true if length of store.selected is bigger than skips', () => {
store.selected = [data[0][keyField], data[2][keyField]];
skip = [data[0][keyField]];
fn = isAnySelectedRow(store);
expect(fn(skip)).toBeTruthy();
});
});
describe('selectableKeys', () => {
beforeEach(() => {
fn = selectableKeys(store);
});
it('should returning all row keys if skip is empty', () => {
expect(fn()).toEqual(data.map(d => d[keyField]));
});
it('should returngin row keys expect the skip', () => {
skip = [data[1][keyField]];
expect(fn(skip)).toHaveLength(data.length - skip.length);
});
});
describe('unSelectableKeys', () => {
it('should returning empty array if skip is empty', () => {
fn = unSelectableKeys(store);
expect(fn()).toHaveLength(0);
});
it('should returning array which must contain skip', () => {
skip = [data[1][keyField]];
store.selected = data.map(d => d[keyField]);
fn = unSelectableKeys(store);
expect(fn(skip)).toHaveLength(skip.length);
});
});
describe('getSelectedRows', () => {
it('should returning rows object correctly', () => {
store.selected = data.map(d => d[keyField]);
const result = getSelectedRows(store);
expect(result).toHaveLength(store.selected.length);
expect(result).toEqual(store.data);
});
});
});

View File

@@ -1,6 +1,7 @@
import sinon from 'sinon';
import { sort } from '../../src/store/sort';
import Store from '../../src/store';
import { sort, nextOrder } from '../../src/store/sort';
import Const from '../../src/const';
describe('Sort Function', () => {
@@ -11,29 +12,65 @@ describe('Sort Function', () => {
{ id: 1, name: '!@#' }
];
it('should sort array with ASC order correctly', () => {
const result = sort('id', data, Const.SORT_ASC);
expect(result.length).toEqual(data.length);
let store;
const sortedArray = data.map(e => e.id).sort((a, b) => a - b);
sortedArray.forEach((e, i) => {
expect(e).toEqual(result[i].id);
describe('sort', () => {
beforeEach(() => {
store = new Store('id');
store.data = data;
});
it('should sort array with ASC order correctly', () => {
store.sortField = 'id';
store.sortOrder = Const.SORT_ASC;
const result = sort(store)();
expect(result.length).toEqual(data.length);
const sortedArray = data.map(e => e.id).sort((a, b) => a - b);
sortedArray.forEach((e, i) => {
expect(e).toEqual(result[i].id);
});
});
it('should sort array with DESC order correctly', () => {
store.sortField = 'id';
store.sortOrder = Const.SORT_DESC;
const result = sort(store)();
expect(result.length).toEqual(data.length);
const sortedArray = data.map(e => e.id).sort((a, b) => b - a);
sortedArray.forEach((e, i) => {
expect(e).toEqual(result[i].id);
});
});
it('should call custom sort function when sortFunc given', () => {
const sortFunc = sinon.stub().returns(1);
store.sortField = 'id';
store.sortOrder = Const.SORT_DESC;
sort(store)(sortFunc);
expect(sortFunc.callCount).toBe(6);
});
});
it('should sort array with DESC order correctly', () => {
const result = sort('id', data, Const.SORT_DESC);
expect(result.length).toEqual(data.length);
const sortedArray = data.map(e => e.id).sort((a, b) => b - a);
sortedArray.forEach((e, i) => {
expect(e).toEqual(result[i].id);
describe('nextOrder', () => {
beforeEach(() => {
store = new Store('id');
store.data = data;
});
});
it('should call custom sort function when sortFunc given', () => {
const sortFunc = sinon.stub().returns(1);
sort('id', data, Const.SORT_DESC, sortFunc);
expect(sortFunc.callCount).toBe(6);
it('should return correcly order when store.sortField is not eq next sort field', () => {
expect(nextOrder(store)('name')).toBe(Const.SORT_DESC);
});
it('should return correcly order when store.sortField is eq next sort field', () => {
store.sortField = 'name';
store.sortOrder = Const.SORT_DESC;
expect(nextOrder(store)('name')).toBe(Const.SORT_ASC);
});
it('should return correcly order when order is specified', () => {
expect(nextOrder(store)('name', Const.SORT_ASC)).toBe(Const.SORT_ASC);
});
});
});

View File

@@ -1,4 +1,4 @@
import Store from '../../src/store/base';
import Store from '../../src/store';
export const extendTo = Base =>
class MockComponent extends Base {
@@ -7,7 +7,8 @@ export const extendTo = Base =>
const { data } = props;
this.store = new Store(props);
this.store = new Store(props.keyField);
this.store.data = data;
this.state = { data };
}