Make sure sandy device plugins are rendered

Summary: Now that we can load device plugins, let's make sure they are rendered as well.

Reviewed By: passy, nikoant

Differential Revision: D22693927

fbshipit-source-id: 22574ec6e629e6dd66e42193b406ceb7dfcf1836
This commit is contained in:
Michel Weststrate
2020-08-04 07:05:57 -07:00
committed by Facebook GitHub Bot
parent 489dd1521e
commit b621dcf754
4 changed files with 163 additions and 18 deletions

View File

@@ -343,7 +343,6 @@ class PluginContainer extends PureComponent<Props, State> {
} }
let pluginElement: null | React.ReactElement<any>; let pluginElement: null | React.ReactElement<any>;
if (isSandyPlugin(activePlugin)) { if (isSandyPlugin(activePlugin)) {
if (target instanceof Client) {
// Make sure we throw away the container for different pluginKey! // Make sure we throw away the container for different pluginKey!
pluginElement = ( pluginElement = (
<SandyPluginRenderer <SandyPluginRenderer
@@ -351,10 +350,6 @@ class PluginContainer extends PureComponent<Props, State> {
plugin={target.sandyPluginStates.get(activePlugin.id)!} plugin={target.sandyPluginStates.get(activePlugin.id)!}
/> />
); );
} else {
// TODO: target might be a device as well, support that T68738317
pluginElement = null;
}
} else { } else {
const props: PluginProps<Object> & { const props: PluginProps<Object> & {
key: string; key: string;

View File

@@ -17,6 +17,8 @@ import {
TestUtils, TestUtils,
usePlugin, usePlugin,
createState, createState,
DevicePluginClient,
DeviceLogEntry,
useValue, useValue,
} from 'flipper-plugin'; } from 'flipper-plugin';
import {selectPlugin, starPlugin} from '../reducers/connections'; import {selectPlugin, starPlugin} from '../reducers/connections';
@@ -367,3 +369,139 @@ test('PluginContainer + Sandy plugin supports deeplink', async () => {
}); });
expect(linksSeen).toEqual(['universe!', 'london!', 'london!']); expect(linksSeen).toEqual(['universe!', 'london!', 'london!']);
}); });
test('PluginContainer can render Sandy device plugins', async () => {
let renders = 0;
function MySandyPlugin() {
renders++;
const sandyApi = usePlugin(devicePlugin);
expect(Object.keys(sandyApi)).toEqual([
'activatedStub',
'deactivatedStub',
'lastLogMessage',
]);
expect(() => {
// eslint-disable-next-line
usePlugin(function bla() {
return {};
});
}).toThrowError(/didn't match the type of the requested plugin/);
const lastLogMessage = useValue(sandyApi.lastLogMessage);
return <div>Hello from Sandy: {lastLogMessage?.message}</div>;
}
const devicePlugin = (client: DevicePluginClient) => {
const lastLogMessage = createState<undefined | DeviceLogEntry>(undefined);
const activatedStub = jest.fn();
const deactivatedStub = jest.fn();
client.onActivate(activatedStub);
client.onDeactivate(deactivatedStub);
client.device.onLogEntry((e) => {
lastLogMessage.set(e);
});
return {activatedStub, deactivatedStub, lastLogMessage};
};
const definition = new SandyPluginDefinition(
TestUtils.createMockPluginDetails(),
{
supportsDevice: () => true,
devicePlugin,
Component: MySandyPlugin,
},
);
// any cast because this plugin is not enriched with the meta data that the plugin loader
// normally adds. Our further sandy plugin test infra won't need this, but
// for this test we do need to act a s a loaded plugin, to make sure PluginContainer itself can handle it
const {renderer, act, store, device} = await renderMockFlipperWithPlugin(
definition,
);
expect(renderer.baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div
class="css-1orvm1g-View-FlexBox-FlexColumn"
>
<div>
Hello from Sandy:
</div>
</div>
<div
class="css-bxcvv9-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</body>
`);
expect(renders).toBe(1);
act(() => {
device.addLogEntry({
date: new Date(),
message: 'helleuh',
pid: 0,
tid: 0,
type: 'info',
tag: 'test',
});
});
expect(renders).toBe(2);
expect(renderer.baseElement).toMatchInlineSnapshot(`
<body>
<div>
<div
class="css-1orvm1g-View-FlexBox-FlexColumn"
>
<div>
Hello from Sandy:
helleuh
</div>
</div>
<div
class="css-bxcvv9-View-FlexBox-FlexRow"
id="detailsSidebar"
/>
</div>
</body>
`);
// make sure the plugin gets connected
const pluginInstance: ReturnType<typeof devicePlugin> = device.sandyPluginStates.get(
definition.id,
)!.instanceApi;
expect(pluginInstance.activatedStub).toBeCalledTimes(1);
expect(pluginInstance.deactivatedStub).toBeCalledTimes(0);
// select non existing plugin
act(() => {
store.dispatch(
selectPlugin({
selectedPlugin: 'Logs',
deepLinkPayload: null,
}),
);
});
expect(renderer.baseElement).toMatchInlineSnapshot(`
<body>
<div />
</body>
`);
expect(pluginInstance.activatedStub).toBeCalledTimes(1);
expect(pluginInstance.deactivatedStub).toBeCalledTimes(1);
// go back
act(() => {
store.dispatch(
selectPlugin({
selectedPlugin: definition.id,
deepLinkPayload: null,
}),
);
});
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
expect(pluginInstance.deactivatedStub).toBeCalledTimes(1);
});

View File

@@ -216,12 +216,21 @@ export async function renderMockFlipperWithPlugin(
function selectTestPlugin(store: Store, client: Client) { function selectTestPlugin(store: Store, client: Client) {
store.dispatch( store.dispatch(
selectPlugin({ selectPlugin(
isDevicePluginDefinition(pluginClazz)
? {
selectedPlugin: pluginClazz.id,
selectedApp: null,
deepLinkPayload: null,
selectedDevice: store.getState().connections.selectedDevice!,
}
: {
selectedPlugin: pluginClazz.id, selectedPlugin: pluginClazz.id,
selectedApp: client.query.app, selectedApp: client.query.app,
deepLinkPayload: null, deepLinkPayload: null,
selectedDevice: store.getState().connections.selectedDevice!, selectedDevice: store.getState().connections.selectedDevice!,
}), },
),
); );
} }

View File

@@ -42,6 +42,7 @@ export type DevicePluginPredicate = (device: Device) => boolean;
export type DevicePluginFactory = (client: DevicePluginClient) => object; export type DevicePluginFactory = (client: DevicePluginClient) => object;
// TODO: better name?
export interface DevicePluginClient { export interface DevicePluginClient {
readonly device: Device; readonly device: Device;
@@ -59,6 +60,8 @@ export interface DevicePluginClient {
* The counterpart of the `onActivate` handler. * The counterpart of the `onActivate` handler.
*/ */
onDeactivate(cb: () => void): void; onDeactivate(cb: () => void): void;
// TODO: support onDeeplink!
} }
export interface RealFlipperDevice { export interface RealFlipperDevice {