From db42e8e9709748f70adbb5a90c8fcdda0bfdffd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20B=C3=BCchele?= Date: Wed, 7 Nov 2018 02:42:46 -0800 Subject: [PATCH] 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 --- src/__tests__/createTablePlugin.node.js | 90 ++++++++++++++++++ src/createTablePlugin.js | 121 ++++++++++-------------- 2 files changed, 142 insertions(+), 69 deletions(-) create mode 100644 src/__tests__/createTablePlugin.node.js diff --git a/src/__tests__/createTablePlugin.node.js b/src/__tests__/createTablePlugin.node.js new file mode 100644 index 000000000..fe61ec91e --- /dev/null +++ b/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]); +}); diff --git a/src/createTablePlugin.js b/src/createTablePlugin.js index 2fb3d1f93..a95ece39e 100644 --- a/src/createTablePlugin.js +++ b/src/createTablePlugin.js @@ -34,15 +34,14 @@ type Props = {| buildRow: (row: T) => any, |}; -type State = {| +type PersistedState = {| rows: TableRows, datas: {[key: ID]: T}, - selectedIds: Array, |}; -type AppendAndUpdateAction = {|type: 'AppendAndUpdate', datas: Array|}; -type ResetAndUpdateAction = {|type: 'ResetAndUpdate', datas: Array|}; -type Actions = AppendAndUpdateAction | ResetAndUpdateAction; +type State = {| + selectedIds: Array, +|}; /** * createTablePlugin creates a Plugin class which handles fetching data from the client and @@ -58,15 +57,52 @@ type Actions = AppendAndUpdateAction | ResetAndUpdateAction; * the client in an unknown state. */ export function createTablePlugin(props: Props) { - return class extends FlipperPlugin, Actions> { + // $FlowFixMe persistedStateReducer is fine to accept payload of type T, because it is of type RowData + return class extends FlipperPlugin> { static title = props.title; static id = props.id; static icon = props.icon; static keyboardActions = ['clear', 'createPaste']; - state = { + static defaultPersistedState: PersistedState = { rows: [], datas: {}, + }; + + static persistedStateReducer = ( + persistedState: PersistedState, + method: string, + payload: T | Array, + ): $Shape> => { + 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(props: Props) { } }; - reducers = { - AppendAndUpdate(state: State, action: AppendAndUpdateAction) { - 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, action: ResetAndUpdateAction) { - 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) => { - this.dispatchAction({ - type: 'AppendAndUpdate', - datas: data instanceof Array ? data : [data], - }); - }); - if (props.resetMethod) { - this.client.subscribe(props.resetMethod, (data: Array) => { - 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(props: Props) { 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(props: Props) { 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(props: Props) { render() { const {columns, columnSizes} = props; - const {rows} = this.state; + const {rows} = this.props.persistedState; return (