Repository: facebook/react
Pull request: Run persistent tests in more configurations in CI

I noticed that `enableSuspenseLayoutEffectSemantics` is not fully implemented in persistent mode. I believe this was an oversight because we don't have a CI job that runs tests in persistent mode and with experimental flags enabled.

This adds additional test configurations to the CI job so we don't miss stuff like this again. It doesn't fix the failing tests — I'll address that separately.

Changed files:
filename: .circleci/config.yml
patch: @@ -456,6 +456,7 @@ workflows:
 
                 # TODO: Test more persistent configurations?
                 - '-r=stable --env=development --persistent'
+                - '-r=experimental --env=development --persistent'
       - yarn_build_combined:
           requires:
             - setup

filename: packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
patch: @@ -220,6 +220,7 @@ describe('ReactOffscreen', () => {
 
   // @gate experimental || www
   // @gate enableSuspenseLayoutEffectSemantics
+  // @gate !persistent
   it('mounts/unmounts layout effects when visibility changes (starting visible)', async () => {
     function Child({text}) {
       useLayoutEffect(() => {
@@ -270,6 +271,7 @@ describe('ReactOffscreen', () => {
 
   // @gate experimental || www
   // @gate enableSuspenseLayoutEffectSemantics
+  // @gate !persistent
   it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => {
     function Child({text}) {
       useLayoutEffect(() => {

filename: packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js
patch: @@ -17,6 +17,8 @@ let caches;
 let seededCache;
 let ErrorBoundary;
 
+// TODO: These tests don't pass in persistent mode yet. Need to implement.
+
 describe('ReactSuspenseEffectsSemantics', () => {
   beforeEach(() => {
     jest.resetModules();
@@ -565,6 +567,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be destroyed and recreated for function components', async () => {
       function App({children = null}) {
         Scheduler.unstable_yieldValue('App render');
@@ -694,6 +697,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be destroyed and recreated for class components', async () => {
       class ClassText extends React.Component {
         componentDidMount() {
@@ -839,6 +843,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be destroyed and recreated when nested below host components', async () => {
       function App({children = null}) {
         Scheduler.unstable_yieldValue('App render');
@@ -949,6 +954,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be destroyed and recreated even if there is a bailout because of memoization', async () => {
       const MemoizedText = React.memo(Text, () => true);
 
@@ -1065,6 +1071,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should respect nested suspense boundaries', async () => {
       function App({innerChildren = null, outerChildren = null}) {
         return (
@@ -1288,6 +1295,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should show nested host nodes if multiple boundaries resolve at the same time', async () => {
       function App({innerChildren = null, outerChildren = null}) {
         return (
@@ -1398,6 +1406,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleaned up inside of a fallback that suspends', async () => {
       function App({fallbackChildren = null, outerChildren = null}) {
         return (
@@ -1541,6 +1550,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleaned up inside of a fallback that suspends (alternate)', async () => {
       function App({fallbackChildren = null, outerChildren = null}) {
         return (
@@ -1661,6 +1671,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleaned up deeper inside of a subtree that suspends', async () => {
       function ConditionalSuspense({shouldSuspend}) {
         if (shouldSuspend) {
@@ -1744,6 +1755,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
     describe('that throw errors', () => {
       // @gate enableSuspenseLayoutEffectSemantics
       // @gate enableCache
+      // @gate !persistent
       it('are properly handled for componentDidMount', async () => {
         let componentDidMountShouldThrow = false;
 
@@ -1883,6 +1895,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
       // @gate enableSuspenseLayoutEffectSemantics
       // @gate enableCache
+      // @gate !persistent
       it('are properly handled for componentWillUnmount', async () => {
         class ThrowsInWillUnmount extends React.Component {
           componentDidMount() {
@@ -1996,6 +2009,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
       // @gate enableSuspenseLayoutEffectSemantics
       // @gate enableCache
+      // @gate !persistent
       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
       it('are properly handled for layout effect creation', async () => {
         let useLayoutEffectShouldThrow = false;
@@ -2136,6 +2150,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
       // @gate enableSuspenseLayoutEffectSemantics
       // @gate enableCache
+      // @gate !persistent
       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
       it('are properly handled for layout effect descruction', async () => {
         function ThrowsInLayoutEffectDestroy() {
@@ -2248,6 +2263,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be only destroy layout effects once if a tree suspends in multiple places', async () => {
       class ClassText extends React.Component {
         componentDidMount() {
@@ -2387,6 +2403,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be only destroy layout effects once if a component suspends multiple times', async () => {
       class ClassText extends React.Component {
         componentDidMount() {
@@ -2671,6 +2688,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleared and reset for host components', async () => {
       function App({children}) {
         Scheduler.unstable_yieldValue(`App render`);
@@ -2768,6 +2786,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleared and reset for class components', async () => {
       class ClassComponent extends React.Component {
         render() {
@@ -2869,6 +2888,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should be cleared and reset for function components with useImperativeHandle', async () => {
       const FunctionComponent = React.forwardRef((props, ref) => {
         Scheduler.unstable_yieldValue('FunctionComponent render');
@@ -2974,6 +2994,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
 
     // @gate enableSuspenseLayoutEffectSemantics
     // @gate enableCache
+    // @gate !persistent
     it('should not reset for user-managed values', async () => {
       function RefChecker({forwardedRef}) {
         Scheduler.unstable_yieldValue(`RefChecker render`);
@@ -3072,6 +3093,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
     describe('that throw errors', () => {
       // @gate enableSuspenseLayoutEffectSemantics
       // @gate enableCache
+      // @gate !persistent
       // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback
       it('are properly handled in ref callbacks', async () => {
         let useRefCallbackShouldThrow = false;

filename: scripts/jest/TestFlags.js
patch: @@ -42,6 +42,8 @@ const environmentFlags = {
   // Similarly, should stable imply "classic"?
   stable: !__EXPERIMENTAL__,
 
+  persistent: global.__PERSISTENT__ === true,
+
   // Use this for tests that are known to be broken.
   FIXME: false,