added deeplink support to sandy device plugins
Summary: Make sure device plugins can be deeplinked as well. (note that the duplication between `Plugin` and `DevicePlugin` is cleaned up again in D22727089, first wanted to make it work and tested, then clean) DeepLink no longer have to be strings, per popular requests, as that makes direct linking between plugins easier (online links from the outside world have to arrive as strings) Reviewed By: jknoxville, nikoant Differential Revision: D22727091 fbshipit-source-id: 523c90b1e1fbf3700fdb4f62699dd57070cbc980
This commit is contained in:
committed by
Facebook GitHub Bot
parent
b621dcf754
commit
f8ff6dc393
@@ -180,7 +180,7 @@ class PluginContainer extends PureComponent<Props, State> {
|
||||
this.processMessageQueue();
|
||||
// make sure deeplinks are propagated
|
||||
const {deepLinkPayload, target, activePlugin} = this.props;
|
||||
if (deepLinkPayload && target instanceof Client && activePlugin) {
|
||||
if (deepLinkPayload && activePlugin && target) {
|
||||
target.sandyPluginStates
|
||||
.get(activePlugin.id)
|
||||
?.triggerDeepLink(deepLinkPayload);
|
||||
|
||||
@@ -505,3 +505,144 @@ test('PluginContainer can render Sandy device plugins', async () => {
|
||||
expect(pluginInstance.activatedStub).toBeCalledTimes(2);
|
||||
expect(pluginInstance.deactivatedStub).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test('PluginContainer + Sandy device plugin supports deeplink', async () => {
|
||||
const linksSeen: any[] = [];
|
||||
|
||||
const devicePlugin = (client: DevicePluginClient) => {
|
||||
const linkState = createState('');
|
||||
client.onDeepLink((link) => {
|
||||
linksSeen.push(link);
|
||||
linkState.set(String(link));
|
||||
});
|
||||
return {
|
||||
linkState,
|
||||
};
|
||||
};
|
||||
|
||||
const definition = new SandyPluginDefinition(
|
||||
TestUtils.createMockPluginDetails(),
|
||||
{
|
||||
devicePlugin,
|
||||
supportsDevice: () => true,
|
||||
Component() {
|
||||
const instance = usePlugin(devicePlugin);
|
||||
const linkState = useValue(instance.linkState);
|
||||
return <h1>hello {linkState || 'world'}</h1>;
|
||||
},
|
||||
},
|
||||
);
|
||||
const {renderer, act, store} = await renderMockFlipperWithPlugin(definition);
|
||||
|
||||
const theUniverse = {
|
||||
thisIs: 'theUniverse',
|
||||
toString() {
|
||||
return JSON.stringify({...this});
|
||||
},
|
||||
};
|
||||
|
||||
expect(linksSeen).toEqual([]);
|
||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="css-1orvm1g-View-FlexBox-FlexColumn"
|
||||
>
|
||||
<h1>
|
||||
hello
|
||||
world
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="css-bxcvv9-View-FlexBox-FlexRow"
|
||||
id="detailsSidebar"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`);
|
||||
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: definition.id,
|
||||
deepLinkPayload: theUniverse,
|
||||
selectedApp: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
expect(linksSeen).toEqual([theUniverse]);
|
||||
expect(renderer.baseElement).toMatchInlineSnapshot(`
|
||||
<body>
|
||||
<div>
|
||||
<div
|
||||
class="css-1orvm1g-View-FlexBox-FlexColumn"
|
||||
>
|
||||
<h1>
|
||||
hello
|
||||
{"thisIs":"theUniverse"}
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="css-bxcvv9-View-FlexBox-FlexRow"
|
||||
id="detailsSidebar"
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
`);
|
||||
|
||||
// Sending same link doesn't trigger again
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: definition.id,
|
||||
deepLinkPayload: theUniverse,
|
||||
selectedApp: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(linksSeen).toEqual([theUniverse]);
|
||||
|
||||
// ...nor does a random other store update that does trigger a plugin container render
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
updateSettings({
|
||||
...store.getState().settingsState,
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(linksSeen).toEqual([theUniverse]);
|
||||
|
||||
// Different link does trigger again
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: definition.id,
|
||||
deepLinkPayload: 'london!',
|
||||
selectedApp: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(linksSeen).toEqual([theUniverse, 'london!']);
|
||||
|
||||
// and same link does trigger if something else was selected in the mean time
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: 'Logs',
|
||||
deepLinkPayload: 'london!',
|
||||
selectedApp: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
act(() => {
|
||||
store.dispatch(
|
||||
selectPlugin({
|
||||
selectedPlugin: definition.id,
|
||||
deepLinkPayload: 'london!',
|
||||
selectedApp: null,
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(linksSeen).toEqual([theUniverse, 'london!', 'london!']);
|
||||
});
|
||||
|
||||
@@ -71,7 +71,10 @@ export default class BaseDevice {
|
||||
// sorted list of supported device plugins
|
||||
devicePlugins: string[] = [];
|
||||
|
||||
sandyPluginStates = new Map<string, SandyDevicePluginInstance>();
|
||||
sandyPluginStates: Map<string, SandyDevicePluginInstance> = new Map<
|
||||
string,
|
||||
SandyDevicePluginInstance
|
||||
>();
|
||||
|
||||
supportsOS(os: OS) {
|
||||
return os.toLowerCase() === this.os.toLowerCase();
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as TestUtils from '../test-utils/test-utils';
|
||||
import * as testPlugin from './TestPlugin';
|
||||
import {createState} from '../state/atom';
|
||||
import {FlipperClient} from '../plugin/Plugin';
|
||||
import {DevicePluginClient} from '../plugin/DevicePlugin';
|
||||
|
||||
test('it can start a plugin and lifecycle events', () => {
|
||||
const {instance, ...p} = TestUtils.startPlugin(testPlugin);
|
||||
@@ -215,3 +216,25 @@ test('plugins can receive deeplinks', async () => {
|
||||
plugin.triggerDeepLink('test');
|
||||
expect(plugin.instance.field1.get()).toBe('test');
|
||||
});
|
||||
|
||||
test('device plugins can receive deeplinks', async () => {
|
||||
const plugin = TestUtils.startDevicePlugin({
|
||||
devicePlugin(client: DevicePluginClient) {
|
||||
client.onDeepLink((deepLink) => {
|
||||
if (typeof deepLink === 'string') {
|
||||
field1.set(deepLink);
|
||||
}
|
||||
});
|
||||
const field1 = createState('', {persist: 'test'});
|
||||
return {field1};
|
||||
},
|
||||
supportsDevice: () => true,
|
||||
Component() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
expect(plugin.instance.field1.get()).toBe('');
|
||||
plugin.triggerDeepLink('test');
|
||||
expect(plugin.instance.field1.get()).toBe('test');
|
||||
});
|
||||
|
||||
@@ -61,7 +61,10 @@ export interface DevicePluginClient {
|
||||
*/
|
||||
onDeactivate(cb: () => void): void;
|
||||
|
||||
// TODO: support onDeeplink!
|
||||
/**
|
||||
* Triggered when this plugin is opened through a deeplink
|
||||
*/
|
||||
onDeepLink(cb: (deepLink: unknown) => void): void;
|
||||
}
|
||||
|
||||
export interface RealFlipperDevice {
|
||||
@@ -91,6 +94,8 @@ export class SandyDevicePluginInstance {
|
||||
initialStates?: Record<string, any>;
|
||||
// all the atoms that should be serialized when making an export / import
|
||||
rootStates: Record<string, Atom<any>> = {};
|
||||
// last seen deeplink
|
||||
lastDeeplink?: any;
|
||||
|
||||
constructor(
|
||||
realDevice: RealFlipperDevice,
|
||||
@@ -120,6 +125,9 @@ export class SandyDevicePluginInstance {
|
||||
onDeactivate: (cb) => {
|
||||
this.events.on('deactivate', cb);
|
||||
},
|
||||
onDeepLink: (callback) => {
|
||||
this.events.on('deeplink', callback);
|
||||
},
|
||||
};
|
||||
setCurrentPluginInstance(this);
|
||||
this.initialStates = initialStates;
|
||||
@@ -143,8 +151,8 @@ export class SandyDevicePluginInstance {
|
||||
}
|
||||
|
||||
deactivate() {
|
||||
this.assertNotDestroyed();
|
||||
if (this.activated) {
|
||||
if (!this.destroyed && this.activated) {
|
||||
this.lastDeeplink = undefined;
|
||||
this.activated = false;
|
||||
this.events.emit('deactivate');
|
||||
}
|
||||
@@ -161,6 +169,14 @@ export class SandyDevicePluginInstance {
|
||||
return '[SandyDevicePluginInstance]';
|
||||
}
|
||||
|
||||
triggerDeepLink(deepLink: unknown) {
|
||||
this.assertNotDestroyed();
|
||||
if (deepLink !== this.lastDeeplink) {
|
||||
this.lastDeeplink = deepLink;
|
||||
this.events.emit('deeplink', deepLink);
|
||||
}
|
||||
}
|
||||
|
||||
exportState() {
|
||||
return Object.fromEntries(
|
||||
Object.entries(this.rootStates).map(([key, atom]) => [key, atom.get()]),
|
||||
|
||||
@@ -138,6 +138,10 @@ interface StartDevicePluginResult<Module extends FlipperDevicePluginModule> {
|
||||
* Emulates sending a log message arriving from the device
|
||||
*/
|
||||
sendLogEntry(logEntry: DeviceLogEntry): void;
|
||||
/**
|
||||
* Emulates triggering a deeplik
|
||||
*/
|
||||
triggerDeepLink(deeplink: unknown): void;
|
||||
/**
|
||||
* Grabs the current (exportable) state
|
||||
*/
|
||||
@@ -278,6 +282,9 @@ export function startDevicePlugin<Module extends FlipperDevicePluginModule>(
|
||||
});
|
||||
},
|
||||
exportState: () => pluginInstance.exportState(),
|
||||
triggerDeepLink: (deepLink: unknown) => {
|
||||
pluginInstance.triggerDeepLink(deepLink);
|
||||
},
|
||||
};
|
||||
// @ts-ignore
|
||||
res._backingInstance = pluginInstance;
|
||||
|
||||
Reference in New Issue
Block a user