Skip to content

Commit

Permalink
createTablePlugin using persistedState (fixes #310)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
danielbuechele authored and facebook-github-bot committed Nov 7, 2018
1 parent 21a18d3 commit db42e8e
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 61 deletions.
90 changes: 90 additions & 0 deletions src/__tests__/createTablePlugin.node.js
@@ -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]);
});
105 changes: 44 additions & 61 deletions src/createTablePlugin.js
Expand Up @@ -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
Expand All @@ -58,86 +57,69 @@ 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: {},
selectedIds: [],
};

onKeyboardAction = (action: string) => {
if (action === 'clear') {
this.clear();
} else if (action === 'createPaste') {
this.createPaste();
}
};

reducers = {
AppendAndUpdate(state: State<T>, action: AppendAndUpdateAction<T>) {
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 action.datas.reverse()) {
for (const data of datas.reverse()) {
if (data.id == null) {
console.warn('The data sent did not contain an ID.');
console.warn('The data sent did not contain an ID.', data);
}
if (this.state.datas[data.id] == null) {
if (persistedState.datas[data.id] == null) {
newData[data.id] = data;
newRows.push(props.buildRow(data));
}
}
return {
datas: {...state.datas, ...newData},
rows: [...state.rows, ...newRows],
datas: {...persistedState.datas, ...newData},
rows: [...persistedState.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));
}
}
} else if (method === props.resetMethod) {
return {
datas: newData,
rows: newRows,
rows: [],
datas: {},
};
},
} else {
return {};
}
};

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 : [],
});
});
state = {
selectedIds: [],
};

onKeyboardAction = (action: string) => {
if (action === 'clear') {
this.clear();
} else if (action === 'createPaste') {
this.createPaste();
}
}
};

clear = () => {
this.setState({
datas: {},
this.props.setPersistedState({
rows: [],
datas: {},
});
this.setState({
selectedIds: [],
});
};
Expand All @@ -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);
};
Expand All @@ -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) {
Expand All @@ -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}>
Expand Down

0 comments on commit db42e8e

Please sign in to comment.