Repository: facebook/react
Pull request: DevTools: Add Jest snapshot serializer for number formatting

Numbers in JavaScript can have precision issues due to how they are encoded. This shows up in snapshot tests sometimes with values like `0.0009999999999999992`, which makes the tests hard to read and visually diff.

This PR adds a new snapshot serializers which clamps numbers at 3 decimal points (e.g. the above number `0.0009999999999999992` is serialized as `0.001`). This new serializer does not impact non-numeric values, integers, and special numbers like `NaN` and `Infinity`.

The change was done in three commits to simplify the review process:
* 63e2df6: All serializers were moved into their own folder (to make tests easier to browse)
* 99ed9c7: A new numeric serializer was added
* 984f3f5: Snapshots were updated to account for the new number format

Changed files:
filename: packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js
patch: @@ -0,0 +1,24 @@
+const MAX_DECIMAL_PLACES = 3;
+
+// test() is part of Jest's serializer API
+export function test(maybeNumber) {
+  return (
+    typeof maybeNumber === 'number' &&
+    Number.isFinite(maybeNumber) &&
+    !Number.isInteger(maybeNumber) &&
+    !Number.isNaN(maybeNumber)
+  );
+}
+
+// print() is part of Jest's serializer API
+export function print(number, serialize, indent) {
+  const string = number.toString();
+  const pieces = string.split('.');
+  if (pieces.length === 2) {
+    if (pieces[1].length > MAX_DECIMAL_PLACES) {
+      return number.toFixed(MAX_DECIMAL_PLACES);
+    }
+  }
+
+  return string;
+}

filename: packages/react-devtools-shared/src/__tests__/preprocessData-test.js
patch: @@ -439,7 +439,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.004999999999999999,
+              "duration": 0.005,
               "lanes": Array [
                 9,
               ],
@@ -459,7 +459,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.002999999999999999,
+              "duration": 0.003,
               "lanes": Array [
                 9,
               ],
@@ -469,7 +469,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 9,
               ],
@@ -529,7 +529,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.004999999999999999,
+              "duration": 0.005,
               "lanes": Array [
                 9,
               ],
@@ -549,7 +549,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 0,
-              "duration": 0.002999999999999999,
+              "duration": 0.003,
               "lanes": Array [
                 9,
               ],
@@ -559,7 +559,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 9,
               ],
@@ -656,7 +656,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 0,
               ],
@@ -748,7 +748,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 0,
               ],
@@ -865,7 +865,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -887,7 +887,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012000000000000004,
+              "duration": 0.012,
               "lanes": Array [
                 4,
               ],
@@ -897,7 +897,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0030000000000000027,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -907,7 +907,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008000000000000004,
+              "duration": 0.008,
               "lanes": Array [
                 4,
               ],
@@ -917,7 +917,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -927,7 +927,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0029999999999999957,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -946,21 +946,21 @@ describe('Timeline profiler', () => {
           },
           Object {
             "componentName": "App",
-            "duration": 0.0020000000000000018,
+            "duration": 0.002,
             "timestamp": 0.018,
             "type": "passive-effect-mount",
             "warning": null,
           },
           Object {
             "componentName": "App",
-            "duration": 0.0010000000000000009,
+            "duration": 0.001,
             "timestamp": 0.023,
             "type": "render",
             "warning": null,
           },
           Object {
             "componentName": "App",
-            "duration": 0.0010000000000000009,
+            "duration": 0.001,
             "timestamp": 0.036,
             "type": "passive-effect-mount",
             "warning": null,
@@ -1052,7 +1052,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 0,
               "depth": 1,
-              "duration": 0.0009999999999999992,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -1072,7 +1072,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.012000000000000004,
+              "duration": 0.012,
               "lanes": Array [
                 4,
               ],
@@ -1082,7 +1082,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0030000000000000027,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],
@@ -1092,7 +1092,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.008000000000000004,
+              "duration": 0.008,
               "lanes": Array [
                 4,
               ],
@@ -1102,7 +1102,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 1,
-              "duration": 0.0010000000000000009,
+              "duration": 0.001,
               "lanes": Array [
                 4,
               ],
@@ -1112,7 +1112,7 @@ describe('Timeline profiler', () => {
             Object {
               "batchUID": 1,
               "depth": 0,
-              "duration": 0.0029999999999999957,
+              "duration": 0.003,
               "lanes": Array [
                 4,
               ],

filename: packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
patch: @@ -28,7 +28,7 @@ describe('StoreStress (Legacy Mode)', () => {
     act = utils.act;
     legacyRender = utils.legacyRender;
 
-    print = require('./storeSerializer').print;
+    print = require('./__serializers__/storeSerializer').print;
   });
 
   // This is a stress test for the tree mount/update/unmount traversal.

filename: packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
patch: @@ -28,7 +28,7 @@ describe('StoreStressConcurrent', () => {
     // this helper with the real thing.
     actAsync = require('./utils').actAsync;
 
-    print = require('./storeSerializer').print;
+    print = require('./__serializers__/storeSerializer').print;
   });
 
   // TODO: Remove this in favor of @gate pragma

filename: scripts/jest/config.build-devtools.js
patch: @@ -62,19 +62,22 @@ module.exports = Object.assign({}, baseConfig, {
   testRegex: 'packages/react-devtools-shared/.+/__tests__/[^]+.test.js$',
   snapshotSerializers: [
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/dehydratedValueSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/dehydratedValueSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/hookSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/hookSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/inspectedElementSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/storeSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/storeSerializer.js'
     ),
     require.resolve(
-      '../../packages/react-devtools-shared/src/__tests__/treeContextStateSerializer.js'
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/treeContextStateSerializer.js'
+    ),
+    require.resolve(
+      '../../packages/react-devtools-shared/src/__tests__/__serializers__/numberToFixedSerializer.js'
     ),
   ],
   setupFiles: [