Add unit tests for selection changes and plugin list computation

Summary: Tests to verify the more complex selection logic resulting from the Metro device exception. Also verifies the logic that computes the available plugins

Reviewed By: nikoant

Differential Revision: D24445555

fbshipit-source-id: 70110c4470e6aa1356e814aa40744b65c21cad89
This commit is contained in:
Michel Weststrate
2020-10-22 09:37:26 -07:00
committed by Facebook GitHub Bot
parent 99dfeacdf8
commit 4f7294c96d
4 changed files with 310 additions and 8 deletions

View File

@@ -47,7 +47,7 @@ async function isMetroRunning(): Promise<boolean> {
});
}
async function registerDevice(
export async function registerMetroDevice(
ws: WebSocket | undefined,
store: Store,
logger: Logger,
@@ -117,7 +117,7 @@ export default (store: Store, logger: Logger) => {
_ws.onopen = () => {
clearTimeout(guard);
ws = _ws;
registerDevice(ws, store, logger);
registerMetroDevice(ws, store, logger);
};
_ws.onclose = _ws.onerror = () => {
@@ -138,7 +138,7 @@ export default (store: Store, logger: Logger) => {
`Flipper did find a running Metro instance, but couldn't connect to the logs. Probably your React Native version is too old to support Flipper. Cause: Failed to get a connection to ${METRO_LOGS_ENDPOINT} in a timely fashion`,
),
);
registerDevice(undefined, store, logger);
registerMetroDevice(undefined, store, logger);
// Note: no scheduleNext, we won't retry until restart
}, 5000);
} else {

View File

@@ -314,7 +314,7 @@ function getPluginTooltip(details: PluginDetails): string {
}`;
}
function findBestClient(
export function findBestClient(
clients: Client[],
selectedApp: string | null,
userPreferredApp: string | null,
@@ -322,13 +322,13 @@ function findBestClient(
return clients.find((c) => c.id === (selectedApp || userPreferredApp));
}
function findMetroDevice(
export function findMetroDevice(
devices: State['connections']['devices'],
): BaseDevice | undefined {
return devices?.find((device) => device.os === 'Metro' && !device.isArchived);
}
function findBestDevice(
export function findBestDevice(
client: Client | undefined,
devices: State['connections']['devices'],
selectedDevice: BaseDevice | null,
@@ -353,7 +353,7 @@ function findBestDevice(
return selected;
}
function computePluginLists(
export function computePluginLists(
device: BaseDevice | undefined,
metroDevice: BaseDevice | undefined,
client: Client | undefined,

View File

@@ -0,0 +1,302 @@
/**
* 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,
MockFlipperResult,
} from '../../../test-utils/createMockFlipperWithPlugin';
import {
findBestClient,
findBestDevice,
findMetroDevice,
computePluginLists,
} from '../PluginList';
import {FlipperPlugin} from '../../../plugin';
import MetroDevice from '../../../devices/MetroDevice';
import BaseDevice from '../../../devices/BaseDevice';
import {SandyPluginDefinition} from 'flipper-plugin';
import {createMockPluginDetails} from 'flipper-plugin/src/test-utils/test-utils';
import {selectPlugin, starPlugin} from '../../../reducers/connections';
import {registerMetroDevice} from '../../../dispatcher/metroDevice';
import {addGatekeepedPlugins, registerPlugins} from '../../../reducers/plugins';
// eslint-disable-next-line
import * as LogsPluginModule from '../../../../../plugins/logs/index';
const logsPlugin = new SandyPluginDefinition(
createMockPluginDetails({id: 'DeviceLogs'}),
LogsPluginModule,
);
class TestPlugin extends FlipperPlugin<any, any, any> {}
describe('basic findBestDevice', () => {
let flipper: MockFlipperResult;
beforeEach(async () => {
flipper = await createMockFlipperWithPlugin(TestPlugin);
});
test('findBestDevice prefers selected device', () => {
const {client, device} = flipper;
const {connections} = flipper.store.getState();
expect(
findBestDevice(
client,
connections.devices,
device,
undefined,
device.title,
),
).toBe(device);
});
test('findBestDevice picks device of current client', () => {
const {client, device} = flipper;
const {connections} = flipper.store.getState();
expect(
findBestDevice(client, connections.devices, null, undefined, null),
).toBe(device);
});
test('findBestDevice picks preferred device if no client and device', () => {
const {device} = flipper;
const {connections} = flipper.store.getState();
expect(
findBestDevice(
undefined,
connections.devices,
null,
undefined,
device.title,
),
).toBe(device);
});
});
describe('basic findBestDevice with metro present', () => {
let flipper: MockFlipperResult;
let metro: MetroDevice;
let testDevice: BaseDevice;
beforeEach(async () => {
flipper = await createMockFlipperWithPlugin(logsPlugin);
testDevice = flipper.device;
// flipper.store.dispatch(registerPlugins([LogsPlugin]))
await registerMetroDevice(undefined, flipper.store, flipper.logger);
metro = findMetroDevice(
flipper.store.getState().connections.devices,
)! as MetroDevice;
});
test('findMetroDevice', () => {
expect(metro).toBeInstanceOf(MetroDevice);
});
test('correct base selection state', () => {
const {connections} = flipper.store.getState();
expect(connections).toMatchObject({
devices: [testDevice, metro],
selectedDevice: testDevice,
selectedPlugin: 'DeviceLogs',
userPreferredDevice: 'MockAndroidDevice',
userPreferredPlugin: 'DeviceLogs',
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
});
expect(
findBestClient(
connections.clients,
connections.selectedApp,
connections.userPreferredApp,
),
).toBe(flipper.client);
});
test('selecting Metro Logs works but keeps normal device preferred', () => {
flipper.store.dispatch(
selectPlugin({
selectedPlugin: logsPlugin.id,
selectedApp: null,
selectedDevice: metro,
deepLinkPayload: null,
}),
);
expect(flipper.store.getState().connections).toMatchObject({
devices: [testDevice, metro],
selectedApp: null,
selectedDevice: metro,
selectedPlugin: 'DeviceLogs',
userPreferredDevice: 'MockAndroidDevice', // Not metro!
userPreferredPlugin: 'DeviceLogs',
userPreferredApp: 'TestApp#Android#MockAndroidDevice#serial',
});
const {connections} = flipper.store.getState();
// find best device is still metro
expect(
findBestDevice(
undefined,
connections.devices,
connections.selectedDevice,
metro,
connections.userPreferredDevice,
),
).toBe(testDevice);
// find best client still returns app
expect(
findBestClient(
connections.clients,
connections.selectedApp,
connections.userPreferredApp,
),
).toBe(flipper.client);
});
test('computePluginLists', () => {
const state = flipper.store.getState();
expect(
computePluginLists(
testDevice,
metro,
flipper.client,
state.plugins,
state.connections.userStarredPlugins,
),
).toEqual({
devicePlugins: [logsPlugin],
metroPlugins: [logsPlugin],
enabledPlugins: [],
disabledPlugins: [],
unavailablePlugins: [],
});
});
test('computePluginLists with problematic plugins', () => {
const noopPlugin = {
plugin() {},
Component() {
return null;
},
};
const unsupportedDevicePlugin = new SandyPluginDefinition(
createMockPluginDetails({
id: 'unsupportedDevicePlugin',
title: 'Unsupported Device Plugin',
}),
{
devicePlugin() {
return {};
},
supportsDevice() {
return false;
},
Component() {
return null;
},
},
);
const unsupportedPlugin = new SandyPluginDefinition(
createMockPluginDetails({
id: 'unsupportedPlugin',
title: 'Unsupported Plugin',
}),
noopPlugin,
);
const gateKeepedPlugin = createMockPluginDetails({
id: 'gateKeepedPlugin',
title: 'Gatekeeped Plugin',
gatekeeper: 'not for you',
});
const plugin1 = new SandyPluginDefinition(
createMockPluginDetails({
id: 'plugin1',
title: 'Plugin 1',
}),
{
plugin() {},
Component() {
return null;
},
},
);
const plugin2 = new SandyPluginDefinition(
createMockPluginDetails({
id: 'plugin2',
title: 'Plugin 2',
}),
noopPlugin,
);
flipper.store.dispatch(
registerPlugins([
unsupportedDevicePlugin,
unsupportedPlugin,
plugin1,
plugin2,
]),
);
flipper.store.dispatch(addGatekeepedPlugins([gateKeepedPlugin]));
// ok, this is a little hackish
flipper.client.plugins = ['plugin1', 'plugin2'];
let state = flipper.store.getState();
expect(
computePluginLists(
testDevice,
metro,
flipper.client,
state.plugins,
state.connections.userStarredPlugins,
),
).toEqual({
devicePlugins: [logsPlugin],
metroPlugins: [logsPlugin],
enabledPlugins: [],
disabledPlugins: [plugin1, plugin2],
unavailablePlugins: [
[
gateKeepedPlugin,
"This plugin is only available to members of gatekeeper 'not for you'",
],
[
unsupportedDevicePlugin.details,
"Device plugin 'Unsupported Device Plugin' is not supported by the current device type.",
],
[
unsupportedPlugin.details,
"Plugin 'Unsupported Plugin' is not loaded by the client application",
],
],
});
flipper.store.dispatch(
starPlugin({
plugin: plugin2,
selectedApp: flipper.client.query.app,
}),
);
state = flipper.store.getState();
expect(
computePluginLists(
testDevice,
metro,
flipper.client,
state.plugins,
state.connections.userStarredPlugins,
),
).toMatchObject({
enabledPlugins: [plugin2],
disabledPlugins: [plugin1],
});
});
});

View File

@@ -39,7 +39,7 @@ import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
import {getInstance} from '../fb-stubs/Logger';
import {setFlipperLibImplementation} from '../utils/flipperLibImplementation';
type MockFlipperResult = {
export type MockFlipperResult = {
client: Client;
device: BaseDevice;
store: Store;