axios-date-conversion.spec.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. import type { DateConvertible } from './axios';
  2. import { convertStringsToDates } from './axios';
  3. describe('convertStringsToDates', () => {
  4. // Test case 1: Basic conversion in a flat object
  5. test('should convert ISO date strings to Date objects in a flat object', () => {
  6. const dateString = '2023-01-15T10:00:00.000Z';
  7. const input = {
  8. id: 1,
  9. createdAt: dateString,
  10. name: 'Test Item',
  11. };
  12. const expected = {
  13. id: 1,
  14. createdAt: new Date(dateString),
  15. name: 'Test Item',
  16. };
  17. const result = convertStringsToDates(input) as Record<string, DateConvertible>;
  18. expect(result.createdAt).toBeInstanceOf(Date);
  19. if (result.createdAt instanceof Date) {
  20. expect(result.createdAt.toISOString()).toEqual(dateString);
  21. }
  22. expect(result).toEqual(expected);
  23. });
  24. // Test case 2: Nested objects
  25. test('should recursively convert ISO date strings in nested objects', () => {
  26. const dateString1 = '2023-02-20T12:30:00.000Z';
  27. const dateString2 = '2023-03-01T08:00:00.000Z';
  28. const input = {
  29. data: {
  30. item1: {
  31. updatedAt: dateString1,
  32. value: 10,
  33. },
  34. item2: {
  35. nested: {
  36. deletedAt: dateString2,
  37. isActive: false,
  38. },
  39. },
  40. },
  41. };
  42. const expected = {
  43. data: {
  44. item1: {
  45. updatedAt: new Date(dateString1),
  46. value: 10,
  47. },
  48. item2: {
  49. nested: {
  50. deletedAt: new Date(dateString2),
  51. isActive: false,
  52. },
  53. },
  54. },
  55. };
  56. const result = convertStringsToDates(input) as {
  57. data: {
  58. item1: {
  59. updatedAt: DateConvertible; // Assert 'updatedAt' later
  60. value: number;
  61. };
  62. item2: {
  63. nested: {
  64. deletedAt: DateConvertible; // Assert 'deletedAt' later
  65. isActive: boolean;
  66. };
  67. };
  68. };
  69. };
  70. expect(result.data.item1.updatedAt).toBeInstanceOf(Date);
  71. if (result.data.item1.updatedAt instanceof Date) {
  72. expect(result.data.item1.updatedAt.toISOString()).toEqual(dateString1);
  73. expect(result.data.item2.nested.deletedAt).toBeInstanceOf(Date);
  74. }
  75. if (result.data.item2.nested.deletedAt instanceof Date) {
  76. expect(result.data.item2.nested.deletedAt).toBeInstanceOf(Date);
  77. expect(result.data.item2.nested.deletedAt.toISOString()).toEqual(dateString2);
  78. }
  79. expect(result).toEqual(expected);
  80. });
  81. // Test case 3: Arrays of objects
  82. test('should recursively convert ISO date strings in arrays of objects', () => {
  83. const dateString1 = '2023-04-05T14:15:00.000Z';
  84. const dateString2 = '2023-05-10T16:00:00.000Z';
  85. const input = [
  86. { id: 1, eventDate: dateString1 },
  87. { id: 2, eventDate: dateString2, data: { nestedProp: 'value' } },
  88. ];
  89. const expected = [
  90. { id: 1, eventDate: new Date(dateString1) },
  91. { id: 2, eventDate: new Date(dateString2), data: { nestedProp: 'value' } },
  92. ];
  93. const result = convertStringsToDates(input) as [
  94. { id: number, eventDate: DateConvertible},
  95. { id: number, eventDate: DateConvertible, data: { nestedProp: string }},
  96. ];
  97. expect(result[0].eventDate).toBeInstanceOf(Date);
  98. if (result[0].eventDate instanceof Date) {
  99. expect(result[0].eventDate.toISOString()).toEqual(dateString1);
  100. }
  101. if (result[1].eventDate instanceof Date) {
  102. expect(result[1].eventDate).toBeInstanceOf(Date);
  103. expect(result[1].eventDate.toISOString()).toEqual(dateString2);
  104. }
  105. expect(result).toEqual(expected);
  106. });
  107. // Test case 4: Array containing date strings directly (though less common for this function)
  108. test('should handle arrays containing date strings directly', () => {
  109. const dateString = '2023-06-20T18:00:00.000Z';
  110. const input: [string, string, number] = ['text', dateString, 123];
  111. const expected = ['text', new Date(dateString), 123];
  112. const result = convertStringsToDates(input) as DateConvertible[];
  113. expect(result[1]).toBeInstanceOf(Date);
  114. if (result[1] instanceof Date) {
  115. expect(result[1].toISOString()).toEqual(dateString);
  116. }
  117. expect(result).toEqual(expected);
  118. });
  119. // Test case 5: Data without date strings should remain unchanged
  120. test('should not modify data without ISO date strings', () => {
  121. const input = {
  122. name: 'Product A',
  123. price: 99.99,
  124. tags: ['electronic', 'sale'],
  125. description: 'Some text',
  126. };
  127. const originalInput = JSON.parse(JSON.stringify(input)); // Deep copy to ensure no mutation
  128. const result = convertStringsToDates(input);
  129. expect(result).toEqual(originalInput); // Should be deeply equal
  130. expect(result).not.toBe(input); // Confirm it mutated the original object
  131. });
  132. // Test case 6: Null, undefined, and primitive values
  133. test('should return primitive values as is', () => {
  134. expect(convertStringsToDates(null)).toBeNull();
  135. expect(convertStringsToDates(undefined)).toBeUndefined();
  136. expect(convertStringsToDates(123)).toBe(123);
  137. expect(convertStringsToDates('hello')).toBe('hello');
  138. expect(convertStringsToDates(true)).toBe(true);
  139. });
  140. // Test case 7: Edge case - empty objects/arrays
  141. test('should handle empty objects and arrays correctly', () => {
  142. const emptyObject = {};
  143. const emptyArray = [];
  144. expect(convertStringsToDates(emptyObject)).toEqual({});
  145. expect(convertStringsToDates(emptyArray)).toEqual([]);
  146. expect(convertStringsToDates(emptyObject)).not.toBe(emptyObject);
  147. expect(convertStringsToDates(emptyArray)).toEqual(emptyArray);
  148. });
  149. // Test case 8: Date string with different milliseconds (isoDateRegex without .000)
  150. test('should handle date strings with varied milliseconds', () => {
  151. const dateString = '2023-01-15T10:00:00Z'; // No milliseconds
  152. const input = { createdAt: dateString };
  153. const expected = { createdAt: new Date(dateString) };
  154. const result = convertStringsToDates(input) as Record<string, DateConvertible>;
  155. expect(result.createdAt).toBeInstanceOf(Date);
  156. if (result.createdAt instanceof Date) {
  157. expect(result.createdAt.toISOString()).toEqual('2023-01-15T10:00:00.000Z');
  158. }
  159. expect(result).toEqual(expected);
  160. });
  161. // Test case 9: Object with null properties
  162. test('should handle objects with null properties', () => {
  163. const dateString = '2023-07-01T00:00:00.000Z';
  164. const input = {
  165. prop1: dateString,
  166. prop2: null,
  167. prop3: {
  168. nestedNull: null,
  169. nestedDate: dateString,
  170. },
  171. };
  172. const expected = {
  173. prop1: new Date(dateString),
  174. prop2: null,
  175. prop3: {
  176. nestedNull: null,
  177. nestedDate: new Date(dateString),
  178. },
  179. };
  180. const result = convertStringsToDates(input) as {
  181. prop1: DateConvertible,
  182. prop2: null,
  183. prop3: {
  184. nestedNull: null,
  185. nestedDate: DateConvertible
  186. }
  187. };
  188. expect(result.prop1).toBeInstanceOf(Date);
  189. expect(result.prop3.nestedDate).toBeInstanceOf(Date);
  190. expect(result).toEqual(expected);
  191. });
  192. // Test case 10: Date string with UTC offset (e.g., +09:00)
  193. test('should convert ISO date strings with UTC offset to Date objects', () => {
  194. const dateStringWithOffset = '2025-06-12T14:00:00+09:00';
  195. const input = {
  196. id: 2,
  197. eventTime: dateStringWithOffset,
  198. details: {
  199. lastActivity: '2025-06-12T05:00:00-04:00',
  200. },
  201. };
  202. const expected = {
  203. id: 2,
  204. eventTime: new Date(dateStringWithOffset),
  205. details: {
  206. lastActivity: new Date('2025-06-12T05:00:00-04:00'),
  207. },
  208. };
  209. const result = convertStringsToDates(input) as {
  210. id: number,
  211. eventTime: DateConvertible,
  212. details: {
  213. lastActivity: DateConvertible
  214. }
  215. };
  216. expect(result.eventTime).toBeInstanceOf(Date);
  217. if (result.eventTime instanceof Date) {
  218. expect(result.eventTime.toISOString()).toEqual(new Date(dateStringWithOffset).toISOString());
  219. }
  220. expect(result.details.lastActivity).toBeInstanceOf(Date);
  221. if (result.details.lastActivity instanceof Date) {
  222. expect(result.details.lastActivity.toISOString()).toEqual(new Date('2025-06-12T05:00:00-04:00').toISOString());
  223. }
  224. expect(result).toEqual(expected);
  225. });
  226. // Test case 11: Date string with negative UTC offset
  227. test('should convert ISO date strings with negative UTC offset (-05:00) to Date objects', () => {
  228. const dateStringWithNegativeOffset = '2025-01-01T10:00:00-05:00';
  229. const input = {
  230. startTime: dateStringWithNegativeOffset,
  231. };
  232. const expected = {
  233. startTime: new Date(dateStringWithNegativeOffset),
  234. };
  235. const result = convertStringsToDates(input) as Record<string, DateConvertible>;
  236. expect(result.startTime).toBeInstanceOf(Date);
  237. if (result.startTime instanceof Date) {
  238. expect(result.startTime.toISOString()).toEqual(new Date(dateStringWithNegativeOffset).toISOString());
  239. }
  240. expect(result).toEqual(expected);
  241. });
  242. // Test case 12: Date string with zero UTC offset (+00:00)
  243. test('should convert ISO date strings with explicit zero UTC offset (+00:00) to Date objects', () => {
  244. const dateStringWithZeroOffset = '2025-03-15T12:00:00+00:00';
  245. const input = {
  246. zeroOffsetDate: dateStringWithZeroOffset,
  247. };
  248. const expected = {
  249. zeroOffsetDate: new Date(dateStringWithZeroOffset),
  250. };
  251. const result = convertStringsToDates(input) as Record<string, DateConvertible>;
  252. expect(result.zeroOffsetDate).toBeInstanceOf(Date);
  253. if (result.zeroOffsetDate instanceof Date) {
  254. expect(result.zeroOffsetDate.toISOString()).toEqual(new Date(dateStringWithZeroOffset).toISOString());
  255. }
  256. expect(result).toEqual(expected);
  257. });
  258. // Test case 13: Date string with milliseconds and UTC offset
  259. test('should convert ISO date strings with milliseconds and UTC offset to Date objects', () => {
  260. const dateStringWithMsAndOffset = '2025-10-20T23:59:59.999-07:00';
  261. const input = {
  262. detailedTime: dateStringWithMsAndOffset,
  263. };
  264. const expected = {
  265. detailedTime: new Date(dateStringWithMsAndOffset),
  266. };
  267. const result = convertStringsToDates(input) as Record<string, DateConvertible>;
  268. expect(result.detailedTime).toBeInstanceOf(Date);
  269. if (result.detailedTime instanceof Date) {
  270. expect(result.detailedTime.toISOString()).toEqual(new Date(dateStringWithMsAndOffset).toISOString());
  271. }
  272. expect(result).toEqual(expected);
  273. });
  274. // Test case 14: Should NOT convert strings that look like dates but are NOT ISO 8601 or missing timezone
  275. test('should NOT convert non-ISO 8601 date-like strings or strings missing timezone', () => {
  276. const nonIsoDate1 = '2025/06/12 14:00:00Z'; // Wrong separator
  277. const nonIsoDate2 = '2025-06-12T14:00:00'; // Missing timezone
  278. const nonIsoDate3 = 'June 12, 2025 14:00:00 GMT'; // Different format
  279. const nonIsoDate4 = '2025-06-12T14:00:00+0900'; // Missing colon in offset
  280. const nonIsoDate5 = '2025-06-12'; // Date only
  281. const input = {
  282. date1: nonIsoDate1,
  283. date2: nonIsoDate2,
  284. date3: nonIsoDate3,
  285. date4: nonIsoDate4,
  286. date5: nonIsoDate5,
  287. someOtherString: 'hello world',
  288. };
  289. // Deep copy to ensure comparison is accurate since the function modifies in place
  290. const expected = JSON.parse(JSON.stringify(input));
  291. const result = convertStringsToDates(input) as {
  292. date1: DateConvertible,
  293. date2: DateConvertible,
  294. date3: DateConvertible,
  295. date4: DateConvertible,
  296. date5: DateConvertible,
  297. someOtherString: string,
  298. };
  299. // Assert that they remain strings (or whatever their original type was)
  300. expect(typeof result.date1).toBe('string');
  301. expect(typeof result.date2).toBe('string');
  302. expect(typeof result.date3).toBe('string');
  303. expect(typeof result.date4).toBe('string');
  304. expect(typeof result.date5).toBe('string');
  305. expect(typeof result.someOtherString).toBe('string');
  306. // Ensure the entire object is unchanged for these properties
  307. expect(result.date1).toEqual(nonIsoDate1);
  308. expect(result.date2).toEqual(nonIsoDate2);
  309. expect(result.date3).toEqual(nonIsoDate3);
  310. expect(result.date4).toEqual(nonIsoDate4);
  311. expect(result.date5).toEqual(nonIsoDate5);
  312. expect(result.someOtherString).toEqual('hello world');
  313. // Finally, assert that the overall result is identical to the input for these non-matching strings
  314. expect(result).toEqual(expected);
  315. });
  316. describe('test circular reference occurrences', () => {
  317. // Test case 1: Circular references
  318. test('should handle circular references without crashing and preserve the cycle', () => {
  319. const dateString1 = '2023-02-20T12:30:00.000Z';
  320. const dateString2 = '2023-03-01T08:00:00.000Z';
  321. const dateString3 = '2023-04-05T14:15:00.000Z';
  322. const input: any = {
  323. data: {
  324. item1: {
  325. updatedAt: dateString1,
  326. value: 10,
  327. },
  328. item2: {
  329. nested1: {
  330. deletedAt: dateString2,
  331. isActive: false,
  332. nested2: {
  333. createdAt: dateString3,
  334. parent: null as any,
  335. },
  336. },
  337. anotherItem: {
  338. someValue: 42,
  339. lastSeen: '2023-11-01T12:00:00Z',
  340. },
  341. },
  342. },
  343. };
  344. // Create a circular reference
  345. input.data.item2.nested1.nested2.parent = input;
  346. const convertedOutput = convertStringsToDates(input) as {
  347. data: {
  348. item1: {
  349. updatedAt: DateConvertible,
  350. value: number,
  351. },
  352. item2: {
  353. nested1: {
  354. deletedAt: DateConvertible,
  355. isActive: boolean,
  356. nested2: {
  357. createdAt: DateConvertible,
  358. parent: any,
  359. },
  360. },
  361. anotherItem: {
  362. someValue: number,
  363. lastSeen: DateConvertible,
  364. },
  365. },
  366. },
  367. };
  368. // Expect the function not to have thrown an error
  369. expect(convertedOutput).toBeDefined();
  370. expect(convertedOutput).toBeInstanceOf(Object);
  371. // Check if circular reference is present
  372. expect(convertedOutput.data.item2.nested1.nested2.parent).toBe(input);
  373. // Check if the date conversion worked
  374. expect(convertedOutput.data.item1.updatedAt).toBeInstanceOf(Date);
  375. if (convertedOutput.data.item1.updatedAt instanceof Date) {
  376. expect(convertedOutput.data.item1.updatedAt.toISOString()).toBe(dateString1);
  377. }
  378. expect(convertedOutput.data.item2.nested1.deletedAt).toBeInstanceOf(Date);
  379. if (convertedOutput.data.item2.nested1.deletedAt instanceof Date) {
  380. expect(convertedOutput.data.item2.nested1.deletedAt.toISOString()).toBe(dateString2);
  381. }
  382. expect(convertedOutput.data.item2.nested1.nested2.createdAt).toBeInstanceOf(Date);
  383. if (convertedOutput.data.item2.nested1.nested2.createdAt instanceof Date) {
  384. expect(convertedOutput.data.item2.nested1.nested2.createdAt.toISOString()).toBe(dateString3);
  385. }
  386. expect(convertedOutput.data.item2.anotherItem.lastSeen).toBeInstanceOf(Date);
  387. if (convertedOutput.data.item2.anotherItem.lastSeen instanceof Date) {
  388. expect(convertedOutput.data.item2.anotherItem.lastSeen.toISOString()).toBe(new Date(input.data.item2.anotherItem.lastSeen).toISOString());
  389. }
  390. });
  391. // Test case 2: Direct self-reference
  392. test('should work when encountering direct self-references', () => {
  393. const obj: any = {};
  394. obj.self = obj;
  395. obj.createdAt = '2023-02-01T00:00:00Z';
  396. const converted = convertStringsToDates(obj) as Record<string, DateConvertible>;
  397. expect(converted).toBeDefined();
  398. expect(converted.self).toBe(obj);
  399. expect(converted.createdAt).toBeInstanceOf(Date);
  400. });
  401. });
  402. });