From 91d5b9534112f786a458d09698714baaf3c747ce Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 17 Dec 2019 06:01:20 -0800 Subject: [PATCH] make it easier to test plugin logic Summary: This diff will make it easier to test a plugin in a "fully loaded" Flipper state. This makes it possible to test all the wiring of flipper, rather than the individual reducers. This is in preparation of processing the message queue async. Reviewed By: passy Differential Revision: D19140879 fbshipit-source-id: 5a333abe9c7a4d0c33d1d06a105cd094cb8fc19f --- src/Client.tsx | 2 +- .../createMockFlipperWithPlugin.node.tsx.snap | 57 +++++++++ .../createMockFlipperWithPlugin.node.tsx | 63 ++++++++++ .../createMockFlipperWithPlugin.tsx | 113 ++++++++++++++++++ 4 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap create mode 100644 src/__tests__/createMockFlipperWithPlugin.node.tsx create mode 100644 src/test-utils/createMockFlipperWithPlugin.tsx diff --git a/src/Client.tsx b/src/Client.tsx index f4d094799..3001fa4c7 100644 --- a/src/Client.tsx +++ b/src/Client.tsx @@ -207,7 +207,7 @@ export default class Client extends EventEmitter { const client = this; // node.js doesn't support requestIdleCallback this.rIC = - typeof window === 'undefined' + typeof window === 'undefined' || !window.requestIdleCallback ? (cb: Function, _: any) => { cb(); } diff --git a/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap b/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap new file mode 100644 index 000000000..627a75731 --- /dev/null +++ b/src/__tests__/__snapshots__/createMockFlipperWithPlugin.node.tsx.snap @@ -0,0 +1,57 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`can create a Fake flipper 1`] = ` +Object { + "androidEmulators": Array [], + "clients": Array [ + Object { + "id": "TestApp#Android#unit_test#serial", + "query": Object { + "app": "TestApp", + "device": "unit_test", + "device_id": "serial", + "os": "Android", + }, + }, + ], + "deepLinkPayload": null, + "devices": Array [ + Object { + "deviceType": "physical", + "logs": Array [], + "os": "Android", + "serial": "serial", + "title": "title", + }, + ], + "errors": Array [], + "selectedApp": "TestApp#Android#unit_test#serial", + "selectedDevice": Object { + "deviceType": "physical", + "logs": Array [], + "os": "Android", + "serial": "serial", + "title": "title", + }, + "selectedPlugin": "TestPlugin", + "staticView": null, + "uninitializedClients": Array [], + "userPreferredApp": null, + "userPreferredDevice": null, + "userPreferredPlugin": "TestPlugin", + "userStarredPlugins": Object {}, +} +`; + +exports[`can create a Fake flipper 2`] = ` +Object { + "clientPlugins": Map { + "TestPlugin" => [Function], + }, + "devicePlugins": Map {}, + "disabledPlugins": Array [], + "failedPlugins": Array [], + "gatekeepedPlugins": Array [], + "selectedPlugins": Array [], +} +`; diff --git a/src/__tests__/createMockFlipperWithPlugin.node.tsx b/src/__tests__/createMockFlipperWithPlugin.node.tsx new file mode 100644 index 000000000..00de2968c --- /dev/null +++ b/src/__tests__/createMockFlipperWithPlugin.node.tsx @@ -0,0 +1,63 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {createMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin'; +import {FlipperPlugin} from '../plugin'; + +interface PersistedState { + count: 1; +} + +class TestPlugin extends FlipperPlugin { + static id = 'TestPlugin'; + + static defaultPersistedState = { + count: 0, + }; + + static persistedStateReducer( + persistedState: PersistedState, + method: string, + _payload: {}, + ) { + if (method === 'inc') { + return Object.assign({}, persistedState, { + count: persistedState.count + 1, + }); + } + return persistedState; + } + + render() { + return null; + } +} + +test('can create a Fake flipper', async () => { + await createMockFlipperWithPlugin( + TestPlugin, + async ({client, device, store, sendMessage}) => { + expect(client).toBeTruthy(); + expect(device).toBeTruthy(); + expect(store).toBeTruthy(); + expect(sendMessage).toBeTruthy(); + expect(client.plugins.includes(TestPlugin.id)).toBe(true); + expect(store.getState().connections).toMatchSnapshot(); + expect(store.getState().plugins).toMatchSnapshot(); + sendMessage('inc', {}); + expect(store.getState().pluginStates).toMatchInlineSnapshot(` + Object { + "TestApp#Android#unit_test#serial#TestPlugin": Object { + "count": 1, + }, + } + `); + }, + ); +}); diff --git a/src/test-utils/createMockFlipperWithPlugin.tsx b/src/test-utils/createMockFlipperWithPlugin.tsx new file mode 100644 index 000000000..c15843716 --- /dev/null +++ b/src/test-utils/createMockFlipperWithPlugin.tsx @@ -0,0 +1,113 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import {createStore} from 'redux'; + +import {selectPlugin} from '../reducers/connections'; +import BaseDevice from '../devices/BaseDevice'; + +import reducers, {Store} from '../reducers/index'; +import Client, {ClientQuery} from '../Client'; + +import {buildClientId} from '../utils/clientUtils'; +import {Logger} from '../fb-interfaces/Logger'; +import {FlipperPlugin} from '../plugin'; +import {registerPlugins} from '../reducers/plugins'; + +export function createStubLogger(): Logger { + return { + ...console, + track: console.info, + trackTimeSince: console.info, + }; +} + +export async function createMockFlipperWithPlugin( + pluginClazz: typeof FlipperPlugin, + callback: (args: { + client: Client; + device: BaseDevice; + store: Store; + sendMessage(method: string, params: any): void; + }) => Promise, +) { + const store = createStore(reducers); + const device = new BaseDevice('serial', 'physical', 'title', 'Android'); + const logger = createStubLogger(); + + store.dispatch(registerPlugins([pluginClazz])); + + store.dispatch({ + type: 'REGISTER_DEVICE', + payload: device, + }); + + const query: ClientQuery = { + app: 'TestApp', + os: 'Android', + device: 'unit_test', + device_id: device.serial, + }; + const id = buildClientId({ + app: query.app, + os: query.os, + device: query.device, + device_id: query.device_id, + }); + + const client = new Client( + id, + query, + null, // create a stub connection to avoid this plugin to be archived? + logger, + store, + [pluginClazz.name], + device, + ); + + // yikes + client._deviceSet = true; + // client.getDeviceSync = () => device; + client.device = { + then() { + return device; + }, + } as any; + + store.dispatch({ + type: 'NEW_CLIENT', + payload: client, + }); + + store.dispatch( + selectPlugin({ + selectedPlugin: pluginClazz.name, + selectedApp: client.query.app, + deepLinkPayload: null, + }), + ); + + await callback({ + client, + device: device as any, + store, + sendMessage(method, params) { + client.onMessage( + JSON.stringify({ + method: 'execute', + params: { + api: pluginClazz.id, + method, + params, + }, + }), + ); + }, + }); +}