Fix condition on processing message queues for sandy plugins

Summary:
While converting Bloks-Script plugin, Timur found a bug where the message queue wasn't processed.

Although queue processing was unit tested, the integration into the rendering lifecycle wasn't explicitly tested and missed a TODO that already signalled this should have been implemented.

Added a unit test to verify the bug and fix. Also tested in a running Flipper instance with the converted plugin (next diff)

Reviewed By: jknoxville

Differential Revision: D23263909

fbshipit-source-id: 63783c980247bdf6c93d00a46881d7d0eb291d09
This commit is contained in:
Michel Weststrate
2020-08-21 09:07:22 -07:00
committed by Facebook GitHub Bot
parent 6c7748238d
commit 76b72f3d77
2 changed files with 103 additions and 23 deletions

View File

@@ -48,6 +48,7 @@ import {Idler} from './utils/Idler';
import {processMessageQueue} from './utils/messageQueue'; import {processMessageQueue} from './utils/messageQueue';
import {ToggleButton, SmallText} from './ui'; import {ToggleButton, SmallText} from './ui';
import {SandyPluginRenderer} from 'flipper-plugin'; import {SandyPluginRenderer} from 'flipper-plugin';
import {isDevicePluginDefinition} from './utils/pluginUtils';
const Container = styled(FlexColumn)({ const Container = styled(FlexColumn)({
width: 0, width: 0,
@@ -193,24 +194,30 @@ class PluginContainer extends PureComponent<Props, State> {
pendingMessages, pendingMessages,
activePlugin, activePlugin,
pluginIsEnabled, pluginIsEnabled,
target,
} = this.props; } = this.props;
if (pluginKey !== this.pluginBeingProcessed) { if (pluginKey !== this.pluginBeingProcessed) {
this.pluginBeingProcessed = pluginKey; this.pluginBeingProcessed = pluginKey;
this.cancelCurrentQueue(); this.cancelCurrentQueue();
this.setState({progress: {current: 0, total: 0}}); this.setState({progress: {current: 0, total: 0}});
// device plugins don't have connections so no message queues
if (!activePlugin || isDevicePluginDefinition(activePlugin)) {
return;
}
if ( if (
pluginIsEnabled && pluginIsEnabled &&
target instanceof Client &&
activePlugin && activePlugin &&
// TODO: support sandy: T68683442 (isSandyPlugin(activePlugin) || activePlugin.persistedStateReducer) &&
!isSandyPlugin(activePlugin) &&
activePlugin.persistedStateReducer &&
pluginKey && pluginKey &&
pendingMessages?.length pendingMessages?.length
) { ) {
const start = Date.now(); const start = Date.now();
this.idler = new Idler(); this.idler = new Idler();
processMessageQueue( processMessageQueue(
activePlugin, isSandyPlugin(activePlugin)
? target.sandyPluginStates.get(activePlugin.id)!
: activePlugin,
pluginKey, pluginKey,
this.store, this.store,
(progress) => { (progress) => {

View File

@@ -98,11 +98,13 @@ test('PluginContainer can render Sandy plugins', async () => {
function MySandyPlugin() { function MySandyPlugin() {
renders++; renders++;
const sandyApi = usePlugin(plugin); const sandyApi = usePlugin(plugin);
const count = useValue(sandyApi.count);
expect(Object.keys(sandyApi)).toEqual([ expect(Object.keys(sandyApi)).toEqual([
'connectedStub', 'connectedStub',
'disconnectedStub', 'disconnectedStub',
'activatedStub', 'activatedStub',
'deactivatedStub', 'deactivatedStub',
'count',
]); ]);
expect(() => { expect(() => {
// eslint-disable-next-line // eslint-disable-next-line
@@ -110,10 +112,15 @@ test('PluginContainer can render Sandy plugins', async () => {
return {}; return {};
}); });
}).toThrowError(/didn't match the type of the requested plugin/); }).toThrowError(/didn't match the type of the requested plugin/);
return <div>Hello from Sandy</div>; return <div>Hello from Sandy{count}</div>;
} }
const plugin = (client: PluginClient) => { type Events = {
inc: {delta: number};
};
const plugin = (client: PluginClient<Events>) => {
const count = createState(0);
const connectedStub = jest.fn(); const connectedStub = jest.fn();
const disconnectedStub = jest.fn(); const disconnectedStub = jest.fn();
const activatedStub = jest.fn(); const activatedStub = jest.fn();
@@ -122,7 +129,16 @@ test('PluginContainer can render Sandy plugins', async () => {
client.onDisconnect(disconnectedStub); client.onDisconnect(disconnectedStub);
client.onActivate(activatedStub); client.onActivate(activatedStub);
client.onDeactivate(deactivatedStub); client.onDeactivate(deactivatedStub);
return {connectedStub, disconnectedStub, activatedStub, deactivatedStub}; client.onMessage('inc', ({delta}) => {
count.set(count.get() + delta);
});
return {
connectedStub,
disconnectedStub,
activatedStub,
deactivatedStub,
count,
};
}; };
const definition = new SandyPluginDefinition( const definition = new SandyPluginDefinition(
@@ -150,6 +166,7 @@ test('PluginContainer can render Sandy plugins', async () => {
> >
<div> <div>
Hello from Sandy Hello from Sandy
0
</div> </div>
</div> </div>
<div <div
@@ -161,11 +178,35 @@ test('PluginContainer can render Sandy plugins', async () => {
`); `);
expect(renders).toBe(1); expect(renders).toBe(1);
// sending a new message doesn't cause a re-render // sending irrelevant message does not cause a re-render
act(() => {
sendMessage('oops', {delta: 2});
});
expect(renders).toBe(1);
// sending a new message cause a re-render
act(() => { act(() => {
sendMessage('inc', {delta: 2}); sendMessage('inc', {delta: 2});
}); });
expect(renders).toBe(1); expect(renders).toBe(2);
expect(renderer.baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div
class="css-1orvm1g-View-FlexBox-FlexColumn"
>
<div>
Hello from Sandy
2
</div>
</div>
<div
class="css-bxcvv9-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</body>
`);
// make sure the plugin gets connected // make sure the plugin gets connected
const pluginInstance: ReturnType<typeof plugin> = client.sandyPluginStates.get( const pluginInstance: ReturnType<typeof plugin> = client.sandyPluginStates.get(
@@ -198,6 +239,14 @@ test('PluginContainer can render Sandy plugins', async () => {
expect(pluginInstance.activatedStub).toBeCalledTimes(1); expect(pluginInstance.activatedStub).toBeCalledTimes(1);
expect(pluginInstance.deactivatedStub).toBeCalledTimes(1); expect(pluginInstance.deactivatedStub).toBeCalledTimes(1);
// send some messages while in BG
act(() => {
sendMessage('inc', {delta: 3});
sendMessage('inc', {delta: 4});
});
expect(renders).toBe(2);
expect(pluginInstance.count.get()).toBe(2);
// go back // go back
act(() => { act(() => {
store.dispatch( store.dispatch(
@@ -207,6 +256,27 @@ test('PluginContainer can render Sandy plugins', async () => {
}), }),
); );
}); });
// Might be needed, but seems to work reliable without: await sleep(1000);
expect(renderer.baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div
class="css-1orvm1g-View-FlexBox-FlexColumn"
>
<div>
Hello from Sandy
9
</div>
</div>
<div
class="css-bxcvv9-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</body>
`);
expect(pluginInstance.count.get()).toBe(9);
expect(pluginInstance.connectedStub).toBeCalledTimes(2); expect(pluginInstance.connectedStub).toBeCalledTimes(2);
expect(pluginInstance.disconnectedStub).toBeCalledTimes(1); expect(pluginInstance.disconnectedStub).toBeCalledTimes(1);
expect(pluginInstance.activatedStub).toBeCalledTimes(2); expect(pluginInstance.activatedStub).toBeCalledTimes(2);
@@ -247,6 +317,9 @@ test('PluginContainer can render Sandy plugins', async () => {
client.sandyPluginStates.get('TestPlugin')!.instanceApi.connectedStub, client.sandyPluginStates.get('TestPlugin')!.instanceApi.connectedStub,
).toBeCalledTimes(1); ).toBeCalledTimes(1);
expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'}); expect(client.rawSend).toBeCalledWith('init', {plugin: 'TestPlugin'});
expect(
client.sandyPluginStates.get('TestPlugin')!.instanceApi.count.get(),
).toBe(0);
}); });
test('PluginContainer triggers correct lifecycles for background plugin', async () => { test('PluginContainer triggers correct lifecycles for background plugin', async () => {