src/legacy/store/__tests__/recycleNodesInto-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';
require('configureForRelayOSS');
jest.unmock('recycleNodesInto');
const Relay = require('Relay');
const RelayTestUtils = require('RelayTestUtils');
const recycleNodesInto = require('recycleNodesInto');
describe('recycleNodesInto', () => {
beforeEach(() => {
jest.resetModuleRegistry();
});
describe('scalars', () => {
it('ignores when `prevData` is null or undefined', () => {
const nextData = {};
expect(recycleNodesInto(null, nextData)).toBe(nextData);
expect(recycleNodesInto(undefined, nextData)).toBe(nextData);
});
it('returns when `nextData` is null or undefined', () => {
const prevData = {};
expect(recycleNodesInto(prevData, null)).toBe(null);
expect(recycleNodesInto(prevData, undefined)).toBe(undefined);
});
it('returns when `nextData` is a string or number', () => {
expect(recycleNodesInto(null, 'foo')).toBe('foo');
expect(recycleNodesInto(null, 1)).toBe(1);
});
it('ignores when `prevData` is not exactly the same', () => {
expect(recycleNodesInto(1, '1')).toBe('1');
expect(recycleNodesInto(null, '')).toBe('');
});
});
describe('objects', () => {
it('recycles equal leaf objects', () => {
const prevData = {foo: 1};
const nextData = {foo: 1};
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('recycles parent objects with equal leaf objects', () => {
const prevData = {foo: {bar: 1}};
const nextData = {foo: {bar: 1}};
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('does not recycle unequal leaf objects', () => {
const prevData = {foo: 1};
const nextData = {foo: 100};
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
it('does not recycle parent objects with unequal leaf objects', () => {
const prevData = {foo: {bar: 1}};
const nextData = {foo: {bar: 100}};
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
it('does not recycle object with fewer properties', () => {
const prevData = {foo: 1};
const nextData = {foo: 1, bar: 2};
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
it('does not recycle object with more properties', () => {
const prevData = {foo: 1, bar: 2};
const nextData = {foo: 1};
expect(recycleNodesInto(prevData, nextData)).toEqual({foo: 1});
});
it('recycles equal leaf objects with unequal parent objects', () => {
const prevData = {foo: {bar: 1}, baz: 2};
const nextData = {foo: {bar: 1}, baz: 200};
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).not.toBe(prevData);
expect(recycled.bar).toBe(prevData.bar);
});
it('recycles identical objects', () => {
const prevData = {foo: {bar: 1}, baz: 2};
// "next" data should not be modified if it is === to previous data
Object.freeze(prevData);
Object.freeze(prevData.foo);
const recycled = recycleNodesInto(prevData, prevData);
expect(recycled).toBe(prevData);
});
it('does not recycle arrays as objects', () => {
const prevData = [1, 2];
const nextData = {0: 1, 1: 2};
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
});
describe('arrays', () => {
it('recycles arrays with equal scalars', () => {
const prevData = [1, 2, 3];
const nextData = [1, 2, 3];
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('does not recycle arrays with unequal scalars', () => {
const prevData = [1, 2, 3];
const nextData = [4, 5, 6];
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
it('recycles arrays with equal objects without mutating `prevData`', () => {
const prevData = [{foo: 1}, {bar: 2}];
const nextData = [{foo: 1}, {bar: 2}];
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('recycles arrays without mutating `prevData`', () => {
const prevItem = {foo: 1};
const prevData = [prevItem];
const nextData = [{foo: 1}];
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).toBe(prevData);
expect(recycled[0]).toBe(prevItem);
});
it('recycles arrays with equal objects but unequal parent objects', () => {
const prevData = {foo: [{foo: 1}, {bar: 2}], qux: 3};
const nextData = {foo: [{foo: 1}, {bar: 2}], qux: 300};
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).not.toBe(prevData);
expect(recycled.foo).toBe(prevData.foo);
});
it('recycles equal objects from an array with unequal siblings', () => {
const prevData = [{foo: 1}, {bar: 2}];
const nextData = [{foo: 1}, {bar: 200}];
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).not.toBe(prevData);
expect(recycled[0]).toBe(prevData[0]);
expect(recycled[1]).not.toBe(prevData[1]);
});
it('recycles equal objects from an array with fewer siblings', () => {
const prevData = [{foo: 1}];
const nextData = [{foo: 1}, {bar: 2}];
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).not.toBe(prevData);
expect(recycled[0]).toBe(prevData[0]);
expect(recycled[1]).toEqual({bar: 2});
});
it('recycles equal objects from an array with more siblings', () => {
const prevData = [{foo: 1}, {bar: 2}];
const nextData = [{foo: 1}];
const recycled = recycleNodesInto(prevData, nextData);
expect(recycled).not.toBe(prevData);
expect(recycled[0]).toBe(prevData[0]);
expect(recycled.length).toBe(1);
});
it('does not recycle objects as arrays', () => {
const prevData = Object.assign(Object.create({length: 2}), {0: 1, 1: 2});
const nextData = [1, 2];
expect(recycleNodesInto(prevData, nextData)).not.toBe(prevData);
});
});
describe('fragment pointers', () => {
let getPointer;
beforeEach(() => {
const {getNode} = RelayTestUtils;
const fragment = getNode(Relay.QL`fragment on Node{id}`);
getPointer = function(dataID) {
return RelayTestUtils.getPointer(dataID, fragment);
};
});
it('recycles equal fragment pointers', () => {
const prevData = getPointer('A');
const nextData = getPointer('A');
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('recycles parent objects with equal fragment pointers', () => {
const prevData = {foo: getPointer('A')};
const nextData = {foo: getPointer('A')};
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('recycles arrays with equal fragment pointers', () => {
const prevData = [getPointer('A')];
const nextData = [getPointer('A')];
expect(recycleNodesInto(prevData, nextData)).toBe(prevData);
});
it('does not recycle unequal fragment pointers', () => {
const prevData = getPointer('A');
const nextData = getPointer('B');
expect(recycleNodesInto(prevData, nextData)).toBe(nextData);
});
it('does not recycle parent objects with unequal fragment pointers', () => {
const prevData = {foo: getPointer('A')};
const nextData = {foo: getPointer('B')};
expect(recycleNodesInto(prevData, nextData)).toBe(nextData);
});
it('does not recycle arrays with unequal fragment pointers', () => {
const prevData = [getPointer('A')];
const nextData = [getPointer('B')];
expect(recycleNodesInto(prevData, nextData)).toBe(nextData);
});
});
});