在react-router-dom v6和@types/react-router-dom v5.3.3中找不到“hello”路径

问题描述 投票:0回答:1

主文件:

    import React, { useContext, useEffect } from 'react';
import { Route, RouteProps, Routes, useLocation, useMatch } from 'react-router-dom';

import AuthenticationContext from './AuthenticationContext';

export type PrivateRouteProps = RouteProps & {
  Element?: React.ComponentType<any>;
};

/* eslint-disable react/jsx-props-no-spreading */
function PrivateRoute({ children, path, Element, ...routePropsWithoutChildrenAndComponent }: any) {
  const { authorize, authenticated, authenticating, callbackPath } =
    useContext(AuthenticationContext);
  const location = useLocation();

  const match = useMatch(path?.toString() || '*');

  useEffect(() => {
    if (!authenticated && !authenticating && match && location.pathname !== callbackPath) {
      const authorizeAsync = async () => {
        authorize(location as unknown as URL);
      };
      authorizeAsync();
    }
  }, [authorize, match, location, authenticated, authenticating, callbackPath]);

  if (!authenticated) {
    return null;
  }

  return (
    <Routes>
      <Route
        {...routePropsWithoutChildrenAndComponent}
        element={Element ? <Element /> : children}
      />
    </Routes>
  );
}

export default PrivateRoute;

测试文件:

/* eslint-disable react/prop-types */
import React from 'react';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { unstable_HistoryRouter as Router } from 'react-router-dom';

import { PrivateRoute } from '..';
import AuthenticationContext, { AuthenticationContextProps } from '../AuthenticationContext';

describe('PrivateRoute', () => {
  function renderWithRouterAndContext(
    content: JSX.Element,
    // eslint-disable-next-line @typescript-eslint/ban-types
    { location, context } = {} as { location: string; context: object },
  ) {
    const history = createMemoryHistory({ initialEntries: [location] });
    const defaultContext: AuthenticationContextProps = {
      fetchToken: () => 'xyz' as any,
      callbackPath: '/oauth',
      setError: () => {},
      authenticated: false,
      authenticating: false,
      authorize: () => {},
      logout: () => {},
      getToken: () => 'xyz',
    };
    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <AuthenticationContext.Provider value={{ ...defaultContext, ...context }}>
        <Router history={history as any}>{children}</Router>
      </AuthenticationContext.Provider>
    );
    return {
      ...render(content, { wrapper } as any),
      history,
    };
  }

  describe('when authenticated', () => {
    it('can render children', () => {
      const { container } = renderWithRouterAndContext(
        <PrivateRoute path="/hello">
          <h1>Hello</h1>
        </PrivateRoute>,
        { location: '/hello', context: { callbackPath: '/oauth', authenticated: true } },
      );
      expect(container.innerHTML).toBe('<h1>Hello</h1>');
    });

    it('can render a component', () => {
      function MyComponent() {
        return <h1>Hey</h1>;
      }
      // eslint-disable-next-line react/jsx-no-bind
      const { container } = renderWithRouterAndContext(<PrivateRoute component={MyComponent} />, {
        location: '/hello',
        context: { callbackPath: '/oauth', authenticated: true },
      });
      expect(container.innerHTML).toBe('<h1>Hey</h1>');
    });

    it('can invoke a render prop function', () => {
      const { container } = renderWithRouterAndContext(
        <PrivateRoute
          render={({ history, location }: any) => <p>{${history.length} ${location.pathname}}</p>}
        />,
        { location: '/hello', context: { callbackPath: '/oauth', authenticated: true } },
      );
      expect(container.innerHTML).toBe('<p>1 /hello</p>');
    });
  });

  describe('when unauthenticated', () => {
    const authorize = jest.fn();

    beforeEach(() => {
      jest.clearAllMocks();
    });

    describe('for a matching path', () => {
      it('checks user authorization and does not render anything', () => {
        const { container } = renderWithRouterAndContext(
          <PrivateRoute path="/hello">
            <h1>Hello</h1>
          </PrivateRoute>,
          {
            location: '/hello',
            context: { callbackPath: '/oauth', authenticated: false, authorize },
          },
        );
        expect(container.innerHTML).toBe('');
        expect(authorize).toHaveBeenCalledWith(expect.objectContaining({ pathname: '/hello' }));
      });
    });

    describe('for an OAuth callback path', () => {
      it('does not check user authorization and does not render anything', () => {
        const { container } = renderWithRouterAndContext(
          <PrivateRoute path="/">
            <h1>Hello</h1>
          </PrivateRoute>,
          {
            location: '/oauth?code=xyz&state=foo',
            context: { callbackPath: '/oauth', authenticated: false, authorize },
          },
        );
        expect(container.innerHTML).toBe('');
        expect(authorize).not.toHaveBeenCalled();
      });
    });

    describe('for a non-matching path', () => {
      it('does not check user authorization', () => {
        renderWithRouterAndContext(
          <PrivateRoute path="/hello">
            <h1>Hello</h1>
          </PrivateRoute>,
          { location: '/hi', context: { callbackPath: '/oauth', authenticated: false, authorize } },
        );
        expect(authorize).not.toHaveBeenCalled();
      });
    });

    describe('when authentication is in progress', () => {
      it('does not check user authorization', () => {
        renderWithRouterAndContext(
          <PrivateRoute path="/hello">
            <h1>Hello</h1>
          </PrivateRoute>,
          {
            location: '/hi',
            context: {
              callbackPath: '/hello',
              authenticating: true,
              authorize,
            },
          },
        );
        expect(authorize).not.toHaveBeenCalled();
      });
    });
  });
});

使用新版本的react-router-dom v6和@types/react-router-dom v5.3.3,在解决了代码更改的一些错误后,它给出了“hello”路径未找到错误。

错误:

控制台.警告 没有路线匹配位置“/hello”

  31 |     );
  32 |     return {
> 33 |       ...render(content, { wrapper } as any),
     |                ^
  34 |       history,
  35 |     };
  36 |   }

● PrivateRoute › 经过身份验证后 › 可以渲染子路由

expect(received).toBe(expected) // Object.is equality

Expected: "<h1>Hello</h1>"
Received: ""

  44 |         { location: '/hello', context: { callbackPath: '/oauth', authenticated: true } },
  45 |       );
> 46 |       expect(container.innerHTML).toBe('<h1>Hello</h1>');
     |                                   ^
  47 |     });
  48 |
  49 |     it('can render a component', () => {

  at Object.<anonymous> (packages/auth/src/_tests_/PrivateRoute.test.tsx:46:35)

● PrivateRoute › 经过身份验证后 › 可以渲染组件

expect(received).toBe(expected) // Object.is equality

Expected: "<h1>Hey</h1>"
Received: ""

  56 |         context: { callbackPath: '/oauth', authenticated: true },
  57 |       });
> 58 |       expect(container.innerHTML).toBe('<h1>Hey</h1>');
     |                                   ^
  59 |     });
  60 |
  61 |     it('can invoke a render prop function', () => {

  at Object.<anonymous> (packages/auth/src/_tests_/PrivateRoute.test.tsx:58:35)

● PrivateRoute › 经过身份验证后 › 可以调用 render prop 函数

expect(received).toBe(expected) // Object.is equality

Expected: "<p>1 /hello</p>"
Received: ""

  66 |         { location: '/hello', context: { callbackPath: '/oauth', authenticated: true } },
  67 |       );
> 68 |       expect(container.innerHTML).toBe('<p>1 /hello</p>');
     |                                   ^
  69 |     });
  70 |   });
  71 |
        

请给一些建议,先谢谢了

javascript reactjs react-hooks react-redux react-router
1个回答
0
投票

没有使用

Route
"/hello"
属性渲染
path
PrivateRoute
组件不会将
path
属性传递给它正在渲染的
Route
。不过,您的
PrivateRoute
组件过于复杂了。它应该简单地为嵌套路由呈现
Outlet
来呈现其
element
内容,或者在未经身份验证的用户的情况下,通常将其重定向到安全的不受保护的路由。

为未经身份验证的用户保留渲染逻辑

null

const PrivateRoute = () => {
  const {
    authorize,
    authenticated,
    authenticating,
    callbackPath
  } = useContext(AuthenticationContext);
  const location = useLocation();

  useEffect(() => {
    if (!authenticated
      && !authenticating
      && location.pathname !== callbackPath
    ) {
      const authorizeAsync = async () => {
        authorize(location as unknown as URL);
      };
      authorizeAsync();
    }
  }, [authorize, location, authenticated, authenticating, callbackPath]);

  return authenticated ? <Outlet /> : null;
}

应重构单元测试以将

PrivateRoute
渲染为布局路由组件。

import React from 'react';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { unstable_HistoryRouter as Router } from 'react-router-dom';
import { PrivateRoute } from '..';
import AuthenticationContext, {
  AuthenticationContextProps
} from '../AuthenticationContext';

describe('PrivateRoute', () => {
  function renderWithRouterAndContext(
    content: JSX.Element,
    // eslint-disable-next-line @typescript-eslint/ban-types
    { location, context } = {} as { location: string; context: object },
  ) {
    const history = createMemoryHistory({ initialEntries: [location] });

    const defaultContext: AuthenticationContextProps = {
      fetchToken: () => 'xyz' as any,
      callbackPath: '/oauth',
      setError: () => {},
      authenticated: false,
      authenticating: false,
      authorize: () => {},
      logout: () => {},
      getToken: () => 'xyz',
    };

    const wrapper = ({ children }: { children: React.ReactNode }) => (
      <AuthenticationContext.Provider value={{ ...defaultContext, ...context }}>
        <Router history={history as any}>{children}</Router>
      </AuthenticationContext.Provider>
    );

    return {
      ...render(content, { wrapper } as any),
      history,
    };
  }

  describe('when authenticated', () => {
    it('can render children', () => {
      const { container } = renderWithRouterAndContext(
        <Routes>
          <Route element={<PrivateRoute />}>
            <Route path="/hello" element={<h1>Hello</h1>} />
          </Route>
        </Routes>,
        {
          location: '/hello',
          context: { callbackPath: '/oauth', authenticated: true }
        },
      );
      expect(container.innerHTML).toBe('<h1>Hello</h1>');
    });

    it('can render a component', () => {
      function MyComponent() {
        return <h1>Hey</h1>;
      }

      // eslint-disable-next-line react/jsx-no-bind
      const { container } = renderWithRouterAndContext(
        <Routes>
          <Route element={<PrivateRoute />}>
            <Route path="/hello" element={<MyComponent />} />
          </Route>
        </Routes>,
        {
          location: '/hello',
          context: { callbackPath: '/oauth', authenticated: true },
        }
      );
      expect(container.innerHTML).toBe('<h1>Hey</h1>');
    });
  });

  describe('when unauthenticated', () => {
    const authorize = jest.fn();

    beforeEach(() => {
      jest.clearAllMocks();
    });

    describe('for a matching path', () => {
      it('checks user authorization and does not render anything', () => {
        const { container } = renderWithRouterAndContext(
          <Routes>
            <Route element={<PrivateRoute />}>
              <Route path="/hello" element={<h1>Hello</h1>} />
            </Route>
          </Routes>,
          {
            location: '/hello',
            context: { callbackPath: '/oauth', authenticated: false, authorize },
          },
        );
        expect(container.innerHTML).toBe('');
        expect(authorize).toHaveBeenCalledWith(
          expect.objectContaining({ pathname: '/hello' })
        );
      });
    });

    describe('for an OAuth callback path', () => {
      it('does not check user authorization and does not render anything', () => {
        const { container } = renderWithRouterAndContext(
          <Routes>
            <Route element={<PrivateRoute />}>
              <Route path="/hello" element={<h1>Hello</h1>} />
            </Route>
          </Routes>,
          {
            location: '/oauth?code=xyz&state=foo',
            context: { callbackPath: '/oauth', authenticated: false, authorize },
          },
        );
        expect(container.innerHTML).toBe('');
        expect(authorize).not.toHaveBeenCalled();
      });
    });

    describe('for a non-matching path', () => {
      it('does not check user authorization', () => {
        renderWithRouterAndContext(
          <Routes>
            <Route path="/hi" element={<h1>Hi</h1>} />
            <Route element={<PrivateRoute />}>
              <Route path="/hello" element={<h1>Hello</h1>} />
            </Route>
          </Routes>,
          {
            location: '/hi',
            context: { callbackPath: '/oauth', authenticated: false, authorize }
          },
        );
        expect(authorize).not.toHaveBeenCalled();
      });
    });

    describe('when authentication is in progress', () => {
      it('does not check user authorization', () => {
        renderWithRouterAndContext(
          <Routes>
            <Route path="/hi" element={<h1>Hi</h1>} />
            <Route element={<PrivateRoute />}>
              <Route path="/hello" element={<h1>Hello</h1>} />
            </Route>
          </Routes>,
          {
            location: '/hi',
            context: {
              callbackPath: '/hello',
              authenticating: true,
              authorize,
            },
          },
        );
        expect(authorize).not.toHaveBeenCalled();
      });
    });
  });
});
© www.soinside.com 2019 - 2024. All rights reserved.