mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
## Summary I'm looking at cleaning up some unnecessary manual property flattening in React Native and wanted to verify this behaviour is working as expected, where properties from nested objects will always overwrite properties from the base object. ## How did you test this change? Unit tests
288 lines
7.4 KiB
JavaScript
288 lines
7.4 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.
|
|
*
|
|
* @jest-environment node
|
|
*/
|
|
'use strict';
|
|
|
|
const ReactNativeAttributePayload = require('../ReactNativeAttributePayload');
|
|
|
|
const diff = ReactNativeAttributePayload.diff;
|
|
|
|
describe('ReactNativeAttributePayload', () => {
|
|
it('should work with simple example', () => {
|
|
expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
|
|
a: null,
|
|
b: 2,
|
|
});
|
|
});
|
|
|
|
it('should skip fields that are equal', () => {
|
|
expect(
|
|
diff(
|
|
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
|
{a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
|
|
{a: true, b: true, c: true, d: true, e: true, f: true},
|
|
),
|
|
).toEqual(null);
|
|
});
|
|
|
|
it('should remove fields', () => {
|
|
expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
|
|
});
|
|
|
|
it('should remove fields that are set to undefined', () => {
|
|
expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
|
|
});
|
|
|
|
it('should ignore invalid fields', () => {
|
|
expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
|
|
});
|
|
|
|
it('should use the diff attribute', () => {
|
|
const diffA = jest.fn((a, b) => true);
|
|
const diffB = jest.fn((a, b) => false);
|
|
expect(
|
|
diff(
|
|
{a: [1], b: [3]},
|
|
{a: [2], b: [4]},
|
|
{a: {diff: diffA}, b: {diff: diffB}},
|
|
),
|
|
).toEqual({a: [2]});
|
|
expect(diffA).toBeCalledWith([1], [2]);
|
|
expect(diffB).toBeCalledWith([3], [4]);
|
|
});
|
|
|
|
it('should not use the diff attribute on addition/removal', () => {
|
|
const diffA = jest.fn();
|
|
const diffB = jest.fn();
|
|
expect(
|
|
diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
|
|
).toEqual({a: null, b: [2]});
|
|
expect(diffA).not.toBeCalled();
|
|
expect(diffB).not.toBeCalled();
|
|
});
|
|
|
|
it('should do deep diffs of Objects by default', () => {
|
|
expect(
|
|
diff(
|
|
{a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
|
|
{a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
|
|
{a: true, b: true, c: true},
|
|
),
|
|
).toEqual({a: [2], c: {k: [4, 5]}});
|
|
});
|
|
|
|
it('should work with undefined styles', () => {
|
|
expect(
|
|
diff(
|
|
{style: {a: '#ffffff', b: 1}},
|
|
{style: undefined},
|
|
{style: {b: true}},
|
|
),
|
|
).toEqual({b: null});
|
|
expect(
|
|
diff(
|
|
{style: undefined},
|
|
{style: {a: '#ffffff', b: 1}},
|
|
{style: {b: true}},
|
|
),
|
|
).toEqual({b: 1});
|
|
expect(
|
|
diff({style: undefined}, {style: undefined}, {style: {b: true}}),
|
|
).toEqual(null);
|
|
});
|
|
|
|
it('should work with empty styles', () => {
|
|
expect(diff({a: 1, c: 3}, {}, {a: true, b: true})).toEqual({a: null});
|
|
expect(diff({}, {a: 1, c: 3}, {a: true, b: true})).toEqual({a: 1});
|
|
expect(diff({}, {}, {a: true, b: true})).toEqual(null);
|
|
});
|
|
|
|
it('should flatten nested styles and predefined styles', () => {
|
|
const validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
|
|
|
expect(
|
|
diff({}, {someStyle: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
|
|
).toEqual({foo: 1, bar: 2});
|
|
|
|
expect(
|
|
diff({someStyle: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
|
|
).toEqual({foo: null, bar: null});
|
|
|
|
const barStyle = {
|
|
bar: 3,
|
|
};
|
|
|
|
expect(
|
|
diff(
|
|
{},
|
|
{someStyle: [[{foo: 1}, {foo: 2}], barStyle]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: 2, bar: 3});
|
|
});
|
|
|
|
it('should reset a value to a previous if it is removed', () => {
|
|
const validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{foo: 1}, {foo: 3}]},
|
|
{someStyle: [{foo: 1}, {bar: 2}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: 1, bar: 2});
|
|
});
|
|
|
|
it('should not clear removed props if they are still in another slot', () => {
|
|
const validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{}, {foo: 3, bar: 2}]},
|
|
{someStyle: [{foo: 3}, {bar: 2}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{}, {foo: 3, bar: 2}]},
|
|
{someStyle: [{foo: 1, bar: 1}, {bar: 2}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({bar: 2, foo: 1});
|
|
});
|
|
|
|
it('should clear a prop if a later style is explicit null/undefined', () => {
|
|
const validStyleAttribute = {someStyle: {foo: true, bar: true}};
|
|
expect(
|
|
diff(
|
|
{someStyle: [{}, {foo: 3, bar: 2}]},
|
|
{someStyle: [{foo: 1}, {bar: 2, foo: null}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: null});
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{foo: 3}, {foo: null, bar: 2}]},
|
|
{someStyle: [{foo: null}, {bar: 2}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: null});
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{foo: 1}, {foo: null}]},
|
|
{someStyle: [{foo: 2}, {foo: null}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
|
|
|
// Test the same case with object equality because an early bailout doesn't
|
|
// work in this case.
|
|
const fooObj = {foo: 3};
|
|
expect(
|
|
diff(
|
|
{someStyle: [{foo: 1}, fooObj]},
|
|
{someStyle: [{foo: 2}, fooObj]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: 3}); // this should ideally be null. heuristic.
|
|
|
|
expect(
|
|
diff(
|
|
{someStyle: [{foo: 1}, {foo: 3}]},
|
|
{someStyle: [{foo: 2}, {foo: undefined}]},
|
|
validStyleAttribute,
|
|
),
|
|
).toEqual({foo: null}); // this should ideally be null. heuristic.
|
|
});
|
|
|
|
it('handles attributes defined multiple times', () => {
|
|
const validAttributes = {foo: true, style: {foo: true}};
|
|
expect(diff({}, {foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
|
|
foo: 2,
|
|
});
|
|
expect(diff({foo: 4}, {style: {foo: 2}}, validAttributes)).toEqual({
|
|
foo: 2,
|
|
});
|
|
expect(diff({style: {foo: 2}}, {foo: 4}, validAttributes)).toEqual({
|
|
foo: 4,
|
|
});
|
|
});
|
|
|
|
// Function properties are just markers to native that events should be sent.
|
|
it('should convert functions to booleans', () => {
|
|
// Note that if the property changes from one function to another, we don't
|
|
// need to send an update.
|
|
expect(
|
|
diff(
|
|
{
|
|
a: function () {
|
|
return 1;
|
|
},
|
|
b: function () {
|
|
return 2;
|
|
},
|
|
c: 3,
|
|
},
|
|
{
|
|
b: function () {
|
|
return 9;
|
|
},
|
|
c: function () {
|
|
return 3;
|
|
},
|
|
},
|
|
{a: true, b: true, c: true},
|
|
),
|
|
).toEqual({a: null, c: true});
|
|
});
|
|
|
|
it('should skip changed functions', () => {
|
|
expect(
|
|
diff(
|
|
{
|
|
a: function () {
|
|
return 1;
|
|
},
|
|
},
|
|
{
|
|
a: function () {
|
|
return 9;
|
|
},
|
|
},
|
|
{a: true},
|
|
),
|
|
).toEqual(null);
|
|
});
|
|
|
|
it('should skip deeply-nested changed functions', () => {
|
|
expect(
|
|
diff(
|
|
{
|
|
wrapper: {
|
|
a: function () {
|
|
return 1;
|
|
},
|
|
},
|
|
},
|
|
{
|
|
wrapper: {
|
|
a: function () {
|
|
return 9;
|
|
},
|
|
},
|
|
},
|
|
{wrapper: true},
|
|
),
|
|
).toEqual(null);
|
|
});
|
|
});
|