Files
flipper/desktop/app/src/utils/pluginStateRecorder.tsx
Michel Weststrate ba5f067320 Fix circular imports and lint against them
Summary: When trying to refactor some components, did once again run into circular imports that cause the flipper startup sequence to fail. Added linting rules to make sure this is much less likely to happen in the future, and fixed all resulting errors

Reviewed By: nikoant

Differential Revision: D24390583

fbshipit-source-id: 9b20cf6a4d3555dc68f0069c2950dd7162b17e67
2020-10-20 03:24:47 -07:00

217 lines
5.9 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 path from 'path';
import fs from 'fs';
import {Store, State} from '../reducers';
import {getPluginKey} from './pluginUtils';
import {serialize} from './serialization';
import {isSandyPlugin} from '../plugin';
let pluginRecordingState: {
recording: string;
pluginName: string;
startState: any;
events: [string, any][];
endState: any;
} = initialRecordingState();
function initialRecordingState(): typeof pluginRecordingState {
return {
recording: '',
startState: undefined,
events: [],
endState: undefined,
pluginName: '',
};
}
export function isRecordingEvents(pluginKey: string) {
return pluginRecordingState.recording === pluginKey;
}
export function flipperRecorderAddEvent(
pluginKey: string,
method: string,
params: any,
) {
if (pluginRecordingState.recording === pluginKey) {
pluginRecordingState.events.push([method, params]);
}
}
async function flipperStartPluginRecording(state: State) {
if (pluginRecordingState.recording) {
throw new Error('A plugin recording is already running');
}
const app = state.connections.selectedApp;
const client = state.connections.clients.find((client) => client.id === app);
if (!app || !client) {
throw new Error('Can only record plugin states if a device is selected');
}
const selectedPlugin = state.connections.selectedPlugin;
const pluginKey = getPluginKey(client.id, null, selectedPlugin!);
const plugin = state.plugins.clientPlugins.get(selectedPlugin!);
if (!selectedPlugin || !plugin) {
throw new Error('Can only record plugin states if a plugin is selected');
}
pluginRecordingState = {
recording: pluginKey,
startState: undefined,
events: [],
endState: undefined,
pluginName: selectedPlugin,
};
// Note that we don't use the plugin's own serializeState, as that might interact with the
// device state, and is used for creating Flipper Exports.
pluginRecordingState.startState = await serialize(
state.pluginStates[pluginKey] ||
(isSandyPlugin(plugin) ? {} : plugin.defaultPersistedState),
);
console.log(
`Started recordig the states of plugin ${selectedPlugin}..... Use window.flipperStopPluginRecording() to finish this process`,
);
}
async function flipperStopPluginRecording(state: State) {
if (!pluginRecordingState.recording) {
throw new Error('No plugin recording is running. ');
}
if (!pluginRecordingState.events.length) {
console.warn('No events were captured, cancelling recording');
pluginRecordingState = initialRecordingState();
return;
}
pluginRecordingState.endState = await serialize(
state.pluginStates[pluginRecordingState.recording],
);
const pluginName = pluginRecordingState.pluginName;
const snapShotFileContents = JSON.stringify(pluginRecordingState);
const snapShotFileName = `${pluginName}.pluginSnapshot.json`;
const testFileName = `${pluginName}EventsRunner.tsx`;
const outDir = getOutputDir(pluginName);
const testFileContents = generateTestSuite(pluginName, snapShotFileName);
await fs.promises.writeFile(
path.join(outDir, snapShotFileName),
snapShotFileContents,
'utf8',
);
await fs.promises.writeFile(
path.join(outDir, testFileName),
testFileContents,
'utf8',
);
console.log(
`Finished recording ${pluginRecordingState.events.length} for plugin ${
pluginRecordingState.recording
}. Generated files ${path.join(outDir, testFileName)} and ${path.join(
outDir,
snapShotFileName,
)}. Move them to the '__tests__ folder of your plugin to incorporate them`,
);
pluginRecordingState = initialRecordingState();
}
export function registerRecordingHooks(store: Store) {
Object.assign(window, {
flipperStartPluginRecording() {
flipperStartPluginRecording(store.getState());
},
flipperStopPluginRecording() {
flipperStopPluginRecording(store.getState());
},
});
}
function getOutputDir(pluginName: string) {
const outDir = path.join(process.cwd(), '..');
const fbPluginDir = path.join(
outDir,
'plugins',
'fb',
pluginName.toLowerCase(),
'__tests__',
);
const defaultPluginDir = path.join(
outDir,
'plugins',
pluginName.toLowerCase(),
'__tests__',
);
if (fs.existsSync(fbPluginDir)) {
return fbPluginDir;
} else if (fs.existsSync(defaultPluginDir)) {
return defaultPluginDir;
}
return outDir;
}
function generateTestSuite(pluginName: string, snapShotFileName: string) {
return `\
/**
* 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
*/
// This file was initially generated by using \`flipperStartPluginRecording()\` in Flipper console
import fs from 'fs';
import path from 'path';
import {deserialize} from '../ui';
import Plugin from '../';
test('Verify events produce a consistent end state for plugin ${pluginName}', async () => {
const snapshotData: {
startState: string;
endState: string;
events: [string, any][];
} = JSON.parse(
await fs.promises.readFile(
path.join(__dirname, '${snapShotFileName}'),
'utf8',
),
);
const startState: typeof Plugin.defaultPersistedState = deserialize(
snapshotData.startState,
);
const endState: typeof Plugin.defaultPersistedState = deserialize(
snapshotData.endState,
);
const startTime = Date.now();
const generatedEndState = snapshotData.events.reduce(
(store, [method, params]) =>
Plugin.persistedStateReducer(store, method, params),
startState,
);
const totalTime = Date.now() - startTime;
expect(generatedEndState).toEqual(endState);
console.log(
\`Reducer took $\{totalTime\}ms. to process $\{snapshotData.events.length\} events\`,
);
});
`;
}