Move RenderHost initialisation to Jest

Summary:
This diff moves RenderHost initialisation to jest, which is thereby treated as just another 'Host' like flipper-ui, the electron app etc. A benefit is that it provides a mocked flipperServer by default that can be used to mock or intercept requests. See LaunchEmulator.spec as example.

Also made the jest setup scripts strongly typed by converting them to TS.

This change allows the test stub configuration, which was OS dependent, out of flipper-ui-core.

Reviewed By: nikoant

Differential Revision: D32668632

fbshipit-source-id: fac0c09812b000fd7d1acb75010c35573087c99f
This commit is contained in:
Michel Weststrate
2021-12-08 04:25:28 -08:00
committed by Facebook GitHub Bot
parent e7f841b6d2
commit f9b72ac69e
14 changed files with 237 additions and 270 deletions

View File

@@ -11,12 +11,7 @@ import type {NotificationEvents} from './dispatcher/notifications';
import type {PluginNotification} from './reducers/notifications';
import type {NotificationConstructorOptions} from 'electron';
import {FlipperLib} from 'flipper-plugin';
import {
FlipperServer,
FlipperServerConfig,
ReleaseChannel,
Tristate,
} from 'flipper-common';
import {FlipperServer, FlipperServerConfig} from 'flipper-common';
// Events that are emitted from the main.ts ovr the IPC process bridge in Electron
type MainProcessEvents = {
@@ -101,7 +96,7 @@ export interface RenderHost {
openLink(url: string): void;
loadDefaultPlugins(): Record<string, any>;
GK(gatekeeper: string): boolean;
flipperServer?: FlipperServer;
flipperServer: FlipperServer;
serverConfig: FlipperServerConfig;
}
@@ -111,87 +106,3 @@ export function getRenderHostInstance(): RenderHost {
}
return window.FlipperRenderHostInstance;
}
if (process.env.NODE_ENV === 'test') {
const {tmpdir} = require('os');
const {resolve} = require('path');
const rootPath = resolve(__dirname, '..', '..');
const stubConfig: FlipperServerConfig = {
env: {...process.env},
gatekeepers: {
TEST_PASSING_GK: true,
TEST_FAILING_GK: false,
},
isProduction: false,
launcherSettings: {
ignoreLocalPin: false,
releaseChannel: ReleaseChannel.DEFAULT,
},
paths: {
appPath: rootPath,
desktopPath: `/dev/null`,
execPath: process.execPath,
homePath: `/dev/null`,
staticPath: resolve(rootPath, 'static'),
tempPath: tmpdir(),
},
processConfig: {
disabledPlugins: new Set(),
lastWindowPosition: null,
launcherEnabled: false,
launcherMsg: null,
screenCapturePath: `/dev/null`,
},
settings: {
androidHome: `/dev/null`,
darkMode: 'light',
enableAndroid: false,
enableIOS: false,
enablePhysicalIOS: false,
enablePrefetching: Tristate.False,
idbPath: `/dev/null`,
reactNative: {
shortcuts: {enabled: false, openDevMenu: '', reload: ''},
},
showWelcomeAtStartup: false,
suppressPluginErrors: false,
},
validWebSocketOrigins: [],
};
window.FlipperRenderHostInstance = {
processId: -1,
isProduction: false,
readTextFromClipboard() {
return '';
},
writeTextToClipboard() {},
async importFile() {
return undefined;
},
async exportFile() {
return undefined;
},
registerShortcut() {
return () => undefined;
},
hasFocus() {
return true;
},
onIpcEvent() {},
sendIpcEvent() {},
shouldUseDarkColors() {
return false;
},
restartFlipper() {},
openLink() {},
serverConfig: stubConfig,
loadDefaultPlugins() {
return {};
},
GK(gk) {
return stubConfig.gatekeepers[gk] ?? false;
},
};
}

View File

@@ -35,13 +35,6 @@ Object {
"TestPlugin",
],
},
"flipperServer": Object {
"close": [MockFunction],
"connect": [Function],
"exec": [MockFunction],
"off": [MockFunction],
"on": [MockFunction],
},
"pluginMenuEntries": Array [],
"selectedAppId": "TestApp#Android#MockAndroidDevice#serial",
"selectedAppPluginListRevision": 0,

View File

@@ -27,11 +27,6 @@ export function connectFlipperServerToStore(
store: Store,
logger: Logger,
) {
store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: server,
});
server.on('notification', ({type, title, description}) => {
const text = `[$type] ${title}: ${description}`;
console.warn(text);

View File

@@ -12,12 +12,7 @@ import {produce} from 'immer';
import type BaseDevice from '../devices/BaseDevice';
import type Client from '../Client';
import type {
UninitializedClient,
DeviceOS,
Logger,
FlipperServer,
} from 'flipper-common';
import type {UninitializedClient, DeviceOS, Logger} from 'flipper-common';
import type {Actions} from '.';
import {WelcomeScreenStaticView} from '../sandy-chrome/WelcomeScreen';
import {isDevicePluginDefinition} from '../utils/pluginUtils';
@@ -79,7 +74,6 @@ type StateV2 = {
deepLinkPayload: unknown;
staticView: StaticView;
selectedAppPluginListRevision: number;
flipperServer: FlipperServer | undefined;
};
type StateV1 = Omit<StateV2, 'enabledPlugins' | 'enabledDevicePlugins'> & {
@@ -165,10 +159,6 @@ export type Action =
| {
type: 'APP_PLUGIN_LIST_CHANGED';
}
| {
type: 'SET_FLIPPER_SERVER';
payload: FlipperServer;
}
| RegisterPluginAction;
const DEFAULT_PLUGIN = 'DeviceLogs';
@@ -195,18 +185,10 @@ const INITAL_STATE: State = {
deepLinkPayload: null,
staticView: WelcomeScreenStaticView,
selectedAppPluginListRevision: 0,
flipperServer: undefined,
};
export default (state: State = INITAL_STATE, action: Actions): State => {
switch (action.type) {
case 'SET_FLIPPER_SERVER': {
return {
...state,
flipperServer: action.payload,
};
}
case 'SET_STATIC_VIEW': {
const {payload, deepLinkPayload} = action;
return {

View File

@@ -20,6 +20,7 @@ import {useStore} from '../../utils/useStore';
import {Layout, renderReactRoot, withTrackingScope} from 'flipper-plugin';
import {Provider} from 'react-redux';
import {IOSDeviceParams} from 'flipper-common';
import {getRenderHostInstance} from '../../RenderHost';
const COLD_BOOT = 'cold-boot';
@@ -37,7 +38,6 @@ function LaunchEmulatorContainer({onClose}: {onClose: () => void}) {
export const LaunchEmulatorDialog = withTrackingScope(
function LaunchEmulatorDialog({onClose}: {onClose: () => void}) {
const flipperServer = useStore((state) => state.connections.flipperServer);
const iosEnabled = useStore((state) => state.settingsState.enableIOS);
const androidEnabled = useStore(
(state) => state.settingsState.enableAndroid,
@@ -49,8 +49,8 @@ export const LaunchEmulatorDialog = withTrackingScope(
if (!iosEnabled) {
return;
}
flipperServer!
.exec('ios-get-simulators', false)
getRenderHostInstance()
.flipperServer.exec('ios-get-simulators', false)
.then((emulators) => {
setIosEmulators(
emulators.filter(
@@ -63,21 +63,21 @@ export const LaunchEmulatorDialog = withTrackingScope(
.catch((e) => {
console.warn('Failed to find simulators', e);
});
}, [iosEnabled, flipperServer]);
}, [iosEnabled]);
useEffect(() => {
if (!androidEnabled) {
return;
}
flipperServer!
.exec('android-get-emulators')
getRenderHostInstance()
.flipperServer.exec('android-get-emulators')
.then((emulators) => {
setAndroidEmulators(emulators);
})
.catch((e) => {
console.warn('Failed to find emulators', e);
});
}, [androidEnabled, flipperServer]);
}, [androidEnabled]);
const items = [
...(androidEmulators.length > 0
@@ -85,8 +85,8 @@ export const LaunchEmulatorDialog = withTrackingScope(
: []),
...androidEmulators.map((name) => {
const launch = (coldBoot: boolean) => {
flipperServer!
.exec('android-launch-emulator', name, coldBoot)
getRenderHostInstance()
.flipperServer.exec('android-launch-emulator', name, coldBoot)
.then(onClose)
.catch((e) => {
console.error('Failed to start emulator: ', e);
@@ -123,8 +123,8 @@ export const LaunchEmulatorDialog = withTrackingScope(
<Button
key={device.udid}
onClick={() =>
flipperServer!
.exec('ios-launch-simulator', device.udid)
getRenderHostInstance()
.flipperServer.exec('ios-launch-simulator', device.udid)
.catch((e) => {
console.error('Failed to start simulator: ', e);
message.error('Failed to start simulator: ' + e);

View File

@@ -14,18 +14,19 @@ import {createStore} from 'redux';
import {LaunchEmulatorDialog} from '../LaunchEmulator';
import {createRootReducer} from '../../../reducers';
import {sleep, TestUtils} from 'flipper-plugin';
import {sleep} from 'flipper-plugin';
import {getRenderHostInstance} from '../../../RenderHost';
test('Can render and launch android apps - empty', async () => {
const store = createStore(createRootReducer());
const mockServer = TestUtils.createFlipperServerMock({
'ios-get-simulators': () => Promise.resolve([]),
'android-get-emulators': () => Promise.resolve([]),
});
store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: mockServer,
});
const responses: any = {
'ios-get-simulators': [],
'android-get-emulators': [],
};
getRenderHostInstance().flipperServer.exec = async function (cmd: any) {
return responses[cmd];
} as any;
const onClose = jest.fn();
const renderer = render(
@@ -44,21 +45,16 @@ test('Can render and launch android apps - empty', async () => {
});
test('Can render and launch android apps', async () => {
let p: Promise<any> | undefined = undefined;
const store = createStore(createRootReducer());
const launch = jest.fn().mockImplementation(() => Promise.resolve());
const mockServer = TestUtils.createFlipperServerMock({
'ios-get-simulators': () => Promise.resolve([]),
'android-get-emulators': () =>
(p = Promise.resolve(['emulator1', 'emulator2'])),
'android-launch-emulator': launch,
});
store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: mockServer,
const exec = jest.fn().mockImplementation(async (cmd) => {
if (cmd === 'android-get-emulators') {
return ['emulator1', 'emulator2'];
}
});
getRenderHostInstance().flipperServer.exec = exec;
store.dispatch({
type: 'UPDATE_SETTINGS',
payload: {
@@ -74,7 +70,7 @@ test('Can render and launch android apps', async () => {
</Provider>,
);
await p!;
await sleep(1); // give exec time to resolve
expect(await renderer.findAllByText(/emulator/)).toMatchInlineSnapshot(`
Array [
@@ -91,5 +87,16 @@ test('Can render and launch android apps', async () => {
fireEvent.click(renderer.getByText('emulator2'));
await sleep(1000);
expect(onClose).toBeCalled();
expect(launch).toBeCalledWith('emulator2', false);
expect(exec.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"android-get-emulators",
],
Array [
"android-launch-emulator",
"emulator2",
false,
],
]
`);
});

View File

@@ -28,7 +28,6 @@ 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[];
@@ -58,7 +57,7 @@ export default class MockFlipper {
private _clients: Client[] = [];
private _deviceCounter: number = 0;
private _clientCounter: number = 0;
flipperServer: FlipperServer = TestUtils.createFlipperServerMock();
flipperServer: FlipperServer = getRenderHostInstance().flipperServer;
public get store(): Store {
return this._store;
@@ -96,10 +95,6 @@ export default class MockFlipper {
this._logger,
);
this._store.dispatch(registerPlugins(plugins ?? []));
this._store.dispatch({
type: 'SET_FLIPPER_SERVER',
payload: this.flipperServer,
});
}
public async initWithDeviceAndClient(

View File

@@ -10,6 +10,7 @@
import {DeviceOS, DeviceType} from 'flipper-plugin';
import {DeviceSpec} from 'flipper-common';
import BaseDevice from '../devices/BaseDevice';
import {getRenderHostInstance} from '../RenderHost';
export class TestDevice extends BaseDevice {
constructor(
@@ -19,21 +20,12 @@ export class TestDevice extends BaseDevice {
os: DeviceOS,
specs?: DeviceSpec[],
) {
super(
{
async connect() {},
on: jest.fn(),
off: jest.fn(),
exec: jest.fn(),
close: jest.fn(),
},
{
serial,
deviceType,
title,
os,
specs,
},
);
super(getRenderHostInstance().flipperServer, {
serial,
deviceType,
title,
os,
specs,
});
}
}