Files
react-native/Libraries/BatchedBridge/__tests__/NativeModules-test.js
T
Rujin Cao b2ac528156 @emails -> @oncall (remaining ones)
Differential Revision: D39536169

fbshipit-source-id: 6c8d6787328eefecd23f3498b14a6d9ff750a670
2022-09-15 15:54:10 -07:00

223 lines
7.7 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
'use strict';
jest.unmock('../NativeModules');
let BatchedBridge;
let NativeModules;
const MODULE_IDS = 0;
const METHOD_IDS = 1;
const PARAMS = 2;
const CALL_ID = 3;
const assertQueue = (flushedQueue, index, moduleID, methodID, params) => {
expect(flushedQueue[MODULE_IDS][index]).toEqual(moduleID);
expect(flushedQueue[METHOD_IDS][index]).toEqual(methodID);
expect(flushedQueue[PARAMS][index]).toEqual(params);
};
// Important things to test:
//
// [x] Calling remote method actually queues it up on the BatchedBridge
//
// [x] Both error and success callbacks are invoked.
//
// [x] When simulating an error callback from remote method, both error and
// success callbacks are cleaned up.
//
// [ ] Remote invocation throws if not supplying an error callback.
describe('MessageQueue', function () {
beforeEach(function () {
jest.resetModules();
global.__fbBatchedBridgeConfig = require('../__mocks__/MessageQueueTestConfig');
BatchedBridge = require('../BatchedBridge');
NativeModules = require('../NativeModules');
});
it('should generate native modules', () => {
NativeModules.RemoteModule1.remoteMethod('foo');
const flushedQueue = BatchedBridge.flushedQueue();
assertQueue(flushedQueue, 0, 0, 0, ['foo']);
});
it('should make round trip and clear memory', function () {
const onFail = jest.fn();
const onSucc = jest.fn();
// Perform communication
NativeModules.RemoteModule1.promiseMethod(
'paloAlto',
'menloPark',
onFail,
onSucc,
);
NativeModules.RemoteModule2.promiseMethod('mac', 'windows', onFail, onSucc);
const resultingRemoteInvocations = BatchedBridge.flushedQueue();
// As always, the message queue has four fields
expect(resultingRemoteInvocations.length).toBe(4);
expect(resultingRemoteInvocations[MODULE_IDS].length).toBe(2);
expect(resultingRemoteInvocations[METHOD_IDS].length).toBe(2);
expect(resultingRemoteInvocations[PARAMS].length).toBe(2);
expect(typeof resultingRemoteInvocations[CALL_ID]).toEqual('number');
expect(resultingRemoteInvocations[0][0]).toBe(0); // `RemoteModule1`
expect(resultingRemoteInvocations[1][0]).toBe(1); // `promiseMethod`
expect([
// the arguments
resultingRemoteInvocations[2][0][0],
resultingRemoteInvocations[2][0][1],
]).toEqual(['paloAlto', 'menloPark']);
// Callbacks ids are tacked onto the end of the remote arguments.
const firstFailCBID = resultingRemoteInvocations[2][0][2];
const firstSuccCBID = resultingRemoteInvocations[2][0][3];
expect(resultingRemoteInvocations[0][1]).toBe(1); // `RemoteModule2`
expect(resultingRemoteInvocations[1][1]).toBe(1); // `promiseMethod`
expect([
// the arguments
resultingRemoteInvocations[2][1][0],
resultingRemoteInvocations[2][1][1],
]).toEqual(['mac', 'windows']);
const secondFailCBID = resultingRemoteInvocations[2][1][2];
const secondSuccCBID = resultingRemoteInvocations[2][1][3];
// Handle the first remote invocation by signaling failure.
BatchedBridge.__invokeCallback(firstFailCBID, ['firstFailure']);
// The failure callback was already invoked, the success is no longer valid
expect(function () {
BatchedBridge.__invokeCallback(firstSuccCBID, ['firstSucc']);
}).toThrow();
expect(onFail.mock.calls.length).toBe(1);
expect(onSucc.mock.calls.length).toBe(0);
// Handle the second remote invocation by signaling success.
BatchedBridge.__invokeCallback(secondSuccCBID, ['secondSucc']);
// The success callback was already invoked, the fail cb is no longer valid
expect(function () {
BatchedBridge.__invokeCallback(secondFailCBID, ['secondFail']);
}).toThrow();
expect(onFail.mock.calls.length).toBe(1);
expect(onSucc.mock.calls.length).toBe(1);
});
it('promise-returning methods (type=promise)', async function () {
// Perform communication
const promise1 = NativeModules.RemoteModule1.promiseReturningMethod(
'paloAlto',
'menloPark',
);
const promise2 = NativeModules.RemoteModule2.promiseReturningMethod(
'mac',
'windows',
);
const resultingRemoteInvocations = BatchedBridge.flushedQueue();
// As always, the message queue has four fields
expect(resultingRemoteInvocations.length).toBe(4);
expect(resultingRemoteInvocations[MODULE_IDS].length).toBe(2);
expect(resultingRemoteInvocations[METHOD_IDS].length).toBe(2);
expect(resultingRemoteInvocations[PARAMS].length).toBe(2);
expect(typeof resultingRemoteInvocations[CALL_ID]).toEqual('number');
expect(resultingRemoteInvocations[0][0]).toBe(0); // `RemoteModule1`
expect(resultingRemoteInvocations[1][0]).toBe(2); // `promiseReturningMethod`
expect([
// the arguments
resultingRemoteInvocations[2][0][0],
resultingRemoteInvocations[2][0][1],
]).toEqual(['paloAlto', 'menloPark']);
// For promise-returning methods, the order of callbacks is flipped from
// regular async methods.
const firstSuccCBID = resultingRemoteInvocations[2][0][2];
const firstFailCBID = resultingRemoteInvocations[2][0][3];
expect(resultingRemoteInvocations[0][1]).toBe(1); // `RemoteModule2`
expect(resultingRemoteInvocations[1][1]).toBe(2); // `promiseReturningMethod`
expect([
// the arguments
resultingRemoteInvocations[2][1][0],
resultingRemoteInvocations[2][1][1],
]).toEqual(['mac', 'windows']);
const secondSuccCBID = resultingRemoteInvocations[2][1][2];
const secondFailCBID = resultingRemoteInvocations[2][1][3];
// Handle the first remote invocation by signaling failure.
BatchedBridge.__invokeCallback(firstFailCBID, [{message: 'firstFailure'}]);
// The failure callback was already invoked, the success is no longer valid
expect(function () {
BatchedBridge.__invokeCallback(firstSuccCBID, ['firstSucc']);
}).toThrow();
await expect(promise1).rejects.toBeInstanceOf(Error);
await expect(promise1).rejects.toMatchObject({message: 'firstFailure'});
// Handle the second remote invocation by signaling success.
BatchedBridge.__invokeCallback(secondSuccCBID, ['secondSucc']);
// The success callback was already invoked, the fail cb is no longer valid
expect(function () {
BatchedBridge.__invokeCallback(secondFailCBID, ['secondFail']);
}).toThrow();
await promise2;
});
describe('sync methods', () => {
afterEach(function () {
delete global.nativeCallSyncHook;
});
it('throwing an exception', function () {
global.nativeCallSyncHook = jest.fn(() => {
throw new Error('firstFailure');
});
let error;
try {
NativeModules.RemoteModule1.syncMethod('paloAlto', 'menloPark');
} catch (e) {
error = e;
}
expect(global.nativeCallSyncHook).toBeCalledTimes(1);
expect(global.nativeCallSyncHook).toBeCalledWith(
0, // `RemoteModule1`
3, // `syncMethod`
['paloAlto', 'menloPark'],
);
expect(error).toBeInstanceOf(Error);
expect(error).toMatchObject({
message: 'firstFailure',
});
});
it('returning a value', function () {
global.nativeCallSyncHook = jest.fn(() => {
return 'secondSucc';
});
const result = NativeModules.RemoteModule2.syncMethod('mac', 'windows');
expect(global.nativeCallSyncHook).toBeCalledTimes(1);
expect(global.nativeCallSyncHook).toBeCalledWith(
1, // `RemoteModule2`
3, // `syncMethod`
['mac', 'windows'],
);
expect(result).toBe('secondSucc');
});
});
});