Update tests to use the sandyLegacy wrapper by default

Summary: Changed unit test infra to wrap legacy plugins by default in Sandy, and adapted tests where needed.

Reviewed By: nikoant

Differential Revision: D29264660

fbshipit-source-id: fe7cbc8af826afac5f945103586b3cb9647e7fbd
This commit is contained in:
Michel Weststrate
2021-06-21 08:35:52 -07:00
committed by Facebook GitHub Bot
parent 00e2c803ef
commit 640e06f130
13 changed files with 309 additions and 71 deletions

View File

@@ -87,3 +87,112 @@ Object {
"uninstalledPluginNames": Set {},
}
`;
exports[`can create a Fake flipper with legacy wrapper 1`] = `
Object {
"activeClient": Object {
"id": "TestApp#Android#MockAndroidDevice#serial",
"query": Object {
"app": "TestApp",
"device": "MockAndroidDevice",
"device_id": "serial",
"os": "Android",
"sdk_version": 4,
},
},
"activeDevice": Object {
"deviceType": "physical",
"os": "Android",
"serial": "serial",
"title": "MockAndroidDevice",
},
"androidEmulators": Array [],
"clients": Array [
Object {
"id": "TestApp#Android#MockAndroidDevice#serial",
"query": Object {
"app": "TestApp",
"device": "MockAndroidDevice",
"device_id": "serial",
"os": "Android",
"sdk_version": 4,
},
},
],
"deepLinkPayload": null,
"devices": Array [
Object {
"deviceType": "physical",
"os": "Android",
"serial": "serial",
"title": "MockAndroidDevice",
},
],
"enabledDevicePlugins": Set {
"DeviceLogs",
"CrashReporter",
"MobileBuilds",
"Hermesdebuggerrn",
"React",
},
"enabledPlugins": Object {
"TestApp": Array [
"TestPlugin",
],
},
"metroDevice": null,
"selectedApp": "TestApp#Android#MockAndroidDevice#serial",
"selectedDevice": Object {
"deviceType": "physical",
"os": "Android",
"serial": "serial",
"title": "MockAndroidDevice",
},
"selectedPlugin": "TestPlugin",
"staticView": null,
"uninitializedClients": Array [],
"userPreferredApp": "TestApp#Android#MockAndroidDevice#serial",
"userPreferredDevice": "MockAndroidDevice",
"userPreferredPlugin": "TestPlugin",
}
`;
exports[`can create a Fake flipper with legacy wrapper 2`] = `
Object {
"bundledPlugins": Map {},
"clientPlugins": Map {
"TestPlugin" => SandyPluginDefinition {
"details": Object {
"dir": "/Users/mock/.flipper/thirdparty/flipper-plugin-sample1",
"entry": "./test/index.js",
"id": "TestPlugin",
"isActivatable": true,
"isBundled": false,
"main": "dist/bundle.js",
"name": "flipper-plugin-hello",
"pluginType": "client",
"source": "src/index.js",
"specVersion": 2,
"title": "TestPlugin",
"version": "0.1.0",
},
"id": "TestPlugin",
"isDevicePlugin": false,
"module": Object {
"Component": [Function],
"plugin": [Function],
},
},
},
"devicePlugins": Map {},
"disabledPlugins": Array [],
"failedPlugins": Array [],
"gatekeepedPlugins": Array [],
"initialised": false,
"installedPlugins": Map {},
"loadedPlugins": Map {},
"marketplacePlugins": Array [],
"selectedPlugins": Array [],
"uninstalledPluginNames": Set {},
}
`;

View File

@@ -9,6 +9,7 @@
import {createMockFlipperWithPlugin} from '../test-utils/createMockFlipperWithPlugin';
import {FlipperPlugin} from '../plugin';
import {TestIdler} from '../utils/Idler';
interface PersistedState {
count: 1;
@@ -41,7 +42,7 @@ class TestPlugin extends FlipperPlugin<any, any, any> {
test('can create a Fake flipper', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {disableLegacyWrapper: true});
expect(client).toBeTruthy();
expect(device).toBeTruthy();
expect(store).toBeTruthy();
@@ -58,3 +59,29 @@ test('can create a Fake flipper', async () => {
}
`);
});
const testIdler = new TestIdler();
function testOnStatusMessage() {
// emtpy stub
}
test('can create a Fake flipper with legacy wrapper', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
expect(client).toBeTruthy();
expect(device).toBeTruthy();
expect(store).toBeTruthy();
expect(sendMessage).toBeTruthy();
expect(client.plugins.includes(TestPlugin.id)).toBe(true);
expect(client.sandyPluginStates.has(TestPlugin.id)).toBe(true);
const state = store.getState();
expect(state.connections).toMatchSnapshot();
expect(state.plugins).toMatchSnapshot();
sendMessage('inc', {});
expect(
await state.connections.clients[0].sandyPluginStates
.get(TestPlugin.id)!
.exportState(testIdler, testOnStatusMessage),
).toMatchInlineSnapshot(`"{\\"count\\":1}"`);
});

View File

@@ -93,8 +93,15 @@ test('SettingsSheet snapshot with one plugin enabled', async () => {
},
);
// enabled, but no data
expect(getExportablePlugins(store.getState(), device, client)).toEqual([]);
// enabled
// in Sandy wrapper, a plugin is either persistable or not, but it doesn't depend on the current state.
// So this plugin will show up, even though its state is still the default
expect(getExportablePlugins(store.getState(), device, client)).toEqual([
{
id: 'TestPlugin',
label: 'TestPlugin',
},
]);
store.dispatch(
setPluginState({

View File

@@ -162,7 +162,14 @@ test('requirePlugin loads plugin', () => {
version: '1.0.0',
});
expect(plugin).not.toBeNull();
expect((plugin as any).prototype).toBeInstanceOf(FlipperPlugin);
expect(Object.keys(plugin as any)).toEqual([
'id',
'details',
'isDevicePlugin',
'module',
]);
expect(Object.keys((plugin as any).module)).toEqual(['plugin', 'Component']);
expect(plugin!.id).toBe(TestPlugin.id);
});

View File

@@ -318,11 +318,11 @@ const requirePluginInternal = (
plugin.packageName = pluginDetails.name;
plugin.details = pluginDetails;
if (GK.get('flipper_use_sandy_plugin_wrapper')) {
return new _SandyPluginDefinition(
pluginDetails,
createSandyPluginWrapper(plugin),
);
if (
GK.get('flipper_use_sandy_plugin_wrapper') ||
process.env.NODE_ENV === 'test'
) {
return createSandyPluginFromClassicPlugin(pluginDetails, plugin);
}
// set values from package.json as static variables on class
@@ -336,6 +336,17 @@ const requirePluginInternal = (
return plugin;
};
export function createSandyPluginFromClassicPlugin(
pluginDetails: ActivatablePluginDetails,
plugin: any,
) {
pluginDetails.id = plugin.id; // for backward compatibility, see above check!
return new _SandyPluginDefinition(
pluginDetails,
createSandyPluginWrapper(plugin),
);
}
export function selectCompatibleMarketplaceVersions(
availablePlugins: MarketplacePluginDetails[],
): MarketplacePluginDetails[] {

View File

@@ -9,7 +9,6 @@
import {createMockFlipperWithPlugin} from '../../test-utils/createMockFlipperWithPlugin';
import {Store, Client} from '../../';
import {selectPlugin} from '../../reducers/connections';
import {registerPlugins} from '../../reducers/plugins';
import {
_SandyPluginDefinition,
@@ -66,19 +65,8 @@ function starTestPlugin(store: Store, client: Client) {
);
}
function selectTestPlugin(store: Store, client: Client) {
store.dispatch(
selectPlugin({
selectedPlugin: TestPlugin.id,
selectedApp: client.query.app,
deepLinkPayload: null,
selectedDevice: store.getState().connections.selectedDevice!,
}),
);
}
test('it should initialize starred sandy plugins', async () => {
const {client, store} = await createMockFlipperWithPlugin(TestPlugin);
const {client} = await createMockFlipperWithPlugin(TestPlugin);
// already started, so initialized immediately
expect(initialized).toBe(true);
@@ -89,12 +77,6 @@ test('it should initialize starred sandy plugins', async () => {
TestPlugin.id,
)!.instanceApi;
expect(instanceApi.connectStub).toBeCalledTimes(0);
selectTestPlugin(store, client);
// without rendering, non-bg plugins won't connect automatically,
// so this isn't the best test, but PluginContainer tests do test that part of the lifecycle
client.initPlugin(TestPlugin.id);
expect(instanceApi.connectStub).toBeCalledTimes(1);
client.deinitPlugin(TestPlugin.id);
expect(instanceApi.disconnectStub).toBeCalledTimes(1);

View File

@@ -216,9 +216,10 @@ export default class MockFlipper {
}
};
client.rawSend = jest.fn();
if (!device.isArchived) {
await client.init();
} else {
await client.initFromImport({});
}
// As convenience, by default we select the new client, star the plugin, and select it

View File

@@ -27,11 +27,14 @@ import {Store} from '../reducers/index';
import Client, {ClientQuery} from '../Client';
import {Logger} from '../fb-interfaces/Logger';
import {PluginDefinition} from '../plugin';
import {FlipperDevicePlugin, PluginDefinition} from '../plugin';
import PluginContainer from '../PluginContainer';
import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils';
import MockFlipper from './MockFlipper';
import {switchPlugin} from '../reducers/pluginManager';
import {createSandyPluginFromClassicPlugin} from '../dispatcher/plugins';
import {createMockActivatablePluginDetails} from '../utils/testUtils';
import {_SandyPluginDefinition} from 'flipper-plugin';
export type MockFlipperResult = {
client: Client;
@@ -49,6 +52,12 @@ export type MockFlipperResult = {
): Promise<Client>;
logger: Logger;
togglePlugin(plugin?: string): void;
selectPlugin(
id?: string,
client?: Client,
device?: BaseDevice,
deepLinkPayload?: any,
): void;
};
type MockOptions = Partial<{
@@ -63,6 +72,7 @@ type MockOptions = Partial<{
supportedPlugins?: string[];
device?: BaseDevice;
archivedDevice?: boolean;
disableLegacyWrapper?: boolean;
}>;
function isPluginEnabled(
@@ -84,9 +94,30 @@ export async function createMockFlipperWithPlugin(
pluginClazz: PluginDefinition,
options?: MockOptions,
): Promise<MockFlipperResult> {
function wrapSandy(clazz: PluginDefinition) {
return clazz instanceof _SandyPluginDefinition ||
options?.disableLegacyWrapper
? clazz
: createSandyPluginFromClassicPlugin(
createMockActivatablePluginDetails({
id: clazz.id,
title: clazz.title ?? clazz.id,
pluginType:
clazz.prototype instanceof FlipperDevicePlugin
? 'device'
: 'client',
}),
clazz,
);
}
pluginClazz = wrapSandy(pluginClazz);
const mockFlipper = new MockFlipper();
await mockFlipper.init({
plugins: [pluginClazz, ...(options?.additionalPlugins ?? [])],
plugins: [
pluginClazz,
...(options?.additionalPlugins?.map(wrapSandy) ?? []),
],
});
const logger = mockFlipper.logger;
const store = mockFlipper.store;
@@ -107,6 +138,7 @@ export async function createMockFlipperWithPlugin(
supportedPlugins: options?.supportedPlugins,
backgroundPlugins: options?.asBackgroundPlugin ? [pluginClazz.id] : [],
});
// enable the plugin
if (!isPluginEnabled(store, pluginClazz, name)) {
store.dispatch(
@@ -116,6 +148,7 @@ export async function createMockFlipperWithPlugin(
}),
);
}
if (!options?.dontEnableAdditionalPlugins) {
options?.additionalPlugins?.forEach((plugin) => {
if (!isPluginEnabled(store, plugin, name)) {
@@ -139,19 +172,36 @@ export async function createMockFlipperWithPlugin(
store.dispatch(selectDevice(device));
store.dispatch(selectClient(client.id));
store.dispatch(
selectPlugin({
selectedPlugin: pluginClazz.id,
selectedApp: client.query.app,
deepLinkPayload: null,
selectedDevice: device,
}),
);
let lastSelected: string | undefined = undefined;
function selectPluginImpl(
id = pluginClazz.id,
theClient = client,
theDevice = device,
deepLinkPayload = null,
) {
if (lastSelected) {
client.deinitPlugin(lastSelected);
}
store.dispatch(
selectPlugin({
selectedPlugin: id,
selectedApp: theClient.query.app,
deepLinkPayload,
selectedDevice: theDevice,
}),
);
client.initPlugin(pluginClazz.id); // simulates plugin being mounted
lastSelected = pluginClazz.id;
}
selectPluginImpl();
return {
client,
device: device as any,
store,
selectPlugin: selectPluginImpl,
sendError(error: any, actualClient = client) {
actualClient.onMessage(
JSON.stringify({

View File

@@ -1208,24 +1208,16 @@ test('Non sandy plugins are exported properly if they are still queued', async (
sendMessage('inc', {});
sendMessage('inc', {});
// not flushed
expect(store.getState().pluginStates).toMatchInlineSnapshot(`Object {}`);
// store export will cause flush
const storeExport = await exportStore(store);
// flushed
expect(store.getState().pluginStates).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
"counter": 3,
},
}
`);
const serial = storeExport.exportStoreData.device!.serial;
expect(serial).not.toBeFalsy();
expect(storeExport.exportStoreData.store.pluginStates).toMatchInlineSnapshot(`
expect(storeExport.exportStoreData.pluginStates2).toMatchInlineSnapshot(`
Object {
"TestApp#Android#MockAndroidDevice#${serial}#TestPlugin": "{\\"counter\\":3}",
"TestApp#Android#MockAndroidDevice#00000000-0000-0000-0000-000000000000-serial": Object {
"TestPlugin": "{\\"counter\\":3}",
},
}
`);
});

View File

@@ -88,6 +88,9 @@ function selectTestPlugin(store: Store, client: Client) {
test('queue - events are processed immediately if plugin is selected', async () => {
const {store, client, sendMessage} = await createMockFlipperWithPlugin(
TestPlugin,
{
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
},
);
expect(store.getState().connections.selectedPlugin).toBe('TestPlugin');
sendMessage('noop', {});
@@ -110,7 +113,9 @@ test('queue - events are processed immediately if plugin is selected', async ()
test('queue - events are NOT processed immediately if plugin is NOT selected (but enabled)', async () => {
const {store, client, sendMessage, device} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
@@ -200,7 +205,9 @@ test('queue - events are NOT processed immediately if plugin is NOT selected (bu
test('queue - events are queued for plugins that are favorite when app is not selected', async () => {
const {device, store, sendMessage, createClient} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
@@ -228,7 +235,9 @@ test('queue - events are queued for plugins that are favorite when app is not se
test('queue - events are queued for plugins that are favorite when app is selected on different device', async () => {
const {client, store, sendMessage, createDevice, createClient} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
@@ -270,7 +279,9 @@ test('queue - events are queued for plugins that are favorite when app is select
test('queue - events processing will be paused', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
@@ -314,7 +325,9 @@ test('queue - events processing will be paused', async () => {
test('queue - messages that arrive during processing will be queued', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
@@ -360,7 +373,9 @@ test('queue - messages that arrive during processing will be queued', async () =
test('queue - processing can be cancelled', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
@@ -391,7 +406,9 @@ test('queue - processing can be cancelled', async () => {
test('queue - make sure resetting plugin state clears the message queue', async () => {
const {client, device, store, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});
@@ -458,7 +475,9 @@ test('client - incoming messages are buffered and flushed together', async () =>
}
const {client, store, device, sendMessage} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
store.dispatch(registerPlugins([StubDeviceLogs]));
@@ -614,7 +633,9 @@ test('client - incoming messages are buffered and flushed together', async () =>
test('queue - messages that have not yet flushed be lost when disabling the plugin', async () => {
const {client, store, sendMessage, pluginKey} =
await createMockFlipperWithPlugin(TestPlugin);
await createMockFlipperWithPlugin(TestPlugin, {
disableLegacyWrapper: true, // Sandy is already tested in messageQueueSandy.node.tsx
});
selectDeviceLogs(store);
sendMessage('inc', {});

View File

@@ -99,10 +99,10 @@ test('getActivePersistentPlugins, where the non persistent plugins getting exclu
id: 'ClientPlugin4',
label: 'ClientPlugin4',
},
{
id: 'ClientPlugin5',
label: 'ClientPlugin5',
},
// { Never activated, and no data received
// id: 'ClientPlugin5',
// label: 'ClientPlugin5',
// },
]);
});
@@ -140,5 +140,11 @@ test('getActivePersistentPlugins, where the plugins not in pluginState or queue
id: 'ClientPlugin3', // queued
label: 'ClientPlugin3',
},
{
// in Sandy wrapper, a plugin is either persistable or not, but it doesn't depend on the current state.
// So this plugin will show up, even though its state is still the default
id: 'Plugin1',
label: 'Plugin1',
},
]);
});

View File

@@ -37,11 +37,13 @@ export function createSandyPluginWrapper<S, A extends BaseAction, P>(
Plugin: typeof FlipperPlugin | typeof FlipperDevicePlugin,
): SandyPluginModule {
const isDevicePlugin = Plugin.prototype instanceof FlipperDevicePlugin;
console.warn(
`Loading ${isDevicePlugin ? 'device' : 'client'} plugin ${
Plugin.id
} in legacy mode. Please visit https://fbflipper.com/docs/extending/sandy-migration to learn how to migrate this plugin to the new Sandy architecture`,
);
if (process.env.NODE_ENV !== 'test') {
console.warn(
`Loading ${isDevicePlugin ? 'device' : 'client'} plugin ${
Plugin.id
} in legacy mode. Please visit https://fbflipper.com/docs/extending/sandy-migration to learn how to migrate this plugin to the new Sandy architecture`,
);
}
function legacyPluginWrapper(client: PluginClient | DevicePluginClient) {
const store = getStore();

View File

@@ -7,7 +7,10 @@
* @format
*/
import {DownloadablePluginDetails} from 'flipper-plugin-lib';
import {
ActivatablePluginDetails,
DownloadablePluginDetails,
} from 'flipper-plugin-lib';
export function createMockDownloadablePluginDetails(
params: {
@@ -58,3 +61,23 @@ export function createMockDownloadablePluginDetails(
};
return details;
}
export function createMockActivatablePluginDetails(
base: Partial<ActivatablePluginDetails>,
): ActivatablePluginDetails {
return {
id: 'Hello',
specVersion: 2,
isBundled: false,
isActivatable: true,
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample1',
entry: './test/index.js',
name: 'flipper-plugin-hello',
version: '0.1.0',
pluginType: 'client',
source: 'src/index.js',
main: 'dist/bundle.js',
title: 'Hello',
...base,
};
}