1
-/**
2
- * Copyright (c) Meta Platforms, Inc. and affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- *
7
- * @jest-environment node
8
- */
9
-'use strict';
10
-
11
-const {diff, create} = require('../ReactNativeAttributePayloadFabric');
12
-
13
-describe('ReactNativeAttributePayloadFabric.create', () => {
14
- it('should work with simple example', () => {
15
- expect(create({b: 2, c: 3}, {a: true, b: true})).toEqual({
16
- b: 2,
17
- });
18
- });
19
-
20
- it('should work with complex example', () => {
21
- const validAttributes = {
22
- style: {
23
- position: true,
24
- zIndex: true,
25
- flexGrow: true,
26
- flexShrink: true,
27
- flexDirection: true,
28
- overflow: true,
29
- backgroundColor: true,
30
- },
31
- };
32
-
33
- expect(
34
- create(
35
- {
36
- style: [
37
- {
38
- flexGrow: 1,
39
- flexShrink: 1,
40
- flexDirection: 'row',
41
- overflow: 'scroll',
42
- },
43
- [
44
- {position: 'relative', zIndex: 2},
45
- {flexGrow: 0},
46
- {backgroundColor: 'red'},
47
- ],
48
- ],
49
- },
50
- validAttributes,
51
- ),
52
- ).toEqual({
53
- flexGrow: 0,
54
- flexShrink: 1,
55
- flexDirection: 'row',
56
- overflow: 'scroll',
57
- position: 'relative',
58
- zIndex: 2,
59
- backgroundColor: 'red',
60
- });
61
- });
62
-
63
- it('should nullify previously defined style prop that is subsequently set to null or undefined', () => {
64
- expect(
65
- create({style: [{a: 0}, {a: undefined}]}, {style: {a: true}}),
66
- ).toEqual({a: null});
67
- expect(create({style: [{a: 0}, {a: null}]}, {style: {a: true}})).toEqual({
68
- a: null,
69
- });
70
- });
71
-
72
- it('should ignore non-style fields that are set to undefined', () => {
73
- expect(create({}, {a: true})).toEqual(null);
74
- expect(create({a: undefined}, {a: true})).toEqual(null);
75
- expect(create({a: undefined, b: undefined}, {a: true, b: true})).toEqual(
76
- null,
77
- );
78
- expect(
79
- create({a: undefined, b: undefined, c: 1}, {a: true, b: true}),
80
- ).toEqual(null);
81
- expect(
82
- create({a: undefined, b: undefined, c: 1}, {a: true, b: true, c: true}),
83
- ).toEqual({c: 1});
84
- expect(
85
- create({a: 1, b: undefined, c: 2}, {a: true, b: true, c: true}),
86
- ).toEqual({a: 1, c: 2});
87
- });
88
-
89
- it('should ignore invalid fields', () => {
90
- expect(create({b: 2}, {})).toEqual(null);
91
- });
92
-
93
- it('should not use the diff attribute', () => {
94
- const diffA = jest.fn();
95
- expect(create({a: [2]}, {a: {diff: diffA}})).toEqual({a: [2]});
96
- expect(diffA).not.toBeCalled();
97
- });
98
-
99
- it('should use the process attribute', () => {
100
- const processA = jest.fn(a => a + 1);
101
- expect(create({a: 2}, {a: {process: processA}})).toEqual({a: 3});
102
- expect(processA).toBeCalledWith(2);
103
- });
104
-
105
- it('should use the process attribute for functions as well', () => {
106
- const process = x => x;
107
- const nextFunction = () => {};
108
- expect(create({a: nextFunction}, {a: {process}})).toEqual({
109
- a: nextFunction,
110
- });
111
- });
112
-
113
- it('should work with undefined styles', () => {
114
- expect(create({style: undefined}, {style: {b: true}})).toEqual(null);
115
- expect(create({style: {a: '#ffffff', b: 1}}, {style: {b: true}})).toEqual({
116
- b: 1,
117
- });
118
- });
119
-
120
- it('should flatten nested styles and predefined styles', () => {
121
- const validStyleAttribute = {someStyle: {foo: true, bar: true}};
122
- expect(
123
- create({someStyle: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
124
- ).toEqual({foo: 1, bar: 2});
125
- expect(create({}, validStyleAttribute)).toEqual(null);
126
- const barStyle = {
127
- bar: 3,
128
- };
129
- expect(
130
- create(
131
- {someStyle: [[{foo: 1}, {foo: 2}], barStyle]},
132
- validStyleAttribute,
133
- ),
134
- ).toEqual({foo: 2, bar: 3});
135
- });
136
-
137
- it('should not flatten nested props if attribute config is a primitive or only has diff/process', () => {
138
- expect(create({a: {foo: 1, bar: 2}}, {a: true})).toEqual({
139
- a: {foo: 1, bar: 2},
140
- });
141
- expect(create({a: [{foo: 1}, {bar: 2}]}, {a: true})).toEqual({
142
- a: [{foo: 1}, {bar: 2}],
143
- });
144
- expect(create({a: {foo: 1, bar: 2}}, {a: {diff: a => a}})).toEqual({
145
- a: {foo: 1, bar: 2},
146
- });
147
- expect(
148
- create({a: [{foo: 1}, {bar: 2}]}, {a: {diff: a => a, process: a => a}}),
149
- ).toEqual({a: [{foo: 1}, {bar: 2}]});
150
- });
151
-
152
- it('handles attributes defined multiple times', () => {
153
- const validAttributes = {foo: true, style: {foo: true}};
154
- expect(create({foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
155
- foo: 2,
156
- });
157
- expect(create({style: {foo: 2}}, validAttributes)).toEqual({
158
- foo: 2,
159
- });
160
- expect(create({style: {foo: 2}, foo: 4}, validAttributes)).toEqual({
161
- foo: 4,
162
- });
163
- expect(create({foo: 4, style: {foo: null}}, validAttributes)).toEqual({
164
- foo: null, // this should ideally be null.
165
- });
166
- expect(
167
- create({foo: 4, style: [{foo: null}, {foo: 5}]}, validAttributes),
168
- ).toEqual({
169
- foo: 5,
170
- });
171
- });
172
-
173
- // Function properties are just markers to native that events should be sent.
174
- it('should convert functions to booleans', () => {
175
- expect(
176
- create(
177
- {
178
- a: function () {
179
- return 9;
180
- },
181
- b: function () {
182
- return 3;
183
- },
184
- },
185
- {a: true, b: true},
186
- ),
187
- ).toEqual({a: true, b: true});
188
- });
189
-});
190
-
191
-describe('ReactNativeAttributePayloadFabric.diff', () => {
192
- it('should work with simple example', () => {
193
- expect(diff({a: 1, c: 3}, {b: 2, c: 3}, {a: true, b: true})).toEqual({
194
- a: null,
195
- b: 2,
196
- });
197
- });
198
-
199
- it('should skip fields that are equal', () => {
200
- expect(
201
- diff(
202
- {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
203
- {a: 1, b: 'two', c: true, d: false, e: undefined, f: 0},
204
- {a: true, b: true, c: true, d: true, e: true, f: true},
205
- ),
206
- ).toEqual(null);
207
- });
208
-
209
- it('should remove fields', () => {
210
- expect(diff({a: 1}, {}, {a: true})).toEqual({a: null});
211
- });
212
-
213
- it('should remove fields that are set to undefined', () => {
214
- expect(diff({a: 1}, {a: undefined}, {a: true})).toEqual({a: null});
215
- });
216
-
217
- it('should ignore invalid fields', () => {
218
- expect(diff({a: 1}, {b: 2}, {})).toEqual(null);
219
- });
220
-
221
- it('should use the diff attribute', () => {
222
- const diffA = jest.fn((a, b) => true);
223
- const diffB = jest.fn((a, b) => false);
224
- expect(
225
- diff(
226
- {a: [1], b: [3]},
227
- {a: [2], b: [4]},
228
- {a: {diff: diffA}, b: {diff: diffB}},
229
- ),
230
- ).toEqual({a: [2]});
231
- expect(diffA).toBeCalledWith([1], [2]);
232
- expect(diffB).toBeCalledWith([3], [4]);
233
- });
234
-
235
- it('should not use the diff attribute on addition/removal', () => {
236
- const diffA = jest.fn();
237
- const diffB = jest.fn();
238
- expect(
239
- diff({a: [1]}, {b: [2]}, {a: {diff: diffA}, b: {diff: diffB}}),
240
- ).toEqual({a: null, b: [2]});
241
- expect(diffA).not.toBeCalled();
242
- expect(diffB).not.toBeCalled();
243
- });
244
-
245
- it('should do deep diffs of Objects by default', () => {
246
- expect(
247
- diff(
248
- {a: [1], b: {k: [3, 4]}, c: {k: [4, 4]}},
249
- {a: [2], b: {k: [3, 4]}, c: {k: [4, 5]}},
250
- {a: true, b: true, c: true},
251
- ),
252
- ).toEqual({a: [2], c: {k: [4, 5]}});
253
- });
254
-
255
- it('should work with undefined styles', () => {
256
- expect(
257
- diff(
258
- {style: {a: '#ffffff', b: 1}},
259
- {style: undefined},
260
- {style: {b: true}},
261
- ),
262
- ).toEqual({b: null});
263
- expect(
264
- diff(
265
- {style: undefined},
266
- {style: {a: '#ffffff', b: 1}},
267
- {style: {b: true}},
268
- ),
269
- ).toEqual({b: 1});
270
- expect(
271
- diff({style: undefined}, {style: undefined}, {style: {b: true}}),
272
- ).toEqual(null);
273
- });
274
-
275
- it('should work with empty styles', () => {
276
- expect(diff({a: 1, c: 3}, {}, {a: true, b: true})).toEqual({a: null});
277
- expect(diff({}, {a: 1, c: 3}, {a: true, b: true})).toEqual({a: 1});
278
- expect(diff({}, {}, {a: true, b: true})).toEqual(null);
279
- });
280
-
281
- it('should flatten nested styles and predefined styles', () => {
282
- const validStyleAttribute = {someStyle: {foo: true, bar: true}};
283
-
284
- expect(
285
- diff({}, {someStyle: [{foo: 1}, {bar: 2}]}, validStyleAttribute),
286
- ).toEqual({foo: 1, bar: 2});
287
-
288
- expect(
289
- diff({someStyle: [{foo: 1}, {bar: 2}]}, {}, validStyleAttribute),
290
- ).toEqual({foo: null, bar: null});
291
-
292
- const barStyle = {
293
- bar: 3,
294
- };
295
-
296
- expect(
297
- diff(
298
- {},
299
- {someStyle: [[{foo: 1}, {foo: 2}], barStyle]},
300
- validStyleAttribute,
301
- ),
302
- ).toEqual({foo: 2, bar: 3});
303
- });
304
-
305
- it('should reset a value to a previous if it is removed', () => {
306
- const validStyleAttribute = {someStyle: {foo: true, bar: true}};
307
-
308
- expect(
309
- diff(
310
- {someStyle: [{foo: 1}, {foo: 3}]},
311
- {someStyle: [{foo: 1}, {bar: 2}]},
312
- validStyleAttribute,
313
- ),
314
- ).toEqual({foo: 1, bar: 2});
315
- });
316
-
317
- it('should not clear removed props if they are still in another slot', () => {
318
- const validStyleAttribute = {someStyle: {foo: true, bar: true}};
319
-
320
- expect(
321
- diff(
322
- {someStyle: [{}, {foo: 3, bar: 2}]},
323
- {someStyle: [{foo: 3}, {bar: 2}]},
324
- validStyleAttribute,
325
- ),
326
- ).toEqual({foo: 3}); // this should ideally be null. heuristic tradeoff.
327
-
328
- expect(
329
- diff(
330
- {someStyle: [{}, {foo: 3, bar: 2}]},
331
- {someStyle: [{foo: 1, bar: 1}, {bar: 2}]},
332
- validStyleAttribute,
333
- ),
334
- ).toEqual({bar: 2, foo: 1});
335
- });
336
-
337
- it('should clear a prop if a later style is explicit null/undefined', () => {
338
- const validStyleAttribute = {someStyle: {foo: true, bar: true}};
339
- expect(
340
- diff(
341
- {someStyle: [{}, {foo: 3, bar: 2}]},
342
- {someStyle: [{foo: 1}, {bar: 2, foo: null}]},
343
- validStyleAttribute,
344
- ),
345
- ).toEqual({foo: null});
346
-
347
- expect(
348
- diff(
349
- {someStyle: [{foo: 3}, {foo: null, bar: 2}]},
350
- {someStyle: [{foo: null}, {bar: 2}]},
351
- validStyleAttribute,
352
- ),
353
- ).toEqual({foo: null});
354
-
355
- expect(
356
- diff(
357
- {someStyle: [{foo: 1}, {foo: null}]},
358
- {someStyle: [{foo: 2}, {foo: null}]},
359
- validStyleAttribute,
360
- ),
361
- ).toEqual({foo: null}); // this should ideally be null. heuristic.
362
-
363
- // Test the same case with object equality because an early bailout doesn't
364
- // work in this case.
365
- const fooObj = {foo: 3};
366
- expect(
367
- diff(
368
- {someStyle: [{foo: 1}, fooObj]},
369
- {someStyle: [{foo: 2}, fooObj]},
370
- validStyleAttribute,
371
- ),
372
- ).toEqual({foo: 3}); // this should ideally be null. heuristic.
373
-
374
- expect(
375
- diff(
376
- {someStyle: [{foo: 1}, {foo: 3}]},
377
- {someStyle: [{foo: 2}, {foo: undefined}]},
378
- validStyleAttribute,
379
- ),
380
- ).toEqual({foo: null}); // this should ideally be null. heuristic.
381
- });
382
-
383
- it('handles attributes defined multiple times', () => {
384
- const validAttributes = {foo: true, style: {foo: true}};
385
- expect(diff({}, {foo: 4, style: {foo: 2}}, validAttributes)).toEqual({
386
- foo: 2,
387
- });
388
- expect(diff({foo: 4}, {style: {foo: 2}}, validAttributes)).toEqual({
389
- foo: 2,
390
- });
391
- expect(diff({style: {foo: 2}}, {foo: 4}, validAttributes)).toEqual({
392
- foo: 4,
393
- });
394
- });
395
-
396
- // Function properties are just markers to native that events should be sent.
397
- it('should convert functions to booleans', () => {
398
- // Note that if the property changes from one function to another, we don't
399
- // need to send an update.
400
- expect(
401
- diff(
402
- {
403
- a: function () {
404
- return 1;
405
- },
406
- b: function () {
407
- return 2;
408
- },
409
- c: 3,
410
- },
411
- {
412
- b: function () {
413
- return 9;
414
- },
415
- c: function () {
416
- return 3;
417
- },
418
- },
419
- {a: true, b: true, c: true},
420
- ),
421
- ).toEqual({a: null, c: true});
422
- });
423
-
424
- it('should skip changed functions', () => {
425
- expect(
426
- diff(
427
- {
428
- a: function () {
429
- return 1;
430
- },
431
- },
432
- {
433
- a: function () {
434
- return 9;
435
- },
436
- },
437
- {a: true},
438
- ),
439
- ).toEqual(null);
440
- });
441
-
442
- it('should skip deeply-nested changed functions', () => {
443
- expect(
444
- diff(
445
- {
446
- wrapper: {
447
- a: function () {
448
- return 1;
449
- },
450
- },
451
- },
452
- {
453
- wrapper: {
454
- a: function () {
455
- return 9;
456
- },
457
- },
458
- },
459
- {wrapper: true},
460
- ),
461
- ).toEqual(null);
462
- });
463
-
464
- it('should use the process function config when prop is a function', () => {
465
- const process = jest.fn(a => a);
466
- const nextFunction = function () {};
467
- expect(
468
- diff(
469
- {
470
- a: function () {},
471
- },
472
- {
473
- a: nextFunction,
474
- },
475
- {a: {process}},
476
- ),
477
- ).toEqual({a: nextFunction});
478
- expect(process).toBeCalled();
479
- });
480
-});