/** * 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 React from 'react'; import {Provider} from 'react-redux'; import { render, RenderResult, act as testingLibAct, } from '@testing-library/react'; import {queries} from '@testing-library/dom'; import { selectPlugin, selectDevice, selectClient, } from '../reducers/connections'; import BaseDevice from '../devices/BaseDevice'; import {Store} from '../reducers/index'; import Client, {ClientQuery} from '../Client'; import {Logger} from '../fb-interfaces/Logger'; import {PluginDefinition} from '../plugin'; import PluginContainer from '../PluginContainer'; import {getPluginKey, isDevicePluginDefinition} from '../utils/pluginUtils'; import MockFlipper from './MockFlipper'; import {switchPlugin} from '../reducers/pluginManager'; export type MockFlipperResult = { client: Client; device: BaseDevice; store: Store; pluginKey: string; sendError(error: any, client?: Client): void; sendMessage(method: string, params: any, client?: Client): void; createDevice(serial: string): BaseDevice; createClient( device: BaseDevice, name: string, query?: ClientQuery, skipRegister?: boolean, ): Promise; logger: Logger; togglePlugin(plugin?: string): void; }; type MockOptions = Partial<{ /** * can be used to intercept outgoing calls. If it returns undefined * the base implementation will be used */ onSend?: (pluginId: string, method: string, params?: object) => any; additionalPlugins?: PluginDefinition[]; dontEnableAdditionalPlugins?: true; asBackgroundPlugin?: true; supportedPlugins?: string[]; device?: BaseDevice; archivedDevice?: boolean; }>; function isPluginEnabled( store: Store, pluginClazz: PluginDefinition, selectedApp: string, ) { return ( (!isDevicePluginDefinition(pluginClazz) && store .getState() .connections.enabledPlugins[selectedApp]?.includes(pluginClazz.id)) || (isDevicePluginDefinition(pluginClazz) && store.getState().connections.enabledDevicePlugins.has(pluginClazz.id)) ); } export async function createMockFlipperWithPlugin( pluginClazz: PluginDefinition, options?: MockOptions, ): Promise { const mockFlipper = new MockFlipper(); await mockFlipper.init({ plugins: [pluginClazz, ...(options?.additionalPlugins ?? [])], }); const logger = mockFlipper.logger; const store = mockFlipper.store; const createDevice = (serial: string, archived?: boolean) => mockFlipper.createDevice({serial, archived}); const createClient = async ( device: BaseDevice, name: string, query?: ClientQuery, skipRegister?: boolean, ) => { const client = await mockFlipper.createClient(device, { name, query, skipRegister, onSend: options?.onSend, supportedPlugins: options?.supportedPlugins, backgroundPlugins: options?.asBackgroundPlugin ? [pluginClazz.id] : [], }); // enable the plugin if (!isPluginEnabled(store, pluginClazz, name)) { store.dispatch( switchPlugin({ plugin: pluginClazz, selectedApp: client.query.app, }), ); } if (!options?.dontEnableAdditionalPlugins) { options?.additionalPlugins?.forEach((plugin) => { if (!isPluginEnabled(store, plugin, name)) { store.dispatch( switchPlugin({ plugin, selectedApp: client.query.app, }), ); } }); } return client; }; const device = options?.device ? mockFlipper.loadDevice(options?.device) : createDevice('serial', options?.archivedDevice); const client = await createClient(device, 'TestApp'); store.dispatch(selectDevice(device)); store.dispatch(selectClient(client.id)); store.dispatch( selectPlugin({ selectedPlugin: pluginClazz.id, selectedApp: client.query.app, deepLinkPayload: null, selectedDevice: device, }), ); return { client, device: device as any, store, sendError(error: any, actualClient = client) { actualClient.onMessage( JSON.stringify({ error, }), ); }, sendMessage(method, params, actualClient = client) { actualClient.onMessage( JSON.stringify({ method: 'execute', params: { api: pluginClazz.id, method, params, }, }), ); }, createDevice, createClient, logger, pluginKey: getPluginKey(client.id, device, pluginClazz.id), togglePlugin(id?: string) { const plugin = id ? store.getState().plugins.clientPlugins.get(id) ?? store.getState().plugins.devicePlugins.get(id) : pluginClazz; if (!plugin) { throw new Error('unknown plugin ' + id); } store.dispatch( switchPlugin({ plugin, selectedApp: client.query.app, }), ); }, }; } type Renderer = RenderResult; export async function renderMockFlipperWithPlugin( pluginClazz: PluginDefinition, options?: MockOptions, ): Promise< MockFlipperResult & { renderer: Renderer; act: (cb: () => void) => void; } > { const args = await createMockFlipperWithPlugin(pluginClazz, options); function selectTestPlugin(store: Store, client: Client) { store.dispatch( selectPlugin( isDevicePluginDefinition(pluginClazz) ? { selectedPlugin: pluginClazz.id, selectedApp: null, deepLinkPayload: null, selectedDevice: store.getState().connections.selectedDevice!, } : { selectedPlugin: pluginClazz.id, selectedApp: client.query.app, deepLinkPayload: null, selectedDevice: store.getState().connections.selectedDevice!, }, ), ); } selectTestPlugin(args.store, args.client); const renderer = render( , ); return { ...args, renderer: renderer as any, act(cb) { testingLibAct(cb); args.client.flushMessageBuffer(); }, }; }