src/legacy/store/__tests__/GraphQLStoreChangeEmitter-test.js
/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails oncall+relay
*/
'use strict';
jest.unmock('GraphQLStoreChangeEmitter');
const ErrorUtils = require('ErrorUtils');
const GraphQLStoreChangeEmitter = require('GraphQLStoreChangeEmitter');
const GraphQLStoreRangeUtils = require('GraphQLStoreRangeUtils');
describe('GraphQLStoreChangeEmitter', () => {
let changeEmitter;
let mockCallback;
let rangeData;
beforeEach(() => {
jest.resetModuleRegistry();
rangeData = new GraphQLStoreRangeUtils();
changeEmitter = new GraphQLStoreChangeEmitter(rangeData);
rangeData.getCanonicalClientID.mockImplementation(id => id);
ErrorUtils.applyWithGuard.mockImplementation(callback => {
try {
callback();
} catch (guarded) {}
});
mockCallback = jest.fn();
});
it('should broadcast changes asynchronously', () => {
changeEmitter.addListenerForIDs(['foo'], mockCallback);
changeEmitter.broadcastChangeForID('foo');
expect(mockCallback).not.toBeCalled();
jest.runAllTimers();
expect(mockCallback).toBeCalled();
});
it('should broadcast exclusively to subscribed IDs', () => {
changeEmitter.addListenerForIDs(['foo'], mockCallback);
changeEmitter.broadcastChangeForID('bar');
jest.runAllTimers();
expect(mockCallback).not.toBeCalled();
});
it('should not broadcast to removed callbacks', () => {
changeEmitter.addListenerForIDs(['foo'], mockCallback).remove();
changeEmitter.broadcastChangeForID('foo');
jest.runAllTimers();
expect(mockCallback).not.toBeCalled();
});
it('should only invoke callbacks subscribed at the time of broadcast', () => {
changeEmitter.broadcastChangeForID('foo');
changeEmitter.addListenerForIDs(['foo'], mockCallback);
jest.runAllTimers();
expect(mockCallback).not.toBeCalled();
});
it('should only broadcast once per execution loop', () => {
changeEmitter.addListenerForIDs(['foo', 'bar'], mockCallback);
changeEmitter.broadcastChangeForID('foo');
changeEmitter.broadcastChangeForID('bar');
jest.runAllTimers();
expect(mockCallback.mock.calls.length).toBe(1);
changeEmitter.broadcastChangeForID('bar');
changeEmitter.broadcastChangeForID('foo');
jest.runAllTimers();
expect(mockCallback.mock.calls.length).toBe(2);
});
it('should correctly broadcast changes to range IDs', () => {
rangeData.getCanonicalClientID.mockImplementation(
id => id === 'baz_first(5)' ? 'baz' : id
);
changeEmitter.addListenerForIDs(['baz_first(5)'], mockCallback);
changeEmitter.broadcastChangeForID('baz');
jest.runAllTimers();
expect(mockCallback).toBeCalled();
});
it('should guard against callback errors', () => {
const mockThrowingCallback = jest.fn(() => {
throw new Error();
});
changeEmitter.addListenerForIDs(['foo'], mockThrowingCallback);
changeEmitter.addListenerForIDs(['foo'], mockCallback);
changeEmitter.broadcastChangeForID('foo');
expect(() => {
jest.runAllTimers();
}).not.toThrow();
expect(mockThrowingCallback).toBeCalled();
expect(mockCallback).toBeCalled();
});
it('should use the injected strategy to batch updates', () => {
let mockBatching = false;
const mockBatchingStrategy = jest.fn(
callback => {
mockBatching = true;
callback();
mockBatching = false;
}
);
changeEmitter.injectBatchingStrategy(mockBatchingStrategy);
mockCallback.mockImplementation(() => {
expect(mockBatching).toBe(true);
});
changeEmitter.addListenerForIDs(['foo'], mockCallback);
changeEmitter.broadcastChangeForID('foo');
expect(mockBatchingStrategy.mock.calls.length).toBe(0);
jest.runAllTimers();
expect(mockBatchingStrategy.mock.calls.length).toBe(1);
});
it('schedules changes during broadcasts in the next execution loop', () => {
const mockBatchingStrategy = jest.fn(
callback => callback()
);
changeEmitter.injectBatchingStrategy(mockBatchingStrategy);
changeEmitter.addListenerForIDs(['foo'], () => {
changeEmitter.broadcastChangeForID('bar');
});
changeEmitter.addListenerForIDs(['bar'], mockCallback);
changeEmitter.broadcastChangeForID('foo');
jest.runAllTimers();
expect(mockCallback).toBeCalled();
// Jest does not allow running only one tick, so just assert that broadcasts
// occurring twice means `mockCallback` was invoked separately.
expect(mockBatchingStrategy.mock.calls.length).toBe(2);
});
});