Fixed re-enabling a still selected plugin

Summary: While testing manually discovered the sandy plugin infra din't cover the case that a plugin can be selected but not enabled at the same time. Added test and fixed that.

Reviewed By: nikoant

Differential Revision: D22308597

fbshipit-source-id: 6cef2b543013ee81cee449396d523dd9a657ad1c
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent d16c6061c1
commit 581ddafd18
6 changed files with 69 additions and 31 deletions

View File

@@ -7,18 +7,17 @@
* @format * @format
*/ */
import React, {useContext} from 'react'; import React from 'react';
import produce from 'immer'; import produce from 'immer';
import {FlipperPlugin} from '../plugin'; import {FlipperPlugin} from '../plugin';
import {renderMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin'; import {renderMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin';
import { import {
SandyPluginContext,
SandyPluginDefinition, SandyPluginDefinition,
FlipperClient, FlipperClient,
TestUtils, TestUtils,
usePlugin, usePlugin,
} from 'flipper-plugin'; } from 'flipper-plugin';
import {selectPlugin} from '../reducers/connections'; import {selectPlugin, starPlugin} from '../reducers/connections';
interface PersistedState { interface PersistedState {
count: 1; count: 1;
@@ -132,6 +131,9 @@ test('PluginContainer can render Sandy plugins', async () => {
client, client,
store, store,
} = await renderMockFlipperWithPlugin(definition); } = await renderMockFlipperWithPlugin(definition);
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
expect(renderer.baseElement).toMatchInlineSnapshot(` expect(renderer.baseElement).toMatchInlineSnapshot(`
<body> <body>
<div> <div>
@@ -164,8 +166,6 @@ test('PluginContainer can render Sandy plugins', async () => {
expect(pluginInstance.connectedStub).toBeCalledTimes(1); expect(pluginInstance.connectedStub).toBeCalledTimes(1);
expect(pluginInstance.disconnectedStub).toBeCalledTimes(0); expect(pluginInstance.disconnectedStub).toBeCalledTimes(0);
// TODO: check that messages have arrived T68683442
// select non existing plugin // select non existing plugin
act(() => { act(() => {
store.dispatch( store.dispatch(
@@ -176,6 +176,8 @@ test('PluginContainer can render Sandy plugins', async () => {
); );
}); });
expect(client.rawSend).toBeCalledWith('deinit', {plugin: 'TestPlugin'});
expect(renderer.baseElement).toMatchInlineSnapshot(` expect(renderer.baseElement).toMatchInlineSnapshot(`
<body> <body>
<div /> <div />
@@ -195,4 +197,36 @@ test('PluginContainer can render Sandy plugins', async () => {
}); });
expect(pluginInstance.connectedStub).toBeCalledTimes(2); expect(pluginInstance.connectedStub).toBeCalledTimes(2);
expect(pluginInstance.disconnectedStub).toBeCalledTimes(1); expect(pluginInstance.disconnectedStub).toBeCalledTimes(1);
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
// disable
act(() => {
store.dispatch(
starPlugin({
plugin: definition,
selectedApp: client.query.app,
}),
);
});
expect(pluginInstance.connectedStub).toBeCalledTimes(2);
expect(pluginInstance.disconnectedStub).toBeCalledTimes(2);
expect(client.rawSend).toBeCalledWith('deinit', {plugin: 'TestPlugin'});
// re-enable
act(() => {
store.dispatch(
starPlugin({
plugin: definition,
selectedApp: client.query.app,
}),
);
});
// note: this is the old pluginInstance, so that one is not reconnected!
expect(pluginInstance.connectedStub).toBeCalledTimes(2);
expect(pluginInstance.disconnectedStub).toBeCalledTimes(2);
expect(
client.sandyPluginStates.get('TestPlugin')!.instanceApi.connectedStub,
).toBeCalledTimes(1);
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
}); });

View File

@@ -137,6 +137,7 @@ export async function createMockFlipperWithPlugin(
); );
} }
}; };
client.rawSend = jest.fn();
// enable the plugin // enable the plugin
if ( if (

View File

@@ -13,7 +13,7 @@ export {SandyPluginInstance, FlipperClient} from './plugin/Plugin';
export {SandyPluginDefinition} from './plugin/SandyPluginDefinition'; export {SandyPluginDefinition} from './plugin/SandyPluginDefinition';
export {SandyPluginRenderer} from './plugin/PluginRenderer'; export {SandyPluginRenderer} from './plugin/PluginRenderer';
export {SandyPluginContext, usePlugin} from './plugin/PluginContext'; export {SandyPluginContext, usePlugin} from './plugin/PluginContext';
export {createState as createValue, useValue, Atom} from './state/atom'; export {createState, useValue, Atom} from './state/atom';
// It's not ideal that this exists in flipper-plugin sources directly, // It's not ideal that this exists in flipper-plugin sources directly,
// but is the least pain for plugin authors. // but is the least pain for plugin authors.

View File

@@ -149,7 +149,11 @@ export class SandyPluginInstance {
// the plugin is deselected in the UI // the plugin is deselected in the UI
deactivate() { deactivate() {
this.assertNotDestroyed(); if (this.destroyed) {
// this can happen if the plugin is disabled while active in the UI.
// In that case deinit & destroy is already triggered from the STAR_PLUGIN action
return;
}
const pluginId = this.definition.id; const pluginId = this.definition.id;
if (!this.realClient.isBackgroundPlugin(pluginId)) { if (!this.realClient.isBackgroundPlugin(pluginId)) {
this.realClient.deinitPlugin(pluginId); this.realClient.deinitPlugin(pluginId);
@@ -174,7 +178,9 @@ export class SandyPluginInstance {
destroy() { destroy() {
this.assertNotDestroyed(); this.assertNotDestroyed();
this.disconnect(); if (this.connected) {
this.realClient.deinitPlugin(this.definition.id);
}
this.events.emit('destroy'); this.events.emit('destroy');
this.destroyed = true; this.destroyed = true;
} }

View File

@@ -18,24 +18,17 @@ type Props = {
/** /**
* Component to render a Sandy plugin container * Component to render a Sandy plugin container
*/ */
export const SandyPluginRenderer = memo( export const SandyPluginRenderer = memo(({plugin}: Props) => {
({plugin}: Props) => { useEffect(() => {
useEffect(() => { plugin.activate();
plugin.activate(); return () => {
return () => { plugin.deactivate();
plugin.deactivate(); };
}; }, [plugin]);
}, [plugin]);
return ( return (
<SandyPluginContext.Provider value={plugin}> <SandyPluginContext.Provider value={plugin}>
{createElement(plugin.definition.module.Component)} {createElement(plugin.definition.module.Component)}
</SandyPluginContext.Provider> </SandyPluginContext.Provider>
); );
}, });
() => {
// One of the goals of the ModernPluginContainer is that we want to prevent it from rendering
// for any outside change. Whatever happens outside of us, we don't care. If it is relevant for use, we take care about it from the insde
return true;
},
);

View File

@@ -117,8 +117,12 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
// as from testing perspective the difference shouldn't matter // as from testing perspective the difference shouldn't matter
return false; return false;
}, },
initPlugin(_pluginId: string) {}, initPlugin(_pluginId: string) {
deinitPlugin(_pluginId: string) {}, pluginInstance.connect();
},
deinitPlugin(_pluginId: string) {
pluginInstance.disconnect();
},
call( call(
api: string, api: string,
method: string, method: string,
@@ -131,7 +135,7 @@ export function startPlugin<Module extends FlipperPluginModule<any>>(
const pluginInstance = new SandyPluginInstance(fakeFlipper, definition); const pluginInstance = new SandyPluginInstance(fakeFlipper, definition);
// we start connected // we start connected
pluginInstance.connect(); pluginInstance.activate();
const res: StartPluginResult<Module> = { const res: StartPluginResult<Module> = {
module, module,