Summary: This diff makes sure flipper-ui-core no longer depends on flipper-server-core. Currently server config is still transferred from UI to server, which doesn't really make sense in future scenarios where server might start before client, but will address that separately Reviewed By: timur-valiev, aigoncharov Differential Revision: D32462835 fbshipit-source-id: 498a944256ba1aabbf963b896953e64d11e27214
262 lines
6.7 KiB
TypeScript
262 lines
6.7 KiB
TypeScript
/**
|
|
* 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 {createStore} from 'redux';
|
|
import BaseDevice from '../devices/BaseDevice';
|
|
import {createRootReducer} from '../reducers';
|
|
import {Store} from '../reducers/index';
|
|
import Client, {ClientConnection} from '../Client';
|
|
import {
|
|
Logger,
|
|
buildClientId,
|
|
FlipperServer,
|
|
ClientResponseType,
|
|
} from 'flipper-common';
|
|
import {PluginDefinition} from '../plugin';
|
|
import {registerPlugins} from '../reducers/plugins';
|
|
import {getLogger} from 'flipper-common';
|
|
import {initializeFlipperLibImplementation} from '../utils/flipperLibImplementation';
|
|
import pluginManager from '../dispatcher/pluginManager';
|
|
import {PluginDetails} from 'flipper-plugin-lib';
|
|
import ArchivedDevice from '../devices/ArchivedDevice';
|
|
import {ClientQuery, DeviceOS} from 'flipper-common';
|
|
import {TestDevice} from './TestDevice';
|
|
import {getRenderHostInstance} from '../RenderHost';
|
|
import {TestUtils} from 'flipper-plugin';
|
|
|
|
export interface AppOptions {
|
|
plugins?: PluginDefinition[];
|
|
}
|
|
|
|
export interface ClientOptions {
|
|
name?: string;
|
|
supportedPlugins?: string[];
|
|
backgroundPlugins?: string[];
|
|
onSend?: (pluginId: string, method: string, params?: object) => any;
|
|
query?: ClientQuery;
|
|
skipRegister?: boolean;
|
|
}
|
|
|
|
export interface DeviceOptions {
|
|
serial?: string;
|
|
isSupportedByPlugin?: (p: PluginDetails) => boolean;
|
|
archived?: boolean;
|
|
os?: DeviceOS;
|
|
}
|
|
|
|
export default class MockFlipper {
|
|
private unsubscribePluginManager!: () => Promise<void>;
|
|
private _store!: Store;
|
|
private _logger!: Logger;
|
|
private _devices: BaseDevice[] = [];
|
|
private _clients: Client[] = [];
|
|
private _deviceCounter: number = 0;
|
|
private _clientCounter: number = 0;
|
|
flipperServer: FlipperServer = TestUtils.createFlipperServerMock();
|
|
|
|
public get store(): Store {
|
|
return this._store;
|
|
}
|
|
|
|
public get logger(): Logger {
|
|
return this._logger;
|
|
}
|
|
|
|
public get devices(): ReadonlyArray<BaseDevice> {
|
|
return this._devices;
|
|
}
|
|
|
|
public get clients(): ReadonlyArray<Client> {
|
|
return this._clients;
|
|
}
|
|
|
|
public get dispatch() {
|
|
return this._store.dispatch;
|
|
}
|
|
|
|
public getState() {
|
|
return this._store.getState();
|
|
}
|
|
|
|
public async init({plugins}: AppOptions = {}) {
|
|
this._store = createStore(createRootReducer());
|
|
this._logger = getLogger();
|
|
this.unsubscribePluginManager = pluginManager(this._store, this._logger, {
|
|
runSideEffectsSynchronously: true,
|
|
});
|
|
initializeFlipperLibImplementation(
|
|
getRenderHostInstance(),
|
|
this._store,
|
|
this._logger,
|
|
);
|
|
this._store.dispatch(registerPlugins(plugins ?? []));
|
|
this._store.dispatch({
|
|
type: 'SET_FLIPPER_SERVER',
|
|
payload: this.flipperServer,
|
|
});
|
|
}
|
|
|
|
public async initWithDeviceAndClient(
|
|
{
|
|
appOptions = {},
|
|
deviceOptions = {},
|
|
clientOptions = {},
|
|
}: {
|
|
appOptions?: AppOptions;
|
|
deviceOptions?: DeviceOptions;
|
|
clientOptions?: ClientOptions;
|
|
} = {appOptions: {}, deviceOptions: {}, clientOptions: {}},
|
|
): Promise<{flipper: MockFlipper; device: BaseDevice; client: Client}> {
|
|
await this.init(appOptions);
|
|
const device = this.createDevice(deviceOptions);
|
|
const client = await this.createClient(device, clientOptions);
|
|
return {
|
|
flipper: this,
|
|
device,
|
|
client,
|
|
};
|
|
}
|
|
|
|
public async destroy() {
|
|
this.unsubscribePluginManager && this.unsubscribePluginManager();
|
|
}
|
|
|
|
public createDevice({
|
|
serial,
|
|
isSupportedByPlugin,
|
|
archived,
|
|
os,
|
|
}: DeviceOptions = {}): BaseDevice {
|
|
const s = serial ?? `serial_${++this._deviceCounter}`;
|
|
const device = archived
|
|
? new ArchivedDevice({
|
|
serial: s,
|
|
deviceType: 'emulator',
|
|
title: 'archived device',
|
|
os: 'Android',
|
|
})
|
|
: new TestDevice(s, 'physical', 'MockAndroidDevice', os ?? 'Android');
|
|
device.supportsPlugin = !isSupportedByPlugin
|
|
? () => true
|
|
: isSupportedByPlugin;
|
|
this.loadDevice(device);
|
|
return device;
|
|
}
|
|
|
|
public loadDevice(device: BaseDevice) {
|
|
this._store.dispatch({
|
|
type: 'REGISTER_DEVICE',
|
|
payload: device,
|
|
});
|
|
device.loadDevicePlugins(
|
|
this._store.getState().plugins.devicePlugins,
|
|
this.store.getState().connections.enabledDevicePlugins,
|
|
);
|
|
this._devices.push(device);
|
|
return device;
|
|
}
|
|
|
|
public async createClient(
|
|
device: BaseDevice,
|
|
{
|
|
name,
|
|
supportedPlugins,
|
|
backgroundPlugins,
|
|
onSend,
|
|
skipRegister,
|
|
query,
|
|
}: ClientOptions = {},
|
|
): Promise<Client> {
|
|
if (!this._devices.includes(device)) {
|
|
throw new Error('The provided device does not exist');
|
|
}
|
|
query = query ?? {
|
|
app: name ?? `serial_${++this._clientCounter}`,
|
|
os: 'Android',
|
|
device: device.title,
|
|
device_id: device.serial,
|
|
sdk_version: 4,
|
|
};
|
|
const id = buildClientId({
|
|
app: query.app,
|
|
os: query.os,
|
|
device: query.device,
|
|
device_id: query.device_id,
|
|
});
|
|
supportedPlugins =
|
|
supportedPlugins ??
|
|
[...this._store.getState().plugins.clientPlugins.values()].map(
|
|
(p) => p.id,
|
|
);
|
|
const client = new Client(
|
|
id,
|
|
query,
|
|
device.isArchived ? null : createStubConnection(),
|
|
this._logger,
|
|
this._store,
|
|
new Set(supportedPlugins),
|
|
device,
|
|
);
|
|
client.rawCall = async (
|
|
method: string,
|
|
_fromPlugin: boolean,
|
|
params: any,
|
|
): Promise<any> => {
|
|
const intercepted = onSend?.(method, params);
|
|
if (intercepted !== undefined) {
|
|
return intercepted;
|
|
}
|
|
switch (method) {
|
|
case 'getPlugins':
|
|
// assuming this plugin supports all plugins for now
|
|
return {
|
|
plugins: supportedPlugins,
|
|
};
|
|
case 'getBackgroundPlugins':
|
|
return {
|
|
plugins: backgroundPlugins ?? [],
|
|
};
|
|
default:
|
|
throw new Error(
|
|
`Test client doesn't support rawCall method '${method}'`,
|
|
);
|
|
}
|
|
};
|
|
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
|
|
if (!skipRegister) {
|
|
this._store.dispatch({
|
|
type: 'NEW_CLIENT',
|
|
payload: client,
|
|
});
|
|
}
|
|
|
|
this._clients.push(client);
|
|
|
|
return client;
|
|
}
|
|
}
|
|
|
|
function createStubConnection(): ClientConnection {
|
|
return {
|
|
send(_: any) {
|
|
throw new Error('Should not be called in test');
|
|
},
|
|
sendExpectResponse(_: any): Promise<ClientResponseType> {
|
|
throw new Error('Should not be called in test');
|
|
},
|
|
};
|
|
}
|