Repository: facebook/react
Pull request: Use `act()` in ReactFabric tests

Re-landing #21839

The public `act` API is not supported in production mode, only development. But in the React repo, we run our tests in both production and development mode, because we're testing React itself.

When writing tests in the React repo, we use a special, internal-only implementation of `act` that works in production, available as `require('jest-react').act`.

This PR is the same as #21839, but it uses the internal `act` API instead.

Changed files:
filename: packages/react-native-renderer/src/__tests__/ReactFabric-test.internal.js
patch: @@ -16,6 +16,7 @@ let createReactNativeComponentClass;
 let UIManager;
 let StrictMode;
 let TextInputState;
+let act;
 
 const SET_NATIVE_PROPS_NOT_SUPPORTED_MESSAGE =
   'Warning: setNativeProps is not currently supported in Fabric';
@@ -47,6 +48,8 @@ describe('ReactFabric', () => {
       .ReactNativeViewConfigRegistry.register;
     TextInputState = require('react-native/Libraries/ReactPrivate/ReactNativePrivateInterface')
       .TextInputState;
+
+    act = require('jest-react').act;
   });
 
   it('should be able to create and render a native component', () => {
@@ -55,7 +58,9 @@ describe('ReactFabric', () => {
       uiViewClassName: 'RCTView',
     }));
 
-    ReactFabric.render(<View foo="test" />, 1);
+    act(() => {
+      ReactFabric.render(<View foo="test" />, 1);
+    });
     expect(nativeFabricUIManager.createNode).toBeCalled();
     expect(nativeFabricUIManager.appendChild).not.toBeCalled();
     expect(nativeFabricUIManager.completeRoot).toBeCalled();
@@ -71,11 +76,15 @@ describe('ReactFabric', () => {
 
     nativeFabricUIManager.createNode.mockReturnValue(firstNode);
 
-    ReactFabric.render(<View foo="foo" />, 11);
+    act(() => {
+      ReactFabric.render(<View foo="foo" />, 11);
+    });
 
     expect(nativeFabricUIManager.createNode).toHaveBeenCalledTimes(1);
 
-    ReactFabric.render(<View foo="bar" />, 11);
+    act(() => {
+      ReactFabric.render(<View foo="bar" />, 11);
+    });
 
     expect(nativeFabricUIManager.createNode).toHaveBeenCalledTimes(1);
     expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
@@ -97,7 +106,9 @@ describe('ReactFabric', () => {
       uiViewClassName: 'RCTText',
     }));
 
-    ReactFabric.render(<Text foo="a">1</Text>, 11);
+    act(() => {
+      ReactFabric.render(<Text foo="a">1</Text>, 11);
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
@@ -106,7 +117,9 @@ describe('ReactFabric', () => {
     ).not.toBeCalled();
 
     // If no properties have changed, we shouldn't call cloneNode.
-    ReactFabric.render(<Text foo="a">1</Text>, 11);
+    act(() => {
+      ReactFabric.render(<Text foo="a">1</Text>, 11);
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
@@ -115,7 +128,9 @@ describe('ReactFabric', () => {
     ).not.toBeCalled();
 
     // Only call cloneNode for the changed property (and not for text).
-    ReactFabric.render(<Text foo="b">1</Text>, 11);
+    act(() => {
+      ReactFabric.render(<Text foo="b">1</Text>, 11);
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewProps).toHaveBeenCalledTimes(
@@ -126,7 +141,9 @@ describe('ReactFabric', () => {
     ).not.toBeCalled();
 
     // Only call cloneNode for the changed text (and no other properties).
-    ReactFabric.render(<Text foo="b">2</Text>, 11);
+    act(() => {
+      ReactFabric.render(<Text foo="b">2</Text>, 11);
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(
       nativeFabricUIManager.cloneNodeWithNewChildren,
@@ -139,7 +156,9 @@ describe('ReactFabric', () => {
     ).not.toBeCalled();
 
     // Call cloneNode for both changed text and properties.
-    ReactFabric.render(<Text foo="c">3</Text>, 11);
+    act(() => {
+      ReactFabric.render(<Text foo="c">3</Text>, 11);
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(
       nativeFabricUIManager.cloneNodeWithNewChildren,
@@ -158,25 +177,29 @@ describe('ReactFabric', () => {
       uiViewClassName: 'RCTText',
     }));
 
-    ReactFabric.render(
-      <Text foo="a" bar="a">
-        1
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text foo="a" bar="a">
+          1
+        </Text>,
+        11,
+      );
+    });
     expect(nativeFabricUIManager.cloneNode).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewChildren).not.toBeCalled();
     expect(nativeFabricUIManager.cloneNodeWithNewProps).not.toBeCalled();
     expect(
       nativeFabricUIManager.cloneNodeWithNewChildrenAndProps,
     ).not.toBeCalled();
 
-    ReactFabric.render(
-      <Text foo="a" bar="b">
-        1
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text foo="a" bar="b">
+          1
+        </Text>,
+        11,
+      );
+    });
     expect(
       nativeFabricUIManager.cloneNodeWithNewProps.mock.calls[0][1],
     ).toEqual({
@@ -186,12 +209,14 @@ describe('ReactFabric', () => {
       nativeFabricUIManager.__dumpHierarchyForJestTestsOnly(),
     ).toMatchSnapshot();
 
-    ReactFabric.render(
-      <Text foo="b" bar="b">
-        2
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text foo="b" bar="b">
+          2
+        </Text>,
+        11,
+      );
+    });
     expect(
       nativeFabricUIManager.cloneNodeWithNewChildrenAndProps.mock.calls[0][1],
     ).toEqual({
@@ -211,15 +236,17 @@ describe('ReactFabric', () => {
     UIManager.updateView.mockReset();
 
     let viewRef;
-    ReactFabric.render(
-      <View
-        foo="bar"
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View
+          foo="bar"
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
     expect(UIManager.updateView).not.toBeCalled();
 
     expect(() => {
@@ -247,14 +274,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.dispatchCommand.mockClear();
 
     let viewRef;
-    ReactFabric.render(
-      <View
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.dispatchCommand).not.toBeCalled();
     ReactFabric.dispatchCommand(viewRef, 'updateCommand', [10, 20]);
@@ -274,14 +303,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.dispatchCommand.mockReset();
 
     let viewRef;
-    ReactFabric.render(
-      <BasicClass
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <BasicClass
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.dispatchCommand).not.toBeCalled();
     expect(() => {
@@ -302,14 +333,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.sendAccessibilityEvent.mockClear();
 
     let viewRef;
-    ReactFabric.render(
-      <View
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.sendAccessibilityEvent).not.toBeCalled();
     ReactFabric.sendAccessibilityEvent(viewRef, 'focus');
@@ -332,14 +365,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.sendAccessibilityEvent.mockReset();
 
     let viewRef;
-    ReactFabric.render(
-      <BasicClass
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <BasicClass
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.sendAccessibilityEvent).not.toBeCalled();
     expect(() => {
@@ -360,14 +395,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.measure.mockClear();
 
     let viewRef;
-    ReactFabric.render(
-      <View
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.measure).not.toBeCalled();
     const successCallback = jest.fn();
@@ -386,14 +423,16 @@ describe('ReactFabric', () => {
     nativeFabricUIManager.measureInWindow.mockClear();
 
     let viewRef;
-    ReactFabric.render(
-      <View
-        ref={ref => {
-          viewRef = ref;
-        }}
-      />,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View
+          ref={ref => {
+            viewRef = ref;
+          }}
+        />,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.measureInWindow).not.toBeCalled();
     const successCallback = jest.fn();
@@ -413,22 +452,24 @@ describe('ReactFabric', () => {
 
     let viewRef;
     let otherRef;
-    ReactFabric.render(
-      <View>
-        <View
-          foo="bar"
-          ref={ref => {
-            viewRef = ref;
-          }}
-        />
-        <View
-          ref={ref => {
-            otherRef = ref;
-          }}
-        />
-      </View>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View>
+          <View
+            foo="bar"
+            ref={ref => {
+              viewRef = ref;
+            }}
+          />
+          <View
+            ref={ref => {
+              otherRef = ref;
+            }}
+          />
+        </View>,
+        11,
+      );
+    });
 
     expect(nativeFabricUIManager.measureLayout).not.toBeCalled();
     const successCallback = jest.fn();
@@ -483,12 +524,16 @@ describe('ReactFabric', () => {
     const before = 'abcdefghijklmnopqrst';
     const after = 'mxhpgwfralkeoivcstzy';
 
-    ReactFabric.render(<Component chars={before} />, 11);
+    act(() => {
+      ReactFabric.render(<Component chars={before} />, 11);
+    });
     expect(
       nativeFabricUIManager.__dumpHierarchyForJestTestsOnly(),
     ).toMatchSnapshot();
 
-    ReactFabric.render(<Component chars={after} />, 11);
+    act(() => {
+      ReactFabric.render(<Component chars={after} />, 11);
+    });
     expect(
       nativeFabricUIManager.__dumpHierarchyForJestTestsOnly(),
     ).toMatchSnapshot();
@@ -521,12 +566,14 @@ describe('ReactFabric', () => {
 
     const ref = React.createRef();
     // Wrap in a host node.
-    ReactFabric.render(
-      <View>
-        <Component ref={ref} />
-      </View>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View>
+          <Component ref={ref} />
+        </View>,
+        11,
+      );
+    });
     expect(
       nativeFabricUIManager.__dumpHierarchyForJestTestsOnly(),
     ).toMatchSnapshot();
@@ -552,7 +599,9 @@ describe('ReactFabric', () => {
       }
     }
 
-    ReactFabric.render(<Component />, 11);
+    act(() => {
+      ReactFabric.render(<Component />, 11);
+    });
     expect(mockArgs.length).toEqual(0);
   });
 
@@ -572,12 +621,14 @@ describe('ReactFabric', () => {
       );
     });
 
-    ReactFabric.render(
-      <View foo="a">
-        <View foo="b" />
-      </View>,
-      22,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View foo="a">
+          <View foo="b" />
+        </View>,
+        22,
+      );
+    });
     expect(snapshots).toMatchSnapshot();
   });
 
@@ -595,19 +646,23 @@ describe('ReactFabric', () => {
       uiViewClassName: 'RCTView',
     }));
 
-    ReactFabric.render(
-      <Text>
-        <View />
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text>
+          <View />
+        </Text>,
+        11,
+      );
+    });
 
-    ReactFabric.render(
-      <Text>
-        <Image />
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text>
+          <Image />
+        </Text>,
+        11,
+      );
+    });
   });
 
   it('should throw for text not inside of a <Text> ancestor', () => {
@@ -624,18 +679,22 @@ describe('ReactFabric', () => {
       uiViewClassName: 'RCTView',
     }));
 
-    expect(() => ReactFabric.render(<View>this should warn</View>, 11)).toThrow(
-      'Text strings must be rendered within a <Text> component.',
-    );
+    expect(() => {
+      act(() => {
+        ReactFabric.render(<View>this should warn</View>, 11);
+      });
+    }).toThrow('Text strings must be rendered within a <Text> component.');
 
-    expect(() =>
-      ReactFabric.render(
-        <Text>
-          <ScrollView>hi hello hi</ScrollView>
-        </Text>,
-        11,
-      ),
-    ).toThrow('Text strings must be rendered within a <Text> component.');
+    expect(() => {
+      act(() => {
+        ReactFabric.render(
+          <Text>
+            <ScrollView>hi hello hi</ScrollView>
+          </Text>,
+          11,
+        );
+      });
+    }).toThrow('Text strings must be rendered within a <Text> component.');
   });
 
   it('should not throw for text inside of an indirect <Text> ancestor', () => {
@@ -646,12 +705,14 @@ describe('ReactFabric', () => {
 
     const Indirection = () => 'Hi';
 
-    ReactFabric.render(
-      <Text>
-        <Indirection />
-      </Text>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <Text>
+          <Indirection />
+        </Text>,
+        11,
+      );
+    });
   });
 
   it('dispatches events to the last committed props', () => {
@@ -668,7 +729,9 @@ describe('ReactFabric', () => {
     const touchStart = jest.fn();
     const touchStart2 = jest.fn();
 
-    ReactFabric.render(<View onTouchStart={touchStart} />, 11);
+    act(() => {
+      ReactFabric.render(<View onTouchStart={touchStart} />, 11);
+    });
 
     expect(nativeFabricUIManager.createNode.mock.calls.length).toBe(1);
     expect(nativeFabricUIManager.registerEventHandler.mock.calls.length).toBe(
@@ -698,7 +761,9 @@ describe('ReactFabric', () => {
     expect(touchStart).toBeCalled();
     expect(touchStart2).not.toBeCalled();
 
-    ReactFabric.render(<View onTouchStart={touchStart2} />, 11);
+    act(() => {
+      ReactFabric.render(<View onTouchStart={touchStart2} />, 11);
+    });
 
     // Intentionally dispatch to the same instanceHandle again.
     dispatchEvent(instanceHandle, 'topTouchStart', touchEvent);
@@ -742,33 +807,35 @@ describe('ReactFabric', () => {
     const ref1 = React.createRef();
     const ref2 = React.createRef();
 
-    ReactFabric.render(
-      <View id="parent">
-        <View
-          ref={ref1}
-          id="one"
-          onResponderStart={event => {
-            expect(ref1.current).not.toBeNull();
-            // Check for referential equality
-            expect(ref1.current).toBe(event.target);
-            expect(ref1.current).toBe(event.currentTarget);
-          }}
-          onStartShouldSetResponder={() => true}
-        />
-        <View
-          ref={ref2}
-          id="two"
-          onResponderStart={event => {
-            expect(ref2.current).not.toBeNull();
-            // Check for referential equality
-            expect(ref2.current).toBe(event.target);
-            expect(ref2.current).toBe(event.currentTarget);
-          }}
-          onStartShouldSetResponder={() => true}
-        />
-      </View>,
-      1,
-    );
+    act(() => {
+      ReactFabric.render(
+        <View id="parent">
+          <View
+            ref={ref1}
+            id="one"
+            onResponderStart={event => {
+              expect(ref1.current).not.toBeNull();
+              // Check for referential equality
+              expect(ref1.current).toBe(event.target);
+              expect(ref1.current).toBe(event.currentTarget);
+            }}
+            onStartShouldSetResponder={() => true}
+          />
+          <View
+            ref={ref2}
+            id="two"
+            onResponderStart={event => {
+              expect(ref2.current).not.toBeNull();
+              // Check for referential equality
+              expect(ref2.current).toBe(event.target);
+              expect(ref2.current).toBe(event.currentTarget);
+            }}
+            onStartShouldSetResponder={() => true}
+          />
+        </View>,
+        1,
+      );
+    });
 
     const [
       dispatchEvent,
@@ -823,7 +890,12 @@ describe('ReactFabric', () => {
       }
     }
 
-    ReactFabric.render(<ContainsStrictModeChild ref={n => (parent = n)} />, 11);
+    act(() => {
+      ReactFabric.render(
+        <ContainsStrictModeChild ref={n => (parent = n)} />,
+        11,
+      );
+    });
 
     let match;
     expect(
@@ -855,12 +927,14 @@ describe('ReactFabric', () => {
       }
     }
 
-    ReactFabric.render(
-      <StrictMode>
-        <IsInStrictMode ref={n => (parent = n)} />
-      </StrictMode>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <StrictMode>
+          <IsInStrictMode ref={n => (parent = n)} />
+        </StrictMode>,
+        11,
+      );
+    });
 
     let match;
     expect(
@@ -896,7 +970,12 @@ describe('ReactFabric', () => {
       }
     }
 
-    ReactFabric.render(<ContainsStrictModeChild ref={n => (parent = n)} />, 11);
+    act(() => {
+      ReactFabric.render(
+        <ContainsStrictModeChild ref={n => (parent = n)} />,
+        11,
+      );
+    });
 
     let match;
     expect(() => (match = ReactFabric.findNodeHandle(parent))).toErrorDev([
@@ -926,12 +1005,14 @@ describe('ReactFabric', () => {
       }
     }
 
-    ReactFabric.render(
-      <StrictMode>
-        <IsInStrictMode ref={n => (parent = n)} />
-      </StrictMode>,
-      11,
-    );
+    act(() => {
+      ReactFabric.render(
+        <StrictMode>
+          <IsInStrictMode ref={n => (parent = n)} />
+        </StrictMode>,
+        11,
+      );
+    });
 
     let match;
     expect(() => (match = ReactFabric.findNodeHandle(parent))).toErrorDev([
@@ -953,7 +1034,9 @@ describe('ReactFabric', () => {
     }));
 
     const viewRef = React.createRef();
-    ReactFabric.render(<View ref={viewRef} />, 11);
+    act(() => {
+      ReactFabric.render(<View ref={viewRef} />, 11);
+    });
 
     expect(TextInputState.blurTextInput).not.toBeCalled();
 
@@ -970,7 +1053,9 @@ describe('ReactFabric', () => {
     }));
 
     const viewRef = React.createRef();
-    ReactFabric.render(<View ref={viewRef} />, 11);
+    act(() => {
+      ReactFabric.render(<View ref={viewRef} />, 11);
+    });
 
     expect(TextInputState.focusTextInput).not.toBeCalled();