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)}
+
+ ))}
+
+ );
}