Command processing (2/n): testing
Summary: *Stack summary*: this stack refactors plugin management actions to perform them in a dispatcher rather than in the root reducer (store.tsx) as all of these actions has side effects. To do that, we store requested plugin management actions (install/update/uninstall, star/unstar) in a queue which is then handled by pluginManager dispatcher. This dispatcher then dispatches all required state updates. *Diff summary*: refactored Flipper mocking helpers to allow testing of plugin commands, and wrote some tests for pluginManager. Reviewed By: mweststrate Differential Revision: D26450344 fbshipit-source-id: 0e8414517cc1ad353781dffd7ffb4a5f9a815d38
This commit is contained in:
committed by
Facebook GitHub Bot
parent
8efdde08c4
commit
24aed8fd45
108
desktop/app/src/dispatcher/__tests__/pluginManager.node.tsx
Normal file
108
desktop/app/src/dispatcher/__tests__/pluginManager.node.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
jest.mock('../plugins');
|
||||
jest.mock('../../utils/electronModuleCache');
|
||||
import {loadPlugin} from '../../reducers/pluginManager';
|
||||
import {requirePlugin} from '../plugins';
|
||||
import {mocked} from 'ts-jest/utils';
|
||||
import {TestUtils} from 'flipper-plugin';
|
||||
import * as TestPlugin from '../../test-utils/TestPlugin';
|
||||
import {_SandyPluginDefinition as SandyPluginDefinition} from 'flipper-plugin';
|
||||
import MockFlipper from '../../test-utils/MockFlipper';
|
||||
|
||||
const pluginDetails1 = TestUtils.createMockPluginDetails({
|
||||
id: 'plugin1',
|
||||
version: '0.0.1',
|
||||
});
|
||||
const pluginDefinition1 = new SandyPluginDefinition(pluginDetails1, TestPlugin);
|
||||
|
||||
const pluginDetails1V2 = TestUtils.createMockPluginDetails({
|
||||
id: 'plugin1',
|
||||
version: '0.0.2',
|
||||
});
|
||||
const pluginDefinition1V2 = new SandyPluginDefinition(
|
||||
pluginDetails1V2,
|
||||
TestPlugin,
|
||||
);
|
||||
|
||||
const pluginDetails2 = TestUtils.createMockPluginDetails({id: 'plugin2'});
|
||||
const pluginDefinition2 = new SandyPluginDefinition(pluginDetails2, TestPlugin);
|
||||
|
||||
const mockedRequirePlugin = mocked(requirePlugin);
|
||||
|
||||
let mockFlipper: MockFlipper;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockedRequirePlugin.mockImplementation(
|
||||
(details) =>
|
||||
(details === pluginDetails1
|
||||
? pluginDefinition1
|
||||
: details === pluginDetails2
|
||||
? pluginDefinition2
|
||||
: details === pluginDetails1V2
|
||||
? pluginDefinition1V2
|
||||
: undefined)!,
|
||||
);
|
||||
mockFlipper = new MockFlipper();
|
||||
await mockFlipper.initWithDeviceAndClient({
|
||||
clientOptions: {supportedPlugins: ['plugin1', 'plugin2']},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
mockedRequirePlugin.mockReset();
|
||||
await mockFlipper.destroy();
|
||||
});
|
||||
|
||||
test('load plugin when no other version loaded', async () => {
|
||||
mockFlipper.dispatch(
|
||||
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||
pluginDefinition1,
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
|
||||
pluginDetails1,
|
||||
);
|
||||
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('load plugin when other version loaded', async () => {
|
||||
mockFlipper.dispatch(
|
||||
loadPlugin({plugin: pluginDetails1, enable: false, notifyIfFailed: false}),
|
||||
);
|
||||
mockFlipper.dispatch(
|
||||
loadPlugin({
|
||||
plugin: pluginDetails1V2,
|
||||
enable: false,
|
||||
notifyIfFailed: false,
|
||||
}),
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||
pluginDefinition1V2,
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
|
||||
pluginDetails1V2,
|
||||
);
|
||||
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('load and enable Sandy plugin', async () => {
|
||||
mockFlipper.dispatch(
|
||||
loadPlugin({plugin: pluginDetails1, enable: true, notifyIfFailed: false}),
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.clientPlugins.get('plugin1')).toBe(
|
||||
pluginDefinition1,
|
||||
);
|
||||
expect(mockFlipper.getState().plugins.loadedPlugins.get('plugin1')).toBe(
|
||||
pluginDetails1,
|
||||
);
|
||||
expect(mockFlipper.clients[0].sandyPluginStates.has('plugin1')).toBeTruthy();
|
||||
});
|
||||
@@ -35,15 +35,29 @@ function refreshInstalledPlugins(store: Store) {
|
||||
.then((plugins) => store.dispatch(registerInstalledPlugins(plugins)));
|
||||
}
|
||||
|
||||
export default (store: Store, _logger: Logger) => {
|
||||
export default (
|
||||
store: Store,
|
||||
_logger: Logger,
|
||||
{runSideEffectsSynchronously}: {runSideEffectsSynchronously: boolean} = {
|
||||
runSideEffectsSynchronously: false,
|
||||
},
|
||||
) => {
|
||||
// This needn't happen immediately and is (light) I/O work.
|
||||
window.requestIdleCallback(() => {
|
||||
refreshInstalledPlugins(store);
|
||||
});
|
||||
if (window.requestIdleCallback) {
|
||||
window.requestIdleCallback(() => {
|
||||
refreshInstalledPlugins(store);
|
||||
});
|
||||
}
|
||||
|
||||
sideEffect(
|
||||
const unsubscribeHandlePluginCommands = sideEffect(
|
||||
store,
|
||||
{name: 'handlePluginActivation', throttleMs: 1000, fireImmediately: true},
|
||||
{
|
||||
name: 'handlePluginCommands',
|
||||
throttleMs: 0,
|
||||
fireImmediately: true,
|
||||
runSynchronously: runSideEffectsSynchronously, // Used to simplify writing tests, if "true" passed, the all side effects will be called synchronously and immediately after changes
|
||||
noTimeBudgetWarns: true, // These side effects are critical, so we're doing them with zero throttling and want to avoid unnecessary warns
|
||||
},
|
||||
(state) => state.pluginManager.pluginCommandsQueue,
|
||||
(queue, store) => {
|
||||
for (const command of queue) {
|
||||
@@ -59,6 +73,9 @@ export default (store: Store, _logger: Logger) => {
|
||||
store.dispatch(pluginCommandsProcessed(queue.length));
|
||||
},
|
||||
);
|
||||
return async () => {
|
||||
unsubscribeHandlePluginCommands();
|
||||
};
|
||||
};
|
||||
|
||||
function loadPlugin(store: Store, payload: LoadPluginActionPayload) {
|
||||
|
||||
Reference in New Issue
Block a user