createTablePlugin using persistedState (fixes #310)
Summary: Implementing persistedState for createTablePlugin, so it doesn't lose its data when switching back and forth between plugins. When `runInBackground` is set to true on the native side, createTablePlugins are now also capable of receiving new data in the background. Adds a test suite for createTablePlugin, to make sure it: - returns a FlipperPlugin - ID, title and icon are set correctly - the resetMethod clears the data - the persistedStateReducer correctly adds new data Reviewed By: passy Differential Revision: D12939848 fbshipit-source-id: 30048f3ce2bb98c83b0c165e48df72c8e28eadcd
This commit is contained in:
committed by
Facebook Github Bot
parent
21a18d3e53
commit
db42e8e970
90
src/__tests__/createTablePlugin.node.js
Normal file
90
src/__tests__/createTablePlugin.node.js
Normal file
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Copyright 2018-present Facebook.
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
* @format
|
||||
*/
|
||||
|
||||
import {createTablePlugin} from '../createTablePlugin.js';
|
||||
import {FlipperPlugin} from '../plugin.js';
|
||||
|
||||
const PROPS = {
|
||||
title: 'Plugin Title',
|
||||
id: 'pluginID',
|
||||
icon: 'icon',
|
||||
method: 'method',
|
||||
resetMethod: 'resetMethod',
|
||||
columns: {},
|
||||
columnSizes: {},
|
||||
renderSidebar: () => {},
|
||||
buildRow: () => {},
|
||||
};
|
||||
|
||||
test('createTablePlugin returns FlipperPlugin', () => {
|
||||
const tablePlugin = createTablePlugin({...PROPS});
|
||||
expect(tablePlugin.prototype).toBeInstanceOf(FlipperPlugin);
|
||||
});
|
||||
|
||||
test('Plugin ID is set', () => {
|
||||
const id = 'pluginID';
|
||||
const tablePlugin = createTablePlugin({...PROPS, id});
|
||||
expect(tablePlugin.id).toBe(id);
|
||||
});
|
||||
|
||||
test('Plugin title is set', () => {
|
||||
const title = 'My Plugin Title';
|
||||
const tablePlugin = createTablePlugin({...PROPS, title});
|
||||
expect(tablePlugin.title).toBe(title);
|
||||
});
|
||||
|
||||
test('Plugin icon is set', () => {
|
||||
const icon = 'icon';
|
||||
const tablePlugin = createTablePlugin({...PROPS, icon});
|
||||
expect(tablePlugin.icon).toBe(icon);
|
||||
});
|
||||
|
||||
test('persistedStateReducer is resetting data', () => {
|
||||
const resetMethod = 'resetMethod';
|
||||
const tablePlugin = createTablePlugin({...PROPS, resetMethod});
|
||||
|
||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
||||
{
|
||||
datas: {'1': {id: '1'}},
|
||||
rows: [
|
||||
{
|
||||
key: '1',
|
||||
columns: {
|
||||
id: {
|
||||
value: '1',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
resetMethod,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(datas).toEqual({});
|
||||
expect(rows).toEqual([]);
|
||||
});
|
||||
|
||||
test('persistedStateReducer is adding data', () => {
|
||||
const method = 'method';
|
||||
const tablePlugin = createTablePlugin({...PROPS, method});
|
||||
const id = '1';
|
||||
|
||||
// $FlowFixMe persistedStateReducer exists for createTablePlugin
|
||||
const {rows, datas} = tablePlugin.persistedStateReducer(
|
||||
{
|
||||
datas: {},
|
||||
rows: [],
|
||||
},
|
||||
method,
|
||||
{id},
|
||||
);
|
||||
|
||||
expect(rows.length).toBe(1);
|
||||
expect(Object.keys(datas)).toEqual([id]);
|
||||
});
|
||||
@@ -34,15 +34,14 @@ type Props<T> = {|
|
||||
buildRow: (row: T) => any,
|
||||
|};
|
||||
|
||||
type State<T> = {|
|
||||
type PersistedState<T> = {|
|
||||
rows: TableRows,
|
||||
datas: {[key: ID]: T},
|
||||
selectedIds: Array<ID>,
|
||||
|};
|
||||
|
||||
type AppendAndUpdateAction<T> = {|type: 'AppendAndUpdate', datas: Array<T>|};
|
||||
type ResetAndUpdateAction<T> = {|type: 'ResetAndUpdate', datas: Array<T>|};
|
||||
type Actions<T> = AppendAndUpdateAction<T> | ResetAndUpdateAction<T>;
|
||||
type State = {|
|
||||
selectedIds: Array<ID>,
|
||||
|};
|
||||
|
||||
/**
|
||||
* createTablePlugin creates a Plugin class which handles fetching data from the client and
|
||||
@@ -58,15 +57,52 @@ type Actions<T> = AppendAndUpdateAction<T> | ResetAndUpdateAction<T>;
|
||||
* the client in an unknown state.
|
||||
*/
|
||||
export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||
return class extends FlipperPlugin<State<T>, Actions<T>> {
|
||||
// $FlowFixMe persistedStateReducer is fine to accept payload of type T, because it is of type RowData
|
||||
return class extends FlipperPlugin<State, *, PersistedState<T>> {
|
||||
static title = props.title;
|
||||
static id = props.id;
|
||||
static icon = props.icon;
|
||||
static keyboardActions = ['clear', 'createPaste'];
|
||||
|
||||
state = {
|
||||
static defaultPersistedState: PersistedState<T> = {
|
||||
rows: [],
|
||||
datas: {},
|
||||
};
|
||||
|
||||
static persistedStateReducer = (
|
||||
persistedState: PersistedState<T>,
|
||||
method: string,
|
||||
payload: T | Array<T>,
|
||||
): $Shape<PersistedState<RowData>> => {
|
||||
if (method === props.method) {
|
||||
const newRows = [];
|
||||
const newData = {};
|
||||
const datas = Array.isArray(payload) ? payload : [payload];
|
||||
|
||||
for (const data of datas.reverse()) {
|
||||
if (data.id == null) {
|
||||
console.warn('The data sent did not contain an ID.', data);
|
||||
}
|
||||
if (persistedState.datas[data.id] == null) {
|
||||
newData[data.id] = data;
|
||||
newRows.push(props.buildRow(data));
|
||||
}
|
||||
}
|
||||
return {
|
||||
datas: {...persistedState.datas, ...newData},
|
||||
rows: [...persistedState.rows, ...newRows],
|
||||
};
|
||||
} else if (method === props.resetMethod) {
|
||||
return {
|
||||
rows: [],
|
||||
datas: {},
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
selectedIds: [],
|
||||
};
|
||||
|
||||
@@ -78,66 +114,12 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||
}
|
||||
};
|
||||
|
||||
reducers = {
|
||||
AppendAndUpdate(state: State<T>, action: AppendAndUpdateAction<T>) {
|
||||
const newRows = [];
|
||||
const newData = {};
|
||||
|
||||
for (const data of action.datas.reverse()) {
|
||||
if (data.id == null) {
|
||||
console.warn('The data sent did not contain an ID.');
|
||||
}
|
||||
if (this.state.datas[data.id] == null) {
|
||||
newData[data.id] = data;
|
||||
newRows.push(props.buildRow(data));
|
||||
}
|
||||
}
|
||||
return {
|
||||
datas: {...state.datas, ...newData},
|
||||
rows: [...state.rows, ...newRows],
|
||||
};
|
||||
},
|
||||
ResetAndUpdate(state: State<T>, action: ResetAndUpdateAction<T>) {
|
||||
const newRows = [];
|
||||
const newData = {};
|
||||
|
||||
for (const data of action.datas.reverse()) {
|
||||
if (data.id == null) {
|
||||
console.warn('The data sent did not contain an ID.');
|
||||
}
|
||||
if (this.state.datas[data.id] == null) {
|
||||
newData[data.id] = data;
|
||||
newRows.push(props.buildRow(data));
|
||||
}
|
||||
}
|
||||
return {
|
||||
datas: newData,
|
||||
rows: newRows,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
init() {
|
||||
this.client.subscribe(props.method, (data: T | Array<T>) => {
|
||||
this.dispatchAction({
|
||||
type: 'AppendAndUpdate',
|
||||
datas: data instanceof Array ? data : [data],
|
||||
});
|
||||
});
|
||||
if (props.resetMethod) {
|
||||
this.client.subscribe(props.resetMethod, (data: Array<T>) => {
|
||||
this.dispatchAction({
|
||||
type: 'ResetAndUpdate',
|
||||
datas: data instanceof Array ? data : [],
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clear = () => {
|
||||
this.setState({
|
||||
datas: {},
|
||||
this.props.setPersistedState({
|
||||
rows: [],
|
||||
datas: {},
|
||||
});
|
||||
this.setState({
|
||||
selectedIds: [],
|
||||
});
|
||||
};
|
||||
@@ -151,13 +133,13 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||
|
||||
if (this.state.selectedIds.length > 0) {
|
||||
// create paste from selection
|
||||
paste = this.state.rows
|
||||
paste = this.props.persistedState.rows
|
||||
.filter(row => this.state.selectedIds.indexOf(row.key) > -1)
|
||||
.map(mapFn)
|
||||
.join('\n');
|
||||
} else {
|
||||
// create paste with all rows
|
||||
paste = this.state.rows.map(mapFn).join('\n');
|
||||
paste = this.props.persistedState.rows.map(mapFn).join('\n');
|
||||
}
|
||||
createPaste(paste);
|
||||
};
|
||||
@@ -170,7 +152,8 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||
|
||||
renderSidebar = () => {
|
||||
const {renderSidebar} = props;
|
||||
const {datas, selectedIds} = this.state;
|
||||
const {selectedIds} = this.state;
|
||||
const {datas} = this.props.persistedState;
|
||||
const selectedId = selectedIds.length !== 1 ? null : selectedIds[0];
|
||||
|
||||
if (selectedId != null) {
|
||||
@@ -182,7 +165,7 @@ export function createTablePlugin<T: RowData>(props: Props<T>) {
|
||||
|
||||
render() {
|
||||
const {columns, columnSizes} = props;
|
||||
const {rows} = this.state;
|
||||
const {rows} = this.props.persistedState;
|
||||
|
||||
return (
|
||||
<FlexColumn grow={true}>
|
||||
|
||||
Reference in New Issue
Block a user