From dc82ec28852445c8a317d08bc3ca5869b42488b5 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Mon, 16 Nov 2020 13:08:05 -0800 Subject: [PATCH] flipper-pkg template will now use sandy Summary: This diff updates plugin scaffolding by using Sandy and closely following the internal Scarf templates (see D24949452, D24949452) By using `flipper-plugin`, it is now also possible to write unit tests for a plugin, and the default infra for that is generated (babel / jest) For now there is still a dependency on `flipper` to support fancy components not yet available in Sandy, this will be updated in the future: T79632585 Changelog: `flipper-pkg init` now uses the new Sandy plugin infrastructure ant Ant.design component system Reviewed By: nikoant Differential Revision: D24950080 fbshipit-source-id: afc5e7ac728b20cb84fdbbdcb76cd45968736c01 --- desktop/pkg/src/__tests__/runInit.node.ts | 144 +++++++++++++----- .../templates/plugin/babel.config.js.template | 7 + .../templates/plugin/package.json.template | 15 +- .../src/__tests__/test.spec.tsx.template | 44 ++++++ .../templates/plugin/src/index.tsx.template | 78 +++++----- 5 files changed, 207 insertions(+), 81 deletions(-) create mode 100644 desktop/pkg/templates/plugin/babel.config.js.template create mode 100644 desktop/pkg/templates/plugin/src/__tests__/test.spec.tsx.template diff --git a/desktop/pkg/src/__tests__/runInit.node.ts b/desktop/pkg/src/__tests__/runInit.node.ts index 7096d42c8..f0d92c0b1 100644 --- a/desktop/pkg/src/__tests__/runInit.node.ts +++ b/desktop/pkg/src/__tests__/runInit.node.ts @@ -43,6 +43,14 @@ test('It generates the correct files', async () => { Object { "/dev/null/.gitignore": "node_modules dist/ + ", + "/dev/null/babel.config.js": "module.exports = { + presets: [ + '@babel/preset-typescript', + '@babel/preset-react', + ['@babel/preset-env', {targets: {node: 'current'}}] + ], + }; ", "/dev/null/package.json": "{ \\"$schema\\": \\"https://fbflipper.com/schemas/plugin-package/v2.json\\", @@ -61,65 +69,121 @@ test('It generates the correct files', async () => { \\"lint\\": \\"flipper-pkg lint\\", \\"prepack\\": \\"flipper-pkg lint && flipper-pkg bundle\\", \\"build\\": \\"flipper-pkg bundle\\", - \\"watch\\": \\"flipper-pkg bundle --watch\\" + \\"watch\\": \\"flipper-pkg bundle --watch\\", + \\"test\\": \\"jest\\" }, \\"peerDependencies\\": { - \\"flipper\\": \\"latest\\" + \\"flipper\\": \\"latest\\", + \\"flipper-plugin\\": \\"latest\\", + \\"antd\\": \\"latest\\" }, \\"devDependencies\\": { + \\"@babel/preset-react\\": \\"latest\\", + \\"@babel/preset-typescript\\": \\"latest\\", + \\"@types/jest\\": \\"latest\\", \\"@types/react\\": \\"latest\\", \\"@types/react-dom\\": \\"latest\\", + \\"antd\\": \\"latest\\", \\"flipper\\": \\"latest\\", - \\"flipper-pkg\\": \\"latest\\" + \\"flipper-plugin\\": \\"latest\\", + \\"flipper-pkg\\": \\"latest\\", + \\"jest\\": \\"latest\\" } } + ", + "/dev/null/src/__tests__/test.spec.tsx": "import {TestUtils} from 'flipper-plugin'; + import * as Plugin from '..'; + + // Read more: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic + // API: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic + test('It can store data', () => { + const {instance, sendEvent} = TestUtils.startPlugin(Plugin); + + expect(instance.data.get()).toEqual({}); + + sendEvent('newData', {id: 'firstID'}); + sendEvent('newData', {id: 'secondID'}); + + expect(instance.data.get()).toMatchInlineSnapshot(\` + Object { + \\"firstID\\": Object { + \\"id\\": \\"firstID\\", + }, + \\"secondID\\": Object { + \\"id\\": \\"secondID\\", + }, + } + \`); + }); + + // Read more: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic + // API: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic + test('It can render data', async () => { + const {instance, renderer, sendEvent} = TestUtils.renderPlugin(Plugin); + + expect(instance.data.get()).toEqual({}); + + sendEvent('newData', {id: 'firstID'}); + sendEvent('newData', {id: 'secondID'}); + + expect(await renderer.findByTestId('firstID')).not.toBeNull(); + expect(await renderer.findByTestId('secondID')).toMatchInlineSnapshot(\` +
+          {\\"id\\":\\"secondID\\"}
+        
+ \`); + }); ", "/dev/null/src/index.tsx": "import React from 'react'; - import {FlipperPlugin, View, KeyboardActions} from 'flipper'; + import {PluginClient, usePlugin, createState, useValue, Layout} from 'flipper-plugin'; - type State = {}; - - type Data = {}; - - type PersistedState = { - data: Array; + type Data = { + id: string; + message?: string; }; - export default class extends FlipperPlugin { - static keyboardActions: KeyboardActions = ['clear']; + type Events = { + newData: Data; + }; - static defaultPersistedState: PersistedState = { - data: [], - }; + // Read more: https://fbflipper.com/docs/extending/desktop-plugins#creating-a-first-plugin + // API: https://fbflipper.com/docs/extending/flipper-plugin#pluginclient + export function plugin(client: PluginClient) { + const data = createState>({}, {persist: 'data'}); - static persistedStateReducer = ( - persistedState: PersistedState, - method: string, - data: Data, - ): PersistedState => { - return { - ...persistedState, - data: persistedState.data.concat([data]), - }; - }; + client.onMessage('newData', (newData) => { + data.update((draft) => { + draft[newData.id] = newData; + }); + }); - state = {}; + client.addMenuEntry({ + action: 'clear', + handler: async () => { + data.set({}); + }, + }); - onKeyboardAction = (action: string) => { - if (action === 'clear') { - this.props.setPersistedState({data: []}); - } - }; + return {data}; + } - render() { - return ( - - {this.props.persistedState.data.map((d) => ( -
{JSON.stringify(d, null, 2)}
- ))} -
- ) - } + // Read more: https://fbflipper.com/docs/extending/desktop-plugins#building-a-user-interface-for-the-plugin + // API: https://fbflipper.com/docs/extending/flipper-plugin#react-hooks + export function Component() { + const instance = usePlugin(plugin); + const data = useValue(instance.data); + + return ( + + {Object.entries(data).map(([id, d]) => ( +
+              {JSON.stringify(d)}
+            
+ ))} +
+ ); } ", "/dev/null/tsconfig.json": "{ diff --git a/desktop/pkg/templates/plugin/babel.config.js.template b/desktop/pkg/templates/plugin/babel.config.js.template new file mode 100644 index 000000000..9bdd46258 --- /dev/null +++ b/desktop/pkg/templates/plugin/babel.config.js.template @@ -0,0 +1,7 @@ +module.exports = { + presets: [ + '@babel/preset-typescript', + '@babel/preset-react', + ['@babel/preset-env', {targets: {node: 'current'}}] + ], +}; diff --git a/desktop/pkg/templates/plugin/package.json.template b/desktop/pkg/templates/plugin/package.json.template index 84f5ddcb6..799c45143 100644 --- a/desktop/pkg/templates/plugin/package.json.template +++ b/desktop/pkg/templates/plugin/package.json.template @@ -15,15 +15,24 @@ "lint": "flipper-pkg lint", "prepack": "flipper-pkg lint && flipper-pkg bundle", "build": "flipper-pkg bundle", - "watch": "flipper-pkg bundle --watch" + "watch": "flipper-pkg bundle --watch", + "test": "jest" }, "peerDependencies": { - "flipper": "latest" + "flipper": "latest", + "flipper-plugin": "latest", + "antd": "latest" }, "devDependencies": { + "@babel/preset-react": "latest", + "@babel/preset-typescript": "latest", + "@types/jest": "latest", "@types/react": "latest", "@types/react-dom": "latest", + "antd": "latest", "flipper": "latest", - "flipper-pkg": "latest" + "flipper-plugin": "latest", + "flipper-pkg": "latest", + "jest": "latest" } } diff --git a/desktop/pkg/templates/plugin/src/__tests__/test.spec.tsx.template b/desktop/pkg/templates/plugin/src/__tests__/test.spec.tsx.template new file mode 100644 index 000000000..6214f5050 --- /dev/null +++ b/desktop/pkg/templates/plugin/src/__tests__/test.spec.tsx.template @@ -0,0 +1,44 @@ +import {TestUtils} from 'flipper-plugin'; +import * as Plugin from '..'; + +// Read more: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic +// API: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic +test('It can store data', () => { + const {instance, sendEvent} = TestUtils.startPlugin(Plugin); + + expect(instance.data.get()).toEqual({}); + + sendEvent('newData', {id: 'firstID'}); + sendEvent('newData', {id: 'secondID'}); + + expect(instance.data.get()).toMatchInlineSnapshot(` + Object { + "firstID": Object { + "id": "firstID", + }, + "secondID": Object { + "id": "secondID", + }, + } + `); +}); + +// Read more: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic +// API: https://fbflipper.com/docs/extending/desktop-plugins#testing-plugin-logic +test('It can render data', async () => { + const {instance, renderer, sendEvent} = TestUtils.renderPlugin(Plugin); + + expect(instance.data.get()).toEqual({}); + + sendEvent('newData', {id: 'firstID'}); + sendEvent('newData', {id: 'secondID'}); + + expect(await renderer.findByTestId('firstID')).not.toBeNull(); + expect(await renderer.findByTestId('secondID')).toMatchInlineSnapshot(` +
+      {"id":"secondID"}
+    
+ `); +}); diff --git a/desktop/pkg/templates/plugin/src/index.tsx.template b/desktop/pkg/templates/plugin/src/index.tsx.template index 3b20dcfe8..29a721b0b 100644 --- a/desktop/pkg/templates/plugin/src/index.tsx.template +++ b/desktop/pkg/templates/plugin/src/index.tsx.template @@ -1,47 +1,49 @@ import React from 'react'; -import {FlipperPlugin, View, KeyboardActions} from 'flipper'; +import {PluginClient, usePlugin, createState, useValue, Layout} from 'flipper-plugin'; -type State = {}; - -type Data = {}; - -type PersistedState = { - data: Array; +type Data = { + id: string; + message?: string; }; -export default class extends FlipperPlugin { - static keyboardActions: KeyboardActions = ['clear']; +type Events = { + newData: Data; +}; - static defaultPersistedState: PersistedState = { - data: [], - }; +// Read more: https://fbflipper.com/docs/extending/desktop-plugins#creating-a-first-plugin +// API: https://fbflipper.com/docs/extending/flipper-plugin#pluginclient +export function plugin(client: PluginClient) { + const data = createState>({}, {persist: 'data'}); - static persistedStateReducer = ( - persistedState: PersistedState, - method: string, - data: Data, - ): PersistedState => { - return { - ...persistedState, - data: persistedState.data.concat([data]), - }; - }; + client.onMessage('newData', (newData) => { + data.update((draft) => { + draft[newData.id] = newData; + }); + }); - state = {}; + client.addMenuEntry({ + action: 'clear', + handler: async () => { + data.set({}); + }, + }); - onKeyboardAction = (action: string) => { - if (action === 'clear') { - this.props.setPersistedState({data: []}); - } - }; - - render() { - return ( - - {this.props.persistedState.data.map((d) => ( -
{JSON.stringify(d, null, 2)}
- ))} -
- ) - } + return {data}; +} + +// Read more: https://fbflipper.com/docs/extending/desktop-plugins#building-a-user-interface-for-the-plugin +// API: https://fbflipper.com/docs/extending/flipper-plugin#react-hooks +export function Component() { + const instance = usePlugin(plugin); + const data = useValue(instance.data); + + return ( + + {Object.entries(data).map(([id, d]) => ( +
+          {JSON.stringify(d)}
+        
+ ))} +
+ ); }