Summary: Changelog: Improved plugin / device / app selection handing. During some refactorings I discovered that the `connetions.selectedApp` field contained sometimes an application id, and sometimes just the name. This caused inconsistent behavior especially in unit tests. I've cleaned that up, and renamed it to `selectedAppId` where applicable, to make the distinction more clear. And, in contrast, userPreferredApp now always has a name, not an id. During refactoring our existing selection update logic was quite in the way, which was overcomplicated still, since during the sandy chrome migration, the reducers needed to be able to handle both the old UI, and the new application selection UI. That logic has been simplified now, and a lot of tests were added. As a further simplification the preferredApp/Device/Plugin are now only read and used when updating selection, but not when running selectors. Reviewed By: timur-valiev Differential Revision: D31305180 fbshipit-source-id: 2dbd9f9c33950227cc63aa29cc4a98bdd0db8e7a
751 lines
21 KiB
TypeScript
751 lines
21 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 {FlipperPlugin} from '../../plugin';
|
|
import {
|
|
createMockFlipperWithPlugin,
|
|
wrapSandy,
|
|
} from '../../test-utils/createMockFlipperWithPlugin';
|
|
import {Store, Client, sleep} from '../../';
|
|
import {
|
|
selectPlugin,
|
|
selectClient,
|
|
selectDevice,
|
|
} from '../../reducers/connections';
|
|
import {processMessageQueue} from '../messageQueue';
|
|
import {getPluginKey} from '../pluginKey';
|
|
import {TestIdler} from '../Idler';
|
|
import {registerPlugins} from '../../reducers/plugins';
|
|
import {
|
|
_SandyPluginDefinition,
|
|
TestUtils,
|
|
PluginClient,
|
|
_SandyPluginInstance,
|
|
} from 'flipper-plugin';
|
|
import {switchPlugin} from '../../reducers/pluginManager';
|
|
import pluginMessageQueue, {
|
|
State,
|
|
queueMessages,
|
|
} from '../../reducers/pluginMessageQueue';
|
|
|
|
type Events = {
|
|
inc: {
|
|
delta?: number;
|
|
};
|
|
};
|
|
|
|
function plugin(client: PluginClient<Events, {}>) {
|
|
const state = {
|
|
count: 0,
|
|
};
|
|
|
|
client.onMessage('inc', (params) => {
|
|
state.count += params.delta || 1;
|
|
});
|
|
|
|
return {
|
|
state,
|
|
};
|
|
}
|
|
|
|
const TestPlugin = new _SandyPluginDefinition(
|
|
TestUtils.createMockPluginDetails(),
|
|
{
|
|
plugin,
|
|
Component() {
|
|
return null;
|
|
},
|
|
},
|
|
);
|
|
|
|
function switchTestPlugin(store: Store, client: Client) {
|
|
store.dispatch(
|
|
switchPlugin({
|
|
plugin: TestPlugin,
|
|
selectedApp: client.query.app,
|
|
}),
|
|
);
|
|
}
|
|
|
|
function selectDeviceLogs(store: Store) {
|
|
store.dispatch(
|
|
selectPlugin({
|
|
selectedPlugin: 'DeviceLogs',
|
|
selectedAppId: null,
|
|
deepLinkPayload: null,
|
|
selectedDevice: store.getState().connections.selectedDevice!,
|
|
}),
|
|
);
|
|
}
|
|
|
|
function selectTestPlugin(store: Store, client: Client) {
|
|
store.dispatch(
|
|
selectPlugin({
|
|
selectedPlugin: TestPlugin.id,
|
|
selectedAppId: client.id,
|
|
deepLinkPayload: null,
|
|
selectedDevice: store.getState().connections.selectedDevice!,
|
|
}),
|
|
);
|
|
}
|
|
|
|
function getTestPluginState(
|
|
client: Client,
|
|
): ReturnType<typeof plugin>['state'] {
|
|
return client.sandyPluginStates.get(TestPlugin.id)!.instanceApi.state;
|
|
}
|
|
|
|
test('queue - events are processed immediately if plugin is selected', async () => {
|
|
const {store, client, sendMessage} = await createMockFlipperWithPlugin(
|
|
TestPlugin,
|
|
);
|
|
expect(store.getState().connections.selectedPlugin).toBe('TestPlugin');
|
|
sendMessage('noop', {});
|
|
sendMessage('noop', {});
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 4});
|
|
sendMessage('noop', {});
|
|
client.flushMessageBuffer();
|
|
expect(getTestPluginState(client)).toMatchInlineSnapshot(`
|
|
Object {
|
|
"count": 5,
|
|
}
|
|
`);
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
|
|
`Object {}`,
|
|
);
|
|
});
|
|
|
|
test('queue - events are NOT processed immediately if plugin is NOT selected (but enabled)', async () => {
|
|
const {store, client, sendMessage, device} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3});
|
|
expect(getTestPluginState(client).count).toBe(0);
|
|
// the first message is already visible cause of the leading debounce
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
client.flushMessageBuffer();
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 3,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
|
|
// process the message
|
|
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
|
|
await processMessageQueue(
|
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
|
pluginKey,
|
|
store,
|
|
);
|
|
expect(getTestPluginState(client)).toEqual({
|
|
count: 6,
|
|
});
|
|
|
|
expect(store.getState().pluginMessageQueue).toEqual({
|
|
[pluginKey]: [],
|
|
});
|
|
|
|
// disable. Messages don't arrive anymore
|
|
switchTestPlugin(store, client);
|
|
// weird state...
|
|
selectTestPlugin(store, client);
|
|
sendMessage('inc', {delta: 3});
|
|
client.flushMessageBuffer();
|
|
// active, immediately processed
|
|
expect(client.sandyPluginStates.has(TestPlugin.id)).toBe(false);
|
|
|
|
// different plugin, and not enabled, message will never arrive
|
|
selectDeviceLogs(store);
|
|
sendMessage('inc', {delta: 4});
|
|
client.flushMessageBuffer();
|
|
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
|
|
expect(store.getState().pluginMessageQueue).toEqual({});
|
|
|
|
// star again, plugin still not selected, message is queued
|
|
switchTestPlugin(store, client);
|
|
sendMessage('inc', {delta: 5});
|
|
client.flushMessageBuffer();
|
|
|
|
expect(store.getState().pluginMessageQueue).toEqual({
|
|
[pluginKey]: [{api: 'TestPlugin', method: 'inc', params: {delta: 5}}],
|
|
});
|
|
});
|
|
|
|
test('queue - events ARE processed immediately if plugin is NOT selected / enabled BUT NAVIGATION', async () => {
|
|
const NavigationPlugin = new _SandyPluginDefinition(
|
|
TestUtils.createMockPluginDetails({
|
|
id: 'Navigation',
|
|
}),
|
|
{
|
|
plugin,
|
|
Component() {
|
|
return null;
|
|
},
|
|
},
|
|
);
|
|
const {store, client, sendMessage} = await createMockFlipperWithPlugin(
|
|
NavigationPlugin,
|
|
);
|
|
|
|
// Pre setup, deselect AND disable
|
|
selectDeviceLogs(store);
|
|
expect(store.getState().connections.selectedPlugin).toBe('DeviceLogs');
|
|
store.dispatch(
|
|
switchPlugin({
|
|
plugin: NavigationPlugin,
|
|
selectedApp: client.query.app,
|
|
}),
|
|
);
|
|
expect(store.getState().connections.enabledPlugins).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp": Array [],
|
|
}
|
|
`);
|
|
|
|
// ...mesages are still going to arrive
|
|
const pluginState = () =>
|
|
client.sandyPluginStates.get(NavigationPlugin.id)!.instanceApi.state;
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3});
|
|
// the first message is already visible cause of the leading debounce
|
|
expect(pluginState().count).toBe(1);
|
|
// message queue was never involved due to the bypass...
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
|
|
`Object {}`,
|
|
);
|
|
// flush will make the others visible
|
|
client.flushMessageBuffer();
|
|
expect(pluginState().count).toBe(6);
|
|
});
|
|
|
|
test('queue - events are queued for plugins that are favorite when app is not selected', async () => {
|
|
const {client, device, store, sendMessage, createClient} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
|
|
|
|
const client2 = await createClient(device, 'TestApp2');
|
|
store.dispatch(selectClient(client2.id));
|
|
|
|
// Now we send a message to the second client, it should arrive,
|
|
// as the plugin was enabled already on the first client as well
|
|
sendMessage('inc', {delta: 2});
|
|
expect(getTestPluginState(client)).toEqual({count: 0});
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
});
|
|
|
|
test('queue - events are queued for plugins that are favorite when app is selected on different device', async () => {
|
|
const {client, store, sendMessage, createDevice, createClient} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
expect(store.getState().connections.selectedPlugin).not.toBe('TestPlugin');
|
|
|
|
const device2 = createDevice({serial: 'serial2'});
|
|
const client2 = await createClient(device2, client.query.app); // same app id
|
|
store.dispatch(selectDevice(device2));
|
|
store.dispatch(selectClient(client2.id));
|
|
|
|
// Now we send a message to the first and second client, it should arrive,
|
|
// as the plugin was enabled already on the first client as well
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3}, client2);
|
|
client.flushMessageBuffer();
|
|
client2.flushMessageBuffer();
|
|
expect(getTestPluginState(client)).toEqual({count: 0});
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
],
|
|
"TestApp#Android#MockAndroidDevice#serial2#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 3,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
});
|
|
|
|
test('queue - events processing will be paused', async () => {
|
|
const {client, device, store, sendMessage} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 3});
|
|
sendMessage('inc', {delta: 5});
|
|
client.flushMessageBuffer();
|
|
|
|
// process the message
|
|
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
|
|
|
|
// controlled idler will signal and and off that idling is needed
|
|
const idler = new TestIdler();
|
|
|
|
const p = processMessageQueue(
|
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
|
pluginKey,
|
|
store,
|
|
undefined,
|
|
idler,
|
|
);
|
|
|
|
expect(getTestPluginState(client)).toEqual({
|
|
count: 4,
|
|
});
|
|
|
|
expect(store.getState().pluginMessageQueue).toEqual({
|
|
[pluginKey]: [{api: 'TestPlugin', method: 'inc', params: {delta: 5}}],
|
|
});
|
|
|
|
await idler.next();
|
|
expect(getTestPluginState(client)).toEqual({
|
|
count: 9,
|
|
});
|
|
|
|
expect(store.getState().pluginMessageQueue).toEqual({
|
|
[pluginKey]: [],
|
|
});
|
|
|
|
// don't idle anymore
|
|
idler.run();
|
|
await p;
|
|
});
|
|
|
|
test('queue - messages that arrive during processing will be queued', async () => {
|
|
const {client, device, store, sendMessage} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3});
|
|
client.flushMessageBuffer();
|
|
|
|
// process the message
|
|
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
|
|
|
|
const idler = new TestIdler();
|
|
|
|
const p = processMessageQueue(
|
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
|
pluginKey,
|
|
store,
|
|
undefined,
|
|
idler,
|
|
);
|
|
|
|
// first message is consumed
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
|
|
expect(getTestPluginState(client).count).toBe(3);
|
|
|
|
// Select the current plugin as active, still, messages should end up in the queue
|
|
store.dispatch(
|
|
selectPlugin({
|
|
selectedPlugin: TestPlugin.id,
|
|
selectedAppId: client.id,
|
|
deepLinkPayload: null,
|
|
selectedDevice: device,
|
|
}),
|
|
);
|
|
expect(store.getState().connections.selectedPlugin).toBe('TestPlugin');
|
|
|
|
sendMessage('inc', {delta: 4});
|
|
client.flushMessageBuffer();
|
|
// should not be processed yet
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(2);
|
|
expect(getTestPluginState(client).count).toBe(3);
|
|
|
|
await idler.next();
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(0);
|
|
expect(getTestPluginState(client).count).toBe(10);
|
|
|
|
idler.run();
|
|
await p;
|
|
});
|
|
|
|
test('queue - processing can be cancelled', async () => {
|
|
const {client, device, store, sendMessage} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3});
|
|
sendMessage('inc', {delta: 4});
|
|
sendMessage('inc', {delta: 5});
|
|
client.flushMessageBuffer();
|
|
|
|
// process the message
|
|
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
|
|
|
|
const idler = new TestIdler();
|
|
|
|
const p = processMessageQueue(
|
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
|
pluginKey,
|
|
store,
|
|
undefined,
|
|
idler,
|
|
);
|
|
|
|
// first message is consumed
|
|
await idler.next();
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
|
|
expect(getTestPluginState(client).count).toBe(10);
|
|
|
|
idler.cancel();
|
|
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(1);
|
|
expect(getTestPluginState(client).count).toBe(10);
|
|
await p;
|
|
});
|
|
|
|
test('queue - make sure resetting plugin state clears the message queue', async () => {
|
|
const {client, device, store, sendMessage} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
client.flushMessageBuffer();
|
|
|
|
const pluginKey = getPluginKey(client.id, device, TestPlugin.id);
|
|
|
|
expect(store.getState().pluginMessageQueue[pluginKey].length).toBe(2);
|
|
|
|
store.dispatch({
|
|
type: 'CLEAR_CLIENT_PLUGINS_STATE',
|
|
payload: {clientId: client.id, devicePlugins: new Set()},
|
|
});
|
|
|
|
expect(store.getState().pluginMessageQueue[pluginKey]).toBe(undefined);
|
|
});
|
|
|
|
test('client - incoming messages are buffered and flushed together', async () => {
|
|
class StubPlugin extends FlipperPlugin<any, any, any> {
|
|
static id = 'StubPlugin';
|
|
|
|
static persistedStateReducer = jest.fn();
|
|
}
|
|
|
|
const StubPluginWrapped = wrapSandy(StubPlugin);
|
|
|
|
const {client, store, device, sendMessage, pluginKey} =
|
|
await createMockFlipperWithPlugin(TestPlugin, {
|
|
additionalPlugins: [StubPluginWrapped],
|
|
});
|
|
selectDeviceLogs(store);
|
|
|
|
store.dispatch(registerPlugins([StubPluginWrapped]));
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
sendMessage('inc', {delta: 3});
|
|
|
|
// send a message to device logs
|
|
client.onMessage(
|
|
JSON.stringify({
|
|
method: 'execute',
|
|
params: {
|
|
api: 'StubPlugin',
|
|
method: 'log',
|
|
params: {line: 'suff'},
|
|
},
|
|
}),
|
|
);
|
|
|
|
expect(getTestPluginState(client).count).toBe(0);
|
|
// the first message is already visible cause of the leading debounce
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
expect(client.messageBuffer).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Object {
|
|
"messages": Array [
|
|
Object {
|
|
"api": "StubPlugin",
|
|
"method": "log",
|
|
"params": Object {
|
|
"line": "suff",
|
|
},
|
|
},
|
|
],
|
|
"plugin": "[SandyPluginInstance]",
|
|
},
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
|
|
"messages": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 3,
|
|
},
|
|
},
|
|
],
|
|
"plugin": "[SandyPluginInstance]",
|
|
},
|
|
}
|
|
`);
|
|
expect(client.messageBuffer[pluginKey].plugin).toBeInstanceOf(
|
|
_SandyPluginInstance,
|
|
);
|
|
|
|
await sleep(500);
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [
|
|
Object {
|
|
"api": "StubPlugin",
|
|
"method": "log",
|
|
"params": Object {
|
|
"line": "suff",
|
|
},
|
|
},
|
|
],
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 3,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
|
|
expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(
|
|
`Array []`,
|
|
);
|
|
|
|
// tigger processing the queue
|
|
const pluginKeyDevice = getPluginKey(client.id, device, StubPlugin.id);
|
|
await processMessageQueue(
|
|
client.sandyPluginStates.get(StubPlugin.id)!,
|
|
pluginKeyDevice,
|
|
store,
|
|
);
|
|
|
|
expect(StubPlugin.persistedStateReducer.mock.calls).toMatchInlineSnapshot(`
|
|
Array [
|
|
Array [
|
|
undefined,
|
|
"log",
|
|
Object {
|
|
"line": "suff",
|
|
},
|
|
],
|
|
]
|
|
`);
|
|
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#StubPlugin": Array [],
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 3,
|
|
},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
});
|
|
|
|
test('queue - messages that have not yet flushed be lost when disabling the plugin', async () => {
|
|
const {client, store, sendMessage, pluginKey} =
|
|
await createMockFlipperWithPlugin(TestPlugin);
|
|
selectDeviceLogs(store);
|
|
|
|
sendMessage('inc', {});
|
|
sendMessage('inc', {delta: 2});
|
|
|
|
expect(client.messageBuffer).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Object {
|
|
"messages": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {
|
|
"delta": 2,
|
|
},
|
|
},
|
|
],
|
|
"plugin": "[SandyPluginInstance]",
|
|
},
|
|
}
|
|
`);
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(`
|
|
Object {
|
|
"TestApp#Android#MockAndroidDevice#serial#TestPlugin": Array [
|
|
Object {
|
|
"api": "TestPlugin",
|
|
"method": "inc",
|
|
"params": Object {},
|
|
},
|
|
],
|
|
}
|
|
`);
|
|
|
|
// disable
|
|
switchTestPlugin(store, client);
|
|
expect(client.messageBuffer).toMatchInlineSnapshot(`Object {}`);
|
|
expect(store.getState().pluginMessageQueue).toMatchInlineSnapshot(
|
|
`Object {}`,
|
|
);
|
|
|
|
// re-enable, no messages arrive
|
|
switchTestPlugin(store, client);
|
|
client.flushMessageBuffer();
|
|
processMessageQueue(
|
|
client.sandyPluginStates.get(TestPlugin.id)!,
|
|
pluginKey,
|
|
store,
|
|
);
|
|
expect(getTestPluginState(client)).toEqual({count: 0});
|
|
});
|
|
|
|
test('queue will be cleaned up when it exceeds maximum size', () => {
|
|
let state: State = {};
|
|
const pluginKey = 'test';
|
|
const queueSize = 5000;
|
|
let i = 0;
|
|
for (i = 0; i < queueSize; i++) {
|
|
state = pluginMessageQueue(
|
|
state,
|
|
queueMessages(pluginKey, [{method: 'test', params: {i}}], queueSize),
|
|
);
|
|
}
|
|
// almost full
|
|
expect(state[pluginKey][0]).toEqual({method: 'test', params: {i: 0}});
|
|
expect(state[pluginKey].length).toBe(queueSize); // ~5000
|
|
expect(state[pluginKey][queueSize - 1]).toEqual({
|
|
method: 'test',
|
|
params: {i: queueSize - 1}, // ~4999
|
|
});
|
|
|
|
state = pluginMessageQueue(
|
|
state,
|
|
queueMessages(pluginKey, [{method: 'test', params: {i: ++i}}], queueSize),
|
|
);
|
|
|
|
const newLength = Math.ceil(0.9 * queueSize) + 1; // ~4500
|
|
expect(state[pluginKey].length).toBe(newLength);
|
|
expect(state[pluginKey][0]).toEqual({
|
|
method: 'test',
|
|
params: {i: queueSize - newLength + 1}, // ~500
|
|
});
|
|
expect(state[pluginKey][newLength - 1]).toEqual({
|
|
method: 'test',
|
|
params: {i: i}, // ~50001
|
|
});
|
|
});
|