Repository: facebook/react
Pull request: [bug] Fix component name for Portal and add tests

Based off: https://github.com/facebook/react/pull/32499

While looking into `React.lazy` issues for built-ins, I noticed we already error for `lazy` with build-ins, but we don't have any tests for `getComponentNameFromType` using all the built-ins. This may be something we should handle, but for now we should at least have tests. 

Here's why: while writing tests, I noticed we check `type` instead of `$$typeof` for portals:

https://github.com/facebook/react/blob/9cdf8a99edcfd94d7420835ea663edca04237527/packages/react-reconciler/src/ReactPortal.js#L25-L32

This PR adds tests for all the built-ins and fixes the portal bug.

[Commit to review](https://github.com/facebook/react/pull/32640/commits/e068c167d48d4df01e79db8f13276bb46d7ab439)

Changed files:
filename: packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
patch: @@ -791,7 +791,7 @@ describe('ReactLazy', () => {
     );
   });
 
-  it('throws with a useful error when wrapping fragment with lazy()', async () => {
+  it('throws with a useful error when wrapping Fragment with lazy()', async () => {
     const BadLazy = lazy(() => fakeImport(React.Fragment));
 
     const root = ReactTestRenderer.create(
@@ -817,6 +817,280 @@ describe('ReactLazy', () => {
     );
   });
 
+  // @gate !fb
+  it('throws with a useful error when wrapping createPortal with lazy()', async () => {
+    const ReactDOM = require('react-dom');
+    const container = document.createElement('div');
+    const portal = ReactDOM.createPortal(<div />, container);
+    const BadLazy = lazy(() => fakeImport(portal));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(portal);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: Portal. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  it('throws with a useful error when wrapping Profiler with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.Profiler));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.Profiler);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: Profiler. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  it('throws with a useful error when wrapping StrictMode with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.StrictMode));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.StrictMode);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: StrictMode. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  it('throws with a useful error when wrapping Suspense with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.Suspense));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.Suspense);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: Suspense. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  it('throws with a useful error when wrapping Context with lazy()', async () => {
+    const Context = React.createContext(null);
+    const BadLazy = lazy(() => fakeImport(Context));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(Context);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      gate('enableRenderableContext')
+        ? 'Element type is invalid. Received a promise that resolves to: Context.Provider. ' +
+            'Lazy element type must resolve to a class or function.'
+        : 'Element type is invalid. Received a promise that resolves to: Context.Consumer. ' +
+            'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  // @gate enableRenderableContext
+  it('throws with a useful error when wrapping Context.Consumer with lazy()', async () => {
+    const Context = React.createContext(null);
+    const BadLazy = lazy(() => fakeImport(Context.Consumer));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(Context.Consumer);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: Context.Consumer. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  // @gate enableSuspenseList
+  it('throws with a useful error when wrapping SuspenseList with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.unstable_SuspenseList));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.unstable_SuspenseList);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: SuspenseList. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  // @gate enableViewTransition
+  it('throws with a useful error when wrapping ViewTransition with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.unstable_ViewTransition));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.unstable_ViewTransition);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: ViewTransition. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  // @gate enableActivity
+  it('throws with a useful error when wrapping Activity with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.unstable_Activity));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.unstable_Activity);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: Activity. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
+  // @gate enableTransitionTracing
+  it('throws with a useful error when wrapping TracingMarker with lazy()', async () => {
+    const BadLazy = lazy(() => fakeImport(React.unstable_TracingMarker));
+
+    const root = ReactTestRenderer.create(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+      {
+        unstable_isConcurrent: true,
+      },
+    );
+
+    await waitForAll(['Loading...']);
+
+    await resolveFakeImport(React.unstable_TracingMarker);
+    root.update(
+      <Suspense fallback={<Text text="Loading..." />}>
+        <BadLazy />
+      </Suspense>,
+    );
+    await waitForThrow(
+      'Element type is invalid. Received a promise that resolves to: TracingMarker. ' +
+        'Lazy element type must resolve to a class or function.',
+    );
+  });
+
   it('throws with a useful error when wrapping lazy() multiple times', async () => {
     const Lazy1 = lazy(() => fakeImport(Text));
     const Lazy2 = lazy(() => fakeImport(Lazy1));

filename: packages/shared/getComponentNameFromType.js
patch: @@ -74,8 +74,6 @@ export default function getComponentNameFromType(type: mixed): string | null {
   switch (type) {
     case REACT_FRAGMENT_TYPE:
       return 'Fragment';
-    case REACT_PORTAL_TYPE:
-      return 'Portal';
     case REACT_PROFILER_TYPE:
       return 'Profiler';
     case REACT_STRICT_MODE_TYPE:
@@ -106,6 +104,8 @@ export default function getComponentNameFromType(type: mixed): string | null {
       }
     }
     switch (type.$$typeof) {
+      case REACT_PORTAL_TYPE:
+        return 'Portal';
       case REACT_PROVIDER_TYPE:
         if (enableRenderableContext) {
           return null;