使用react-router-dom v6在测试文件中出现错误:“类型‘IntrinsicAttributes & RouterProps’上不存在属性‘history’。”

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

主文件:

import { useContext, useEffect } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';

import AuthenticationContext from './AuthenticationContext';

function OAuthCallbackRoute() {
  const { fetchToken, callbackPath, setError } = useContext(AuthenticationContext);
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    if (location.pathname !== callbackPath) return;

    async function completeLogin(code: string, state: string) {
      if (await fetchToken(code)) navigate(decodeURIComponent(state));
    }

    const successRedirectPathRegex = /\?code=(\S+)&state=(.+)/;
    const errorRedirectPathRegex = /\?error=(\S+)&error_description=(.+)&state=(.+)/;

    const successPathMatch = location.search.match(successRedirectPathRegex);
    if (successPathMatch) {
      const [, code, state] = successPathMatch;
      completeLogin(code, decodeURIComponent(state));
    }

    const errorRedirectPathMatch = location.search.match(errorRedirectPathRegex);
    if (errorRedirectPathMatch) {
      const [, error, errorDescription] = errorRedirectPathMatch;
      setError(${error} - ${decodeURIComponent(errorDescription)});
    }
  }, [fetchToken, navigate, location, callbackPath, setError]);

  return null;
}

export default OAuthCallbackRoute;

测试文件:

import React from 'react';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';

import { OAuthCallbackRoute } from '..';
import AuthenticationContext from '../AuthenticationContext';

const delay = async (ms: number) =>
  new Promise((resolve) => {
    setTimeout(resolve, ms);
  });

describe('OAuthCallbackRoute', () => {
  let history: any;
  let historyReplace: any;
  let fetchToken: any;
  let setError: any;
  const originalError = global.console.error;

  beforeEach(() => {
    history = createMemoryHistory();
    global.console.error = jest.fn();
    jest.clearAllMocks();
  });

  afterEach(() => {
    global.console.error = originalError;
  });

  function App() {
    return (
      <AuthenticationContext.Provider
        // eslint-disable-next-line react/jsx-no-constructed-context-values
        value={{
          fetchToken,
          callbackPath: '/oauth',
          setError,
          authenticated: true,
          authenticating: false,
          authorize: () => {},
          logout: () => {},
          getToken: () => 'xyz',
        }}
      >
        <Router history={history}>
          <OAuthCallbackRoute />
        </Router>
      </AuthenticationContext.Provider>
    );
  }

  describe('for an OAuth callback', () => {
    describe('on successful OAuth callback', () => {
      beforeEach(() => {
        history.replace('/oauth?code=xyz&state=%2Ffoo');
        historyReplace = jest.spyOn(history, 'replace');
      });

      describe('on successful token fetching', () => {
        beforeEach(() => {
          fetchToken = jest.fn(() => true);
        });

        it('uses the received code to fetch an auth token', () => {
          render(<App />);
          expect(fetchToken).toHaveBeenCalledWith('xyz');
        });

        it('redirects to the referrer path', async () => {
          render(<App />);
          await delay(100);
          expect(historyReplace).toHaveBeenCalledWith('/foo');
        });
      });

      describe('on failed token fetching', () => {
        beforeEach(() => {
          fetchToken = jest.fn(() => false);
        });

        it('does not redirect to the referrer path', async () => {
          render(<App />);
          await delay(100);
          expect(historyReplace).not.toHaveBeenCalled();
        });
      });
    });

    describe('on error included in callback', () => {
      beforeEach(() => {
        setError = jest.fn(() => true);
        history.replace(
          '/oauth?error=access_denied&error_description=Service%20not%20found&state=%2Ffoo',
        );
        historyReplace = jest.spyOn(history, 'replace');
      });

      it('does not redirect to the referrer path', async () => {
        render(<App />);
        await delay(100);
        expect(historyReplace).not.toHaveBeenCalled();
      });

      it('puts the error on the context', async () => {
        render(<App />);
        expect(setError).toHaveBeenCalledWith('access_denied - Service not found');
      });
    });
  });

  describe('for a non-OAuth path', () => {
    beforeEach(() => {
      history.replace('/random-path');
      historyReplace = jest.spyOn(history, 'replace');
    });

    it('does not attempt to fetch a token', async () => {
      render(<App />);
      await delay(100);
      expect(fetchToken).not.toHaveBeenCalled();
    });

    it('does not redirect', async () => {
      render(<App />);
      await delay(100);
      expect(historyReplace).not.toHaveBeenCalled();
    });
  });
});

错误:

packages/auth/src/tests/OAuthCallbackRoute.test.tsx:46:17 - 错误 TS2322:键入 '{children:Element;历史:任何; }' 不可分配给类型“IntrinsicAttributes & RouterProps”。 类型“IntrinsicAttributes & RouterProps”上不存在属性“history”。

46         <Router history={history}>

我尝试使用导航器更改历史记录,但出现了不同的错误。

欣赏一些关于“历史”错误的解决方案。

reactjs typescript react-router react-router-dom
1个回答
0
投票

低级

Router
组件需要一些道具,其中两个是必需的,但都不是
history

路由器

declare function Router(
  props: RouterProps
): React.ReactElement | null;

interface RouterProps {
  basename?: string;
  children?: React.ReactNode;
  location: Partial<Location> | string; // <-- Required
  navigationType?: NavigationType;
  navigator: Navigator;                 // <-- Required
  static?: boolean;
}

也就是说,几乎没有理由使用

Router
组件。通常,您将在单元测试中导入并使用
MemoryRouter
。在这种情况下,您似乎有需要引用自定义历史对象的测试。为此,您可以导入并使用
HistoryRouter
,其中 确实 需要
history
道具。

export interface HistoryRouterProps {
  basename?: string;
  children?: React.ReactNode;
  future?: FutureConfig;
  history: History; // <-- custom history object
}

示例:

import React from 'react';
import { render } from '@testing-library/react';
import { createMemoryHistory } from 'history';

import { unstable_HistoryRouter as Router } from 'react-router-dom';

import { OAuthCallbackRoute } from '..';
import AuthenticationContext from '../AuthenticationContext';

const delay = async (ms: number) =>
  new Promise((resolve) => {
    setTimeout(resolve, ms);
  });

describe('OAuthCallbackRoute', () => {
  let history: any;
  let historyReplace: any;
  let fetchToken: any;
  let setError: any;
  const originalError = global.console.error;

  beforeEach(() => {
    history = createMemoryHistory();
    global.console.error = jest.fn();
    jest.clearAllMocks();
  });

  afterEach(() => {
    global.console.error = originalError;
  });

  function App() {
    return (
      <AuthenticationContext.Provider
        // eslint-disable-next-line react/jsx-no-constructed-context-values
        value={{
          fetchToken,
          callbackPath: '/oauth',
          setError,
          authenticated: true,
          authenticating: false,
          authorize: () => {},
          logout: () => {},
          getToken: () => 'xyz',
        }}
      >
        <Router history={history}>
          <OAuthCallbackRoute />
        </Router>
      </AuthenticationContext.Provider>
    );
  }

  describe('for an OAuth callback', () => {
    describe('on successful OAuth callback', () => {
      beforeEach(() => {
        history.replace('/oauth?code=xyz&state=%2Ffoo');
        historyReplace = jest.spyOn(history, 'replace');
      });

      describe('on successful token fetching', () => {
        beforeEach(() => {
          fetchToken = jest.fn(() => true);
        });

        it('uses the received code to fetch an auth token', () => {
          render(<App />);
          expect(fetchToken).toHaveBeenCalledWith('xyz');
        });

        it('redirects to the referrer path', async () => {
          render(<App />);
          await delay(100);
          expect(historyReplace).toHaveBeenCalledWith('/foo');
        });
      });

      describe('on failed token fetching', () => {
        beforeEach(() => {
          fetchToken = jest.fn(() => false);
        });

        it('does not redirect to the referrer path', async () => {
          render(<App />);
          await delay(100);
          expect(historyReplace).not.toHaveBeenCalled();
        });
      });
    });

    describe('on error included in callback', () => {
      beforeEach(() => {
        setError = jest.fn(() => true);
        history.replace(
          '/oauth?error=access_denied&error_description=Service%20not%20found&state=%2Ffoo',
        );
        historyReplace = jest.spyOn(history, 'replace');
      });

      it('does not redirect to the referrer path', async () => {
        render(<App />);
        await delay(100);
        expect(historyReplace).not.toHaveBeenCalled();
      });

      it('puts the error on the context', async () => {
        render(<App />);
        expect(setError).toHaveBeenCalledWith('access_denied - Service not found');
      });
    });
  });

  describe('for a non-OAuth path', () => {
    beforeEach(() => {
      history.replace('/random-path');
      historyReplace = jest.spyOn(history, 'replace');
    });

    it('does not attempt to fetch a token', async () => {
      render(<App />);
      await delay(100);
      expect(fetchToken).not.toHaveBeenCalled();
    });

    it('does not redirect', async () => {
      render(<App />);
      await delay(100);
      expect(historyReplace).not.toHaveBeenCalled();
    });
  });
});
© www.soinside.com 2019 - 2024. All rights reserved.