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.  * @flow
    
  8.  */
    
  9. 
    
  10. import {
    
  11.   getDisplayName,
    
  12.   getDisplayNameForReactElement,
    
  13.   isPlainObject,
    
  14. } from 'react-devtools-shared/src/utils';
    
  15. import {stackToComponentSources} from 'react-devtools-shared/src/devtools/utils';
    
  16. import {
    
  17.   format,
    
  18.   formatWithStyles,
    
  19.   gt,
    
  20.   gte,
    
  21. } from 'react-devtools-shared/src/backend/utils';
    
  22. import {
    
  23.   REACT_SUSPENSE_LIST_TYPE as SuspenseList,
    
  24.   REACT_STRICT_MODE_TYPE as StrictMode,
    
  25. } from 'shared/ReactSymbols';
    
  26. import {createElement} from 'react/src/ReactElement';
    
  27. 
    
  28. describe('utils', () => {
    
  29.   describe('getDisplayName', () => {
    
  30.     // @reactVersion >= 16.0
    
  31.     it('should return a function name', () => {
    
  32.       function FauxComponent() {}
    
  33.       expect(getDisplayName(FauxComponent)).toEqual('FauxComponent');
    
  34.     });
    
  35. 
    
  36.     // @reactVersion >= 16.0
    
  37.     it('should return a displayName name if specified', () => {
    
  38.       function FauxComponent() {}
    
  39.       FauxComponent.displayName = 'OverrideDisplayName';
    
  40.       expect(getDisplayName(FauxComponent)).toEqual('OverrideDisplayName');
    
  41.     });
    
  42. 
    
  43.     // @reactVersion >= 16.0
    
  44.     it('should return the fallback for anonymous functions', () => {
    
  45.       expect(getDisplayName(() => {}, 'Fallback')).toEqual('Fallback');
    
  46.     });
    
  47. 
    
  48.     // @reactVersion >= 16.0
    
  49.     it('should return Anonymous for anonymous functions without a fallback', () => {
    
  50.       expect(getDisplayName(() => {})).toEqual('Anonymous');
    
  51.     });
    
  52. 
    
  53.     // Simulate a reported bug:
    
  54.     // https://github.com/facebook/react/issues/16685
    
  55.     // @reactVersion >= 16.0
    
  56.     it('should return a fallback when the name prop is not a string', () => {
    
  57.       const FauxComponent = {name: {}};
    
  58.       expect(getDisplayName(FauxComponent, 'Fallback')).toEqual('Fallback');
    
  59.     });
    
  60. 
    
  61.     it('should parse a component stack trace', () => {
    
  62.       expect(
    
  63.         stackToComponentSources(`
    
  64.     at Foobar (http://localhost:3000/static/js/bundle.js:103:74)
    
  65.     at a
    
  66.     at header
    
  67.     at div
    
  68.     at App`),
    
  69.       ).toEqual([
    
  70.         ['Foobar', ['http://localhost:3000/static/js/bundle.js', 103, 74]],
    
  71.         ['a', null],
    
  72.         ['header', null],
    
  73.         ['div', null],
    
  74.         ['App', null],
    
  75.       ]);
    
  76.     });
    
  77.   });
    
  78. 
    
  79.   describe('getDisplayNameForReactElement', () => {
    
  80.     // @reactVersion >= 16.0
    
  81.     it('should return correct display name for an element with function type', () => {
    
  82.       function FauxComponent() {}
    
  83.       FauxComponent.displayName = 'OverrideDisplayName';
    
  84.       const element = createElement(FauxComponent);
    
  85.       expect(getDisplayNameForReactElement(element)).toEqual(
    
  86.         'OverrideDisplayName',
    
  87.       );
    
  88.     });
    
  89. 
    
  90.     // @reactVersion >= 16.0
    
  91.     it('should return correct display name for an element with a type of StrictMode', () => {
    
  92.       const element = createElement(StrictMode);
    
  93.       expect(getDisplayNameForReactElement(element)).toEqual('StrictMode');
    
  94.     });
    
  95. 
    
  96.     // @reactVersion >= 16.0
    
  97.     it('should return correct display name for an element with a type of SuspenseList', () => {
    
  98.       const element = createElement(SuspenseList);
    
  99.       expect(getDisplayNameForReactElement(element)).toEqual('SuspenseList');
    
  100.     });
    
  101. 
    
  102.     // @reactVersion >= 16.0
    
  103.     it('should return NotImplementedInDevtools for an element with invalid symbol type', () => {
    
  104.       const element = createElement(Symbol('foo'));
    
  105.       expect(getDisplayNameForReactElement(element)).toEqual(
    
  106.         'NotImplementedInDevtools',
    
  107.       );
    
  108.     });
    
  109. 
    
  110.     // @reactVersion >= 16.0
    
  111.     it('should return NotImplementedInDevtools for an element with invalid type', () => {
    
  112.       const element = createElement(true);
    
  113.       expect(getDisplayNameForReactElement(element)).toEqual(
    
  114.         'NotImplementedInDevtools',
    
  115.       );
    
  116.     });
    
  117. 
    
  118.     // @reactVersion >= 16.0
    
  119.     it('should return Element for null type', () => {
    
  120.       const element = createElement();
    
  121.       expect(getDisplayNameForReactElement(element)).toEqual('Element');
    
  122.     });
    
  123.   });
    
  124. 
    
  125.   describe('format', () => {
    
  126.     // @reactVersion >= 16.0
    
  127.     it('should format simple strings', () => {
    
  128.       expect(format('a', 'b', 'c')).toEqual('a b c');
    
  129.     });
    
  130. 
    
  131.     // @reactVersion >= 16.0
    
  132.     it('should format multiple argument types', () => {
    
  133.       expect(format('abc', 123, true)).toEqual('abc 123 true');
    
  134.     });
    
  135. 
    
  136.     // @reactVersion >= 16.0
    
  137.     it('should support string substitutions', () => {
    
  138.       expect(format('a %s b %s c', 123, true)).toEqual('a 123 b true c');
    
  139.     });
    
  140. 
    
  141.     // @reactVersion >= 16.0
    
  142.     it('should gracefully handle Symbol types', () => {
    
  143.       expect(format(Symbol('a'), 'b', Symbol('c'))).toEqual(
    
  144.         'Symbol(a) b Symbol(c)',
    
  145.       );
    
  146.     });
    
  147. 
    
  148.     // @reactVersion >= 16.0
    
  149.     it('should gracefully handle Symbol type for the first argument', () => {
    
  150.       expect(format(Symbol('abc'), 123)).toEqual('Symbol(abc) 123');
    
  151.     });
    
  152.   });
    
  153. 
    
  154.   describe('formatWithStyles', () => {
    
  155.     // @reactVersion >= 16.0
    
  156.     it('should format empty arrays', () => {
    
  157.       expect(formatWithStyles([])).toEqual([]);
    
  158.       expect(formatWithStyles([], 'gray')).toEqual([]);
    
  159.       expect(formatWithStyles(undefined)).toEqual(undefined);
    
  160.     });
    
  161. 
    
  162.     // @reactVersion >= 16.0
    
  163.     it('should bail out of strings with styles', () => {
    
  164.       expect(
    
  165.         formatWithStyles(['%ca', 'color: green', 'b', 'c'], 'color: gray'),
    
  166.       ).toEqual(['%ca', 'color: green', 'b', 'c']);
    
  167.     });
    
  168. 
    
  169.     // @reactVersion >= 16.0
    
  170.     it('should format simple strings', () => {
    
  171.       expect(formatWithStyles(['a'])).toEqual(['a']);
    
  172. 
    
  173.       expect(formatWithStyles(['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
    
  174.       expect(formatWithStyles(['a'], 'color: gray')).toEqual([
    
  175.         '%c%s',
    
  176.         'color: gray',
    
  177.         'a',
    
  178.       ]);
    
  179.       expect(formatWithStyles(['a', 'b', 'c'], 'color: gray')).toEqual([
    
  180.         '%c%s %s %s',
    
  181.         'color: gray',
    
  182.         'a',
    
  183.         'b',
    
  184.         'c',
    
  185.       ]);
    
  186.     });
    
  187. 
    
  188.     // @reactVersion >= 16.0
    
  189.     it('should format string substituions', () => {
    
  190.       expect(
    
  191.         formatWithStyles(['%s %s %s', 'a', 'b', 'c'], 'color: gray'),
    
  192.       ).toEqual(['%c%s %s %s', 'color: gray', 'a', 'b', 'c']);
    
  193. 
    
  194.       // The last letter isn't gray here but I think it's not a big
    
  195.       // deal, since there is a string substituion but it's incorrect
    
  196.       expect(formatWithStyles(['%s %s', 'a', 'b', 'c'], 'color: gray')).toEqual(
    
  197.         ['%c%s %s', 'color: gray', 'a', 'b', 'c'],
    
  198.       );
    
  199.     });
    
  200. 
    
  201.     // @reactVersion >= 16.0
    
  202.     it('should support multiple argument types', () => {
    
  203.       const symbol = Symbol('a');
    
  204.       expect(
    
  205.         formatWithStyles(
    
  206.           ['abc', 123, 12.3, true, {hello: 'world'}, symbol],
    
  207.           'color: gray',
    
  208.         ),
    
  209.       ).toEqual([
    
  210.         '%c%s %i %f %s %o %s',
    
  211.         'color: gray',
    
  212.         'abc',
    
  213.         123,
    
  214.         12.3,
    
  215.         true,
    
  216.         {hello: 'world'},
    
  217.         symbol,
    
  218.       ]);
    
  219.     });
    
  220. 
    
  221.     // @reactVersion >= 16.0
    
  222.     it('should properly format escaped string substituions', () => {
    
  223.       expect(formatWithStyles(['%%s'], 'color: gray')).toEqual([
    
  224.         '%c%s',
    
  225.         'color: gray',
    
  226.         '%%s',
    
  227.       ]);
    
  228.       expect(formatWithStyles(['%%c'], 'color: gray')).toEqual([
    
  229.         '%c%s',
    
  230.         'color: gray',
    
  231.         '%%c',
    
  232.       ]);
    
  233.       expect(formatWithStyles(['%%c%c'], 'color: gray')).toEqual(['%%c%c']);
    
  234.     });
    
  235. 
    
  236.     // @reactVersion >= 16.0
    
  237.     it('should format non string inputs as the first argument', () => {
    
  238.       expect(formatWithStyles([{foo: 'bar'}])).toEqual([{foo: 'bar'}]);
    
  239.       expect(formatWithStyles([[1, 2, 3]])).toEqual([[1, 2, 3]]);
    
  240.       expect(formatWithStyles([{foo: 'bar'}], 'color: gray')).toEqual([
    
  241.         '%c%o',
    
  242.         'color: gray',
    
  243.         {foo: 'bar'},
    
  244.       ]);
    
  245.       expect(formatWithStyles([[1, 2, 3]], 'color: gray')).toEqual([
    
  246.         '%c%o',
    
  247.         'color: gray',
    
  248.         [1, 2, 3],
    
  249.       ]);
    
  250.       expect(formatWithStyles([{foo: 'bar'}, 'hi'], 'color: gray')).toEqual([
    
  251.         '%c%o %s',
    
  252.         'color: gray',
    
  253.         {foo: 'bar'},
    
  254.         'hi',
    
  255.       ]);
    
  256.     });
    
  257.   });
    
  258. 
    
  259.   describe('semver comparisons', () => {
    
  260.     it('gte should compare versions correctly', () => {
    
  261.       expect(gte('1.2.3', '1.2.1')).toBe(true);
    
  262.       expect(gte('1.2.1', '1.2.1')).toBe(true);
    
  263.       expect(gte('1.2.1', '1.2.2')).toBe(false);
    
  264.       expect(gte('10.0.0', '9.0.0')).toBe(true);
    
  265.     });
    
  266. 
    
  267.     it('gt should compare versions correctly', () => {
    
  268.       expect(gt('1.2.3', '1.2.1')).toBe(true);
    
  269.       expect(gt('1.2.1', '1.2.1')).toBe(false);
    
  270.       expect(gt('1.2.1', '1.2.2')).toBe(false);
    
  271.       expect(gte('10.0.0', '9.0.0')).toBe(true);
    
  272.     });
    
  273.   });
    
  274. 
    
  275.   describe('isPlainObject', () => {
    
  276.     it('should return true for plain objects', () => {
    
  277.       expect(isPlainObject({})).toBe(true);
    
  278.       expect(isPlainObject({a: 1})).toBe(true);
    
  279.       expect(isPlainObject({a: {b: {c: 123}}})).toBe(true);
    
  280.     });
    
  281. 
    
  282.     it('should return false if object is a class instance', () => {
    
  283.       expect(isPlainObject(new (class C {})())).toBe(false);
    
  284.     });
    
  285. 
    
  286.     it('should return false for objects, which have not only Object in its prototype chain', () => {
    
  287.       expect(isPlainObject([])).toBe(false);
    
  288.       expect(isPlainObject(Symbol())).toBe(false);
    
  289.     });
    
  290. 
    
  291.     it('should return false for primitives', () => {
    
  292.       expect(isPlainObject(5)).toBe(false);
    
  293.       expect(isPlainObject(true)).toBe(false);
    
  294.     });
    
  295. 
    
  296.     it('should return true for objects with no prototype', () => {
    
  297.       expect(isPlainObject(Object.create(null))).toBe(true);
    
  298.     });
    
  299.   });
    
  300. });