object.test.ts 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /**
  2. * Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
  3. * SPDX-License-Identifier: MIT
  4. */
  5. import { describe, expect, it } from 'vitest';
  6. import { getIn, isEmptyArray, isNaN, isPromise, shallowSetIn } from '../src/utils';
  7. describe('object', () => {
  8. describe('isEmptyArray', () => {
  9. it('returns true when an empty array is passed in', () => {
  10. expect(isEmptyArray([])).toBe(true);
  11. });
  12. it('returns false when anything other than empty array is passed in', () => {
  13. expect(isEmptyArray()).toBe(false);
  14. expect(isEmptyArray(null)).toBe(false);
  15. expect(isEmptyArray(123)).toBe(false);
  16. expect(isEmptyArray('abc')).toBe(false);
  17. expect(isEmptyArray({})).toBe(false);
  18. expect(isEmptyArray({ a: 1 })).toBe(false);
  19. expect(isEmptyArray(['abc'])).toBe(false);
  20. });
  21. });
  22. describe('getIn', () => {
  23. const obj = {
  24. a: {
  25. b: 2,
  26. c: false,
  27. d: null,
  28. },
  29. t: true,
  30. s: 'a random string',
  31. };
  32. it('gets a value by array path', () => {
  33. expect(getIn(obj, ['a', 'b'])).toBe(2);
  34. });
  35. it('gets a value by string path', () => {
  36. expect(getIn(obj, 'a.b')).toBe(2);
  37. });
  38. it('return "undefined" if value was not found using given path', () => {
  39. expect(getIn(obj, 'a.z')).toBeUndefined();
  40. });
  41. it('return "undefined" if value was not found using given path and an intermediate value is "false"', () => {
  42. expect(getIn(obj, 'a.c.z')).toBeUndefined();
  43. });
  44. it('return "undefined" if value was not found using given path and an intermediate value is "null"', () => {
  45. expect(getIn(obj, 'a.d.z')).toBeUndefined();
  46. });
  47. it('return "undefined" if value was not found using given path and an intermediate value is "true"', () => {
  48. expect(getIn(obj, 't.z')).toBeUndefined();
  49. });
  50. it('return "undefined" if value was not found using given path and an intermediate value is a string', () => {
  51. expect(getIn(obj, 's.z')).toBeUndefined();
  52. });
  53. });
  54. describe('shallowSetIn', () => {
  55. it('sets flat value', () => {
  56. const obj = { x: 'y' };
  57. const newObj = shallowSetIn(obj, 'flat', 'value');
  58. expect(obj).toEqual({ x: 'y' });
  59. expect(newObj).toEqual({ x: 'y', flat: 'value' });
  60. });
  61. it('keep the same object if nothing is changed', () => {
  62. const obj = { x: 'y' };
  63. const newObj = shallowSetIn(obj, 'x', 'y');
  64. expect(obj).toBe(newObj);
  65. });
  66. it('keep key shen set undefined', () => {
  67. const obj = { x: 'y' };
  68. const newObj = shallowSetIn(obj, 'x', undefined);
  69. expect(obj).toEqual({ x: 'y' });
  70. expect(newObj).toEqual({ x: undefined });
  71. expect(Object.keys(newObj)).toEqual(['x']);
  72. });
  73. it('sets nested value', () => {
  74. const obj = { x: 'y' };
  75. const newObj = shallowSetIn(obj, 'nested.value', 'nested value');
  76. expect(obj).toEqual({ x: 'y' });
  77. expect(newObj).toEqual({ x: 'y', nested: { value: 'nested value' } });
  78. });
  79. it('updates nested value', () => {
  80. const obj = { x: 'y', nested: { value: 'a' } };
  81. const newObj = shallowSetIn(obj, 'nested.value', 'b');
  82. expect(obj).toEqual({ x: 'y', nested: { value: 'a' } });
  83. expect(newObj).toEqual({ x: 'y', nested: { value: 'b' } });
  84. });
  85. it('updates deep nested value', () => {
  86. const obj = { x: 'y', twofoldly: { nested: { value: 'a' } } };
  87. const newObj = shallowSetIn(obj, 'twofoldly.nested.value', 'b');
  88. expect(obj.twofoldly.nested === newObj.twofoldly.nested).toEqual(false); // fails, same object still
  89. expect(obj).toEqual({ x: 'y', twofoldly: { nested: { value: 'a' } } }); // fails, it's b here, too
  90. expect(newObj).toEqual({ x: 'y', twofoldly: { nested: { value: 'b' } } }); // works ofc
  91. });
  92. it('shallow clone data along the update path', () => {
  93. const obj = {
  94. x: 'y',
  95. twofoldly: { nested: ['a', { c: 'd' }] },
  96. other: { nestedOther: 'o' },
  97. };
  98. const newObj = shallowSetIn(obj, 'twofoldly.nested.0', 'b');
  99. // All new objects/arrays created along the update path.
  100. expect(obj).not.toBe(newObj);
  101. expect(obj.twofoldly).not.toBe(newObj.twofoldly);
  102. expect(obj.twofoldly.nested).not.toBe(newObj.twofoldly.nested);
  103. // All other objects/arrays copied, not cloned (retain same memory
  104. // location).
  105. expect(obj.other).toBe(newObj.other);
  106. expect(obj.twofoldly.nested[1]).toBe(newObj.twofoldly.nested[1]);
  107. });
  108. it('sets new array', () => {
  109. const obj = { x: 'y' };
  110. const newObj = shallowSetIn(obj, 'nested.0', 'value');
  111. expect(obj).toEqual({ x: 'y' });
  112. expect(newObj).toEqual({ x: 'y', nested: ['value'] });
  113. });
  114. it('sets new array when item is empty string', () => {
  115. const obj = { x: 'y' };
  116. const newObj = shallowSetIn(obj, 'nested.0', '');
  117. expect(obj).toEqual({ x: 'y' });
  118. expect(newObj).toEqual({ x: 'y', nested: [''] });
  119. });
  120. it('sets new array when item is empty string', () => {
  121. const obj = {};
  122. const newObj = shallowSetIn(obj, 'nested.0', '');
  123. expect(obj).toEqual({});
  124. expect(newObj).toEqual({ nested: [''] });
  125. });
  126. it('updates nested array value', () => {
  127. const obj = { x: 'y', nested: ['a'] };
  128. const newObj = shallowSetIn(obj, 'nested[0]', 'b');
  129. expect(obj).toEqual({ x: 'y', nested: ['a'] });
  130. expect(newObj).toEqual({ x: 'y', nested: ['b'] });
  131. });
  132. it('adds new item to nested array', () => {
  133. const obj = { x: 'y', nested: ['a'] };
  134. const newObj = shallowSetIn(obj, 'nested.1', 'b');
  135. expect(obj).toEqual({ x: 'y', nested: ['a'] });
  136. expect(newObj).toEqual({ x: 'y', nested: ['a', 'b'] });
  137. });
  138. it('sticks to object with int key when defined', () => {
  139. const obj = { x: 'y', nested: { 0: 'a' } };
  140. const newObj = shallowSetIn(obj, 'nested.0', 'b');
  141. expect(obj).toEqual({ x: 'y', nested: { 0: 'a' } });
  142. expect(newObj).toEqual({ x: 'y', nested: { 0: 'b' } });
  143. });
  144. it('supports bracket path', () => {
  145. const obj = { x: 'y' };
  146. const newObj = shallowSetIn(obj, 'nested[0]', 'value');
  147. expect(obj).toEqual({ x: 'y' });
  148. expect(newObj).toEqual({ x: 'y', nested: ['value'] });
  149. });
  150. it('supports path containing key of the object', () => {
  151. const obj = { x: 'y' };
  152. const newObj = shallowSetIn(obj, 'a.x.c', 'value');
  153. expect(obj).toEqual({ x: 'y' });
  154. expect(newObj).toEqual({ x: 'y', a: { x: { c: 'value' } } });
  155. });
  156. // This case is not used in form sdk for now,so we comment it.
  157. // it('should keep class inheritance for the top level object', () => {
  158. // class TestClass {
  159. // constructor(public key: string, public setObj?: any) {}
  160. // }
  161. // const obj = new TestClass('value');
  162. // const newObj = shallowSetIn(obj, 'setObj.nested', 'shallowSetInValue');
  163. // expect(obj).toEqual(new TestClass('value'));
  164. // expect(newObj).toEqual({
  165. // key: 'value',
  166. // setObj: { nested: 'shallowSetInValue' },
  167. // });
  168. // expect(obj instanceof TestClass).toEqual(true);
  169. // expect(newObj instanceof TestClass).toEqual(true);
  170. // });
  171. it('can convert primitives to objects before setting', () => {
  172. const obj = { x: [{ y: true }] };
  173. const newObj = shallowSetIn(obj, 'x.0.y.z', true);
  174. expect(obj).toEqual({ x: [{ y: true }] });
  175. expect(newObj).toEqual({ x: [{ y: { z: true } }] });
  176. });
  177. it('set undefined value with unknown key', () => {
  178. const obj = { a: '' };
  179. let newObj = shallowSetIn(obj, 'a', undefined);
  180. newObj = shallowSetIn(newObj, 'b', undefined);
  181. expect(obj).toEqual({ a: '' });
  182. expect(newObj).toEqual({ a: undefined, b: undefined });
  183. });
  184. });
  185. describe('isPromise', () => {
  186. it('verifies that a value is a promise', () => {
  187. const alwaysResolve = (resolve: Function) => resolve();
  188. const promise = new Promise(alwaysResolve);
  189. expect(isPromise(promise)).toEqual(true);
  190. });
  191. it('verifies that a value is not a promise', () => {
  192. const emptyObject = {};
  193. const identity = (i: any) => i;
  194. const foo = 'foo';
  195. const answerToLife = 42;
  196. expect(isPromise(emptyObject)).toEqual(false);
  197. expect(isPromise(identity)).toEqual(false);
  198. expect(isPromise(foo)).toEqual(false);
  199. expect(isPromise(answerToLife)).toEqual(false);
  200. expect(isPromise(undefined)).toEqual(false);
  201. expect(isPromise(null)).toEqual(false);
  202. });
  203. });
  204. describe('isNaN', () => {
  205. it('correctly validate NaN', () => {
  206. expect(isNaN(NaN)).toBe(true);
  207. });
  208. it('correctly validate not NaN', () => {
  209. expect(isNaN(undefined)).toBe(false);
  210. expect(isNaN(1)).toBe(false);
  211. expect(isNaN('')).toBe(false);
  212. expect(isNaN([])).toBe(false);
  213. });
  214. });
  215. });