From 2962f6f98cb36c2dff408244f7ce78308e58f4c1 Mon Sep 17 00:00:00 2001
From: fzaninotto <fzaninotto@gmail.com>
Date: Thu, 1 Apr 2021 22:05:09 +0200
Subject: [PATCH] Fix customRoutes aren't used when the resources are empty

Closes #6079
---
 examples/simple/src/index.tsx                 |  4 +-
 .../ra-core/src/core/CoreAdminRouter.spec.tsx | 52 +++++++++++++++++--
 packages/ra-core/src/core/CoreAdminRouter.tsx | 49 ++++++++++-------
 packages/ra-core/src/types.ts                 |  1 +
 4 files changed, 80 insertions(+), 26 deletions(-)

diff --git a/examples/simple/src/index.tsx b/examples/simple/src/index.tsx
index 6acc3c35cbd..d7d9ad5da49 100644
--- a/examples/simple/src/index.tsx
+++ b/examples/simple/src/index.tsx
@@ -1,6 +1,6 @@
 /* eslint react/jsx-key: off */
 import * as React from 'react';
-import { Admin, Resource } from 'react-admin'; // eslint-disable-line import/no-unresolved
+import { Admin, Resource, CustomRoute } from 'react-admin'; // eslint-disable-line import/no-unresolved
 import { render } from 'react-dom';
 import { Route } from 'react-router-dom';
 
@@ -23,7 +23,7 @@ render(
         title="Example Admin"
         layout={Layout}
         customRoutes={[
-            <Route
+            <Route<CustomRoute>
                 exact
                 path="/custom"
                 component={props => <CustomRouteNoLayout {...props} />}
diff --git a/packages/ra-core/src/core/CoreAdminRouter.spec.tsx b/packages/ra-core/src/core/CoreAdminRouter.spec.tsx
index b0f617343a6..0d237becec7 100644
--- a/packages/ra-core/src/core/CoreAdminRouter.spec.tsx
+++ b/packages/ra-core/src/core/CoreAdminRouter.spec.tsx
@@ -81,10 +81,10 @@ describe('<CoreAdminRouter>', () => {
                 getPermissions: jest.fn().mockResolvedValue(''),
             };
 
-            const { getByText } = renderWithRedux(
+            const { queryByText } = renderWithRedux(
                 <AuthContext.Provider value={authProvider}>
                     <Router history={history}>
-                        <CoreAdminRouter {...defaultProps} layout={Layout}>
+                        <CoreAdminRouter layout={Layout}>
                             {() => [
                                 <Resource
                                     key="posts"
@@ -104,12 +104,54 @@ describe('<CoreAdminRouter>', () => {
             );
             // Timeout needed because of the authProvider call
             await waitFor(() => {
-                expect(getByText('Layout')).not.toBeNull();
+                expect(queryByText('Layout')).not.toBeNull();
             });
             history.push('/posts');
-            expect(getByText('PostList')).not.toBeNull();
+            expect(queryByText('PostList')).not.toBeNull();
             history.push('/comments');
-            expect(getByText('CommentList')).not.toBeNull();
+            expect(queryByText('CommentList')).not.toBeNull();
+        });
+
+        it('should return loading while the resources are not resolved', async () => {
+            const history = createMemoryHistory();
+            const authProvider = {
+                login: jest.fn().mockResolvedValue(''),
+                logout: jest.fn().mockResolvedValue(''),
+                checkAuth: jest.fn().mockResolvedValue(''),
+                checkError: jest.fn().mockResolvedValue(''),
+                getPermissions: jest.fn().mockResolvedValue(''),
+            };
+            const Loading = () => <>Loading</>;
+            const Custom = () => <>Custom</>;
+
+            const { queryByText } = renderWithRedux(
+                <AuthContext.Provider value={authProvider}>
+                    <Router history={history}>
+                        <CoreAdminRouter
+                            {...defaultProps}
+                            layout={Layout}
+                            loading={Loading}
+                            customRoutes={[
+                                <Route
+                                    key="foo"
+                                    noLayout
+                                    path="/foo"
+                                    component={Custom}
+                                />,
+                            ]}
+                        >
+                            {() => new Promise(() => {})}
+                        </CoreAdminRouter>
+                    </Router>
+                </AuthContext.Provider>
+            );
+            // Timeout needed because of the authProvider call
+            await new Promise(resolve => setTimeout(resolve, 1010));
+            history.push('/posts');
+            expect(queryByText('Loading')).not.toBeNull();
+            history.push('/foo');
+            expect(queryByText('Loading')).toBeNull();
+            expect(queryByText('Custom')).not.toBeNull();
         });
     });
 
diff --git a/packages/ra-core/src/core/CoreAdminRouter.tsx b/packages/ra-core/src/core/CoreAdminRouter.tsx
index 25f8302386e..11396c5b561 100644
--- a/packages/ra-core/src/core/CoreAdminRouter.tsx
+++ b/packages/ra-core/src/core/CoreAdminRouter.tsx
@@ -107,33 +107,43 @@ const CoreAdminRouter: FunctionComponent<AdminRouterProps> = props => {
         loading: LoadingPage,
         logout,
         menu,
-        ready,
+        ready: Ready,
         theme,
         title,
     } = props;
 
-    if (
-        (typeof children !== 'function' && !children) ||
-        (Array.isArray(children) && children.length === 0)
-    ) {
-        return createElement(ready);
+    if (typeof children !== 'function' && !children) {
+        return <Ready />;
     }
 
     if (
-        typeof children === 'function' &&
-        (!computedChildren || computedChildren.length === 0)
+        (typeof children === 'function' &&
+            (!computedChildren || computedChildren.length === 0)) ||
+        (Array.isArray(children) && children.length === 0)
     ) {
-        if (oneSecondHasPassed) {
-            return (
-                <Route
-                    path="/"
-                    key="loading"
-                    render={() => <LoadingPage theme={theme} />}
-                />
-            );
-        } else {
-            return null;
-        }
+        return (
+            <Switch>
+                {customRoutes
+                    .filter(route => route.props.noLayout)
+                    .map((route, key) =>
+                        cloneElement(route, {
+                            key,
+                            render: routeProps =>
+                                renderCustomRoutesWithoutLayout(
+                                    route,
+                                    routeProps
+                                ),
+                            component: undefined,
+                        })
+                    )}
+                {oneSecondHasPassed && (
+                    <Route
+                        key="loading"
+                        render={() => <LoadingPage theme={theme} />}
+                    />
+                )}
+            </Switch>
+        );
     }
 
     const childrenToRender = (typeof children === 'function'
@@ -167,6 +177,7 @@ const CoreAdminRouter: FunctionComponent<AdminRouterProps> = props => {
                                     route,
                                     routeProps
                                 ),
+                            component: undefined,
                         })
                     )}
                 <Route
diff --git a/packages/ra-core/src/types.ts b/packages/ra-core/src/types.ts
index 44f174d9b59..5fd27bff7cc 100644
--- a/packages/ra-core/src/types.ts
+++ b/packages/ra-core/src/types.ts
@@ -389,6 +389,7 @@ export type AdminChildren = RenderResourcesFunction | ReactNode;
 
 export interface CustomRoute extends RouteProps {
     noLayout?: boolean;
+    [key: string]: any;
 }
 
 export type CustomRoutes = Array<ReactElement<CustomRoute>>;