diff --git a/package.json b/package.json
index dc2cbe71dc..7f0ea1eebe 100644
--- a/package.json
+++ b/package.json
@@ -87,7 +87,7 @@
"react-papaparse": "^4.0.2",
"react-qr-reader": "^3.0.0-beta-1",
"react-redux": "^8.1.1",
- "react-router-dom": "^6.14.1",
+ "react-router": "^7.6.2",
"react-select": "^5.7.3",
"react-simple-keyboard": "^3.6.27",
"react-sortable-hoc": "^2.0.0",
diff --git a/src/commons/ReactRouterPrompt.tsx b/src/commons/ReactRouterPrompt.tsx
index 8a00e1a276..75e848fd8c 100644
--- a/src/commons/ReactRouterPrompt.tsx
+++ b/src/commons/ReactRouterPrompt.tsx
@@ -1,14 +1,14 @@
/**
* Intermediate implementation of deprecated component
- * from react-router-dom's v5 to v6 upgrade.
+ * from react-router's v5 to v6 upgrade.
*
- * react-router-dom plans to bring back in the future. Until then,
+ * react-router plans to bring back in the future. Until then,
* we can use this suggested implementation.
*
* See: https://github.com/remix-run/react-router/issues/8139#issuecomment-1382428200
*/
import React from 'react';
-import { useBeforeUnload, useBlocker } from 'react-router-dom';
+import { useBeforeUnload, useBlocker } from 'react-router';
// You can abstract `useBlocker` to use the browser's `window.confirm` dialog to
// determine whether or not the user should navigate within the current origin.
diff --git a/src/commons/application/Application.tsx b/src/commons/application/Application.tsx
index 31f2b666de..61af7a3eeb 100644
--- a/src/commons/application/Application.tsx
+++ b/src/commons/application/Application.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { useDispatch } from 'react-redux';
-import { Outlet } from 'react-router-dom';
+import { Outlet } from 'react-router';
import Messages, {
MessageType,
MessageTypeNames,
diff --git a/src/commons/application/ApplicationWrapper.tsx b/src/commons/application/ApplicationWrapper.tsx
index f96b346d50..e60fc31780 100644
--- a/src/commons/application/ApplicationWrapper.tsx
+++ b/src/commons/application/ApplicationWrapper.tsx
@@ -3,8 +3,7 @@ import { IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { RouterProvider } from 'react-router';
-import { createBrowserRouter } from 'react-router-dom';
+import { createBrowserRouter, RouterProvider } from 'react-router';
import { getAcademyRoutes } from 'src/pages/academy/academyRoutes';
import { getFullAcademyRouterConfig, playgroundOnlyRouterConfig } from '../../routes/routerConfig';
@@ -38,9 +37,7 @@ const ApplicationWrapper: React.FC = () => {
? playgroundOnlyRouterConfig
: getFullAcademyRouterConfig({ name, isLoggedIn, courseId, academyRoutes });
- const r = createBrowserRouter(routerConfig, {
- future: { v7_relativeSplatPath: true }
- });
+ const r = createBrowserRouter(routerConfig);
dispatch(updateReactRouter(r));
return r;
diff --git a/src/commons/application/actions/CommonsActions.ts b/src/commons/application/actions/CommonsActions.ts
index 54707c6555..0e9dc74b8e 100644
--- a/src/commons/application/actions/CommonsActions.ts
+++ b/src/commons/application/actions/CommonsActions.ts
@@ -1,4 +1,4 @@
-import { Router } from '@remix-run/router';
+import { Router } from 'src/commons/application/types/CommonsTypes';
import { createActions } from 'src/commons/redux/utils';
const CommonsActions = createActions('commons', {
diff --git a/src/commons/application/types/CommonsTypes.ts b/src/commons/application/types/CommonsTypes.ts
index e525fb2c84..d85fd201a7 100644
--- a/src/commons/application/types/CommonsTypes.ts
+++ b/src/commons/application/types/CommonsTypes.ts
@@ -1,3 +1,4 @@
-import { Router } from '@remix-run/router';
+import type { createBrowserRouter } from 'react-router';
+export type Router = ReturnType;
export type RouterState = Router | null;
diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx
index 2fbfb5526c..71ec517059 100644
--- a/src/commons/assessment/Assessment.tsx
+++ b/src/commons/assessment/Assessment.tsx
@@ -22,8 +22,7 @@ import classNames from 'classnames';
import { sortBy } from 'lodash';
import React, { useMemo, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { Navigate, useLoaderData, useParams } from 'react-router';
-import { NavLink } from 'react-router-dom';
+import { Navigate, NavLink, useLoaderData, useParams } from 'react-router';
import { numberRegExp } from 'src/features/academy/AcademyTypes';
import classes from 'src/styles/Academy.module.scss';
diff --git a/src/commons/assessment/__tests__/__snapshots__/Assessment.tsx.snap b/src/commons/assessment/__tests__/__snapshots__/Assessment.tsx.snap
index 6af2ca7d00..7f453e8c1b 100644
--- a/src/commons/assessment/__tests__/__snapshots__/Assessment.tsx.snap
+++ b/src/commons/assessment/__tests__/__snapshots__/Assessment.tsx.snap
@@ -493,6 +493,7 @@ exports[`Assessment page does not show attempt Button for upcoming assessments f
>
@@ -715,6 +716,7 @@ exports[`Assessment page does not show attempt Button for upcoming assessments f
>
@@ -1090,6 +1092,7 @@ exports[`Assessment page with multiple loaded missions renders correctly 1`] = `
>
@@ -1354,6 +1357,7 @@ exports[`Assessment page with multiple loaded missions renders correctly 1`] = `
>
@@ -1576,6 +1580,7 @@ exports[`Assessment page with multiple loaded missions renders correctly 1`] = `
>
diff --git a/src/commons/dropdown/DropdownSettings.tsx b/src/commons/dropdown/DropdownSettings.tsx
index dbf0a46437..b418b20a0a 100644
--- a/src/commons/dropdown/DropdownSettings.tsx
+++ b/src/commons/dropdown/DropdownSettings.tsx
@@ -10,7 +10,7 @@ import {
} from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useContext } from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate } from 'react-router';
import { EditorBinding, WorkspaceSettingsContext } from '../WorkspaceSettingsContext';
import LocaleSelector from './LocaleSelector';
diff --git a/src/commons/editingOverviewCard/EditingOverviewCard.tsx b/src/commons/editingOverviewCard/EditingOverviewCard.tsx
index 868c8973c3..5b3999734a 100644
--- a/src/commons/editingOverviewCard/EditingOverviewCard.tsx
+++ b/src/commons/editingOverviewCard/EditingOverviewCard.tsx
@@ -16,7 +16,7 @@ import {
import { IconNames } from '@blueprintjs/icons';
import { ItemRenderer, Select } from '@blueprintjs/select';
import React, { useState } from 'react';
-import { NavLink } from 'react-router-dom';
+import { NavLink } from 'react-router';
import Textarea from 'react-textarea-autosize';
import defaultCoverImage from '../../assets/default_cover_image.jpg';
diff --git a/src/commons/navigationBar/NavigationBar.tsx b/src/commons/navigationBar/NavigationBar.tsx
index 6b0b9fdd80..01b0c770f4 100644
--- a/src/commons/navigationBar/NavigationBar.tsx
+++ b/src/commons/navigationBar/NavigationBar.tsx
@@ -16,7 +16,7 @@ import { IconName, IconNames } from '@blueprintjs/icons';
import classNames from 'classnames';
import React, { useMemo, useState } from 'react';
import { Translation } from 'react-i18next';
-import { Location, NavLink, Route, Routes, useLocation } from 'react-router-dom';
+import { Location, NavLink, Route, Routes, useLocation } from 'react-router';
import { i18nDefaultLangKeys } from 'src/i18n/i18next';
import classes from 'src/styles/NavigationBar.module.scss';
diff --git a/src/commons/navigationBar/__tests__/NavigationBar.tsx b/src/commons/navigationBar/__tests__/NavigationBar.tsx
index ba1e839558..5c1935cb09 100644
--- a/src/commons/navigationBar/__tests__/NavigationBar.tsx
+++ b/src/commons/navigationBar/__tests__/NavigationBar.tsx
@@ -1,12 +1,12 @@
-import { useLocation } from 'react-router-dom';
+import { useLocation } from 'react-router';
import { useTypedSelector } from 'src/commons/utils/Hooks';
import { shallowRender } from 'src/commons/utils/TestUtils';
import { Role } from '../../application/ApplicationTypes';
import NavigationBar from '../NavigationBar';
-jest.mock('react-router-dom', () => ({
- ...jest.requireActual('react-router-dom'),
+jest.mock('react-router', () => ({
+ ...jest.requireActual('react-router'),
useLocation: jest.fn()
}));
jest.mock('react-redux', () => ({
diff --git a/src/commons/navigationBar/subcomponents/__tests__/SicpNavigationBar.tsx b/src/commons/navigationBar/subcomponents/__tests__/SicpNavigationBar.tsx
index 663bc2c1e8..90dd6e5a5d 100644
--- a/src/commons/navigationBar/subcomponents/__tests__/SicpNavigationBar.tsx
+++ b/src/commons/navigationBar/subcomponents/__tests__/SicpNavigationBar.tsx
@@ -1,12 +1,14 @@
-import * as ReactRouter from 'react-router';
import { shallowRender } from 'src/commons/utils/TestUtils';
import SicpNavigationBar from '../SicpNavigationBar';
-test('Navbar renders correctly', () => {
- jest.spyOn(ReactRouter, 'useParams').mockReturnValue({ section: 'index' });
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(jest.fn());
+jest.mock('react-router', () => ({
+ ...jest.requireActual('react-router'),
+ useParams: jest.fn().mockReturnValue({ section: 'index' }),
+ useNavigate: jest.fn().mockReturnValue(jest.fn())
+}));
+test('Navbar renders correctly', () => {
const navbar = ;
const tree = shallowRender(navbar);
expect(tree).toMatchSnapshot();
diff --git a/src/commons/profile/ProfileCard.tsx b/src/commons/profile/ProfileCard.tsx
index cd722900a7..d9e6846e63 100644
--- a/src/commons/profile/ProfileCard.tsx
+++ b/src/commons/profile/ProfileCard.tsx
@@ -1,7 +1,7 @@
import { Callout, ProgressBar } from '@blueprintjs/core';
import { IconName } from '@blueprintjs/icons';
import React from 'react';
-import { NavLink } from 'react-router-dom';
+import { NavLink } from 'react-router';
import { AssessmentOverview, AssessmentType } from '../assessment/AssessmentTypes';
import { assessmentTypeLink } from '../utils/ParamParseHelper';
diff --git a/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx b/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx
index 6af8e410fa..6f5c04d1d0 100644
--- a/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx
+++ b/src/commons/sideContent/content/remoteExecution/SideContentRemoteExecution.tsx
@@ -10,7 +10,7 @@ import {
import classNames from 'classnames';
import React, { SetStateAction, useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import { NavLink } from 'react-router-dom';
+import { NavLink } from 'react-router';
import BrickSvg from 'src/assets/BrickSvg';
import PortSvg from 'src/assets/PortSvg';
import { deleteDevice } from 'src/commons/sagas/RequestsSaga';
diff --git a/src/features/sicp/parser/ParseJson.tsx b/src/features/sicp/parser/ParseJson.tsx
index cd9f1ce6e7..8d3f669d3e 100644
--- a/src/features/sicp/parser/ParseJson.tsx
+++ b/src/features/sicp/parser/ParseJson.tsx
@@ -1,7 +1,7 @@
import { Blockquote, Code, H1, H2, H4, Icon, OL, Pre, UL } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
-import { Link } from 'react-router-dom';
+import { Link } from 'react-router';
import Constants from 'src/commons/utils/Constants';
import SicpExercise from 'src/pages/sicp/subcomponents/SicpExercise';
import SicpLatex from 'src/pages/sicp/subcomponents/SicpLatex';
diff --git a/src/features/sicp/parser/__tests__/ParseJson.tsx b/src/features/sicp/parser/__tests__/ParseJson.tsx
index fcc403ab75..ca6c8316ea 100644
--- a/src/features/sicp/parser/__tests__/ParseJson.tsx
+++ b/src/features/sicp/parser/__tests__/ParseJson.tsx
@@ -1,5 +1,5 @@
import lzString from 'lz-string';
-import { BrowserRouter } from 'react-router-dom';
+import { BrowserRouter } from 'react-router';
import { renderTreeJson } from 'src/commons/utils/TestUtils';
import { CodeSnippetProps } from 'src/pages/sicp/subcomponents/CodeSnippet';
diff --git a/src/features/sicp/parser/__tests__/__snapshots__/ParseJson.tsx.snap b/src/features/sicp/parser/__tests__/__snapshots__/ParseJson.tsx.snap
index 2cd4ec46dc..b2d8868098 100644
--- a/src/features/sicp/parser/__tests__/__snapshots__/ParseJson.tsx.snap
+++ b/src/features/sicp/parser/__tests__/__snapshots__/ParseJson.tsx.snap
@@ -197,6 +197,7 @@ exports[`Parse figures FIGURE with image and scale successful 1`] = `
>
@@ -503,6 +508,7 @@ exports[`Parse links LINK successful 1`] = `
exports[`Parse links REF successful 1`] = `
diff --git a/src/pages/academy/Academy.tsx b/src/pages/academy/Academy.tsx
index ea503ed10b..46c6277c5d 100644
--- a/src/pages/academy/Academy.tsx
+++ b/src/pages/academy/Academy.tsx
@@ -41,7 +41,8 @@ const CourseSelectingAcademy: React.FC = () => {
React.useEffect(() => {
// Regex to handle case where routeCourseIdStr is not a number
if (!routeCourseIdStr?.match(numberRegExp)) {
- return navigate('/');
+ navigate('/');
+ return;
}
if (routeCourseId !== undefined && !Number.isNaN(routeCourseId) && courseId !== routeCourseId) {
diff --git a/src/pages/academy/grading/subcomponents/GradingActions.tsx b/src/pages/academy/grading/subcomponents/GradingActions.tsx
index d43a80458c..ed6ef468d5 100644
--- a/src/pages/academy/grading/subcomponents/GradingActions.tsx
+++ b/src/pages/academy/grading/subcomponents/GradingActions.tsx
@@ -2,7 +2,7 @@ import { Button, Icon, Position, Tooltip } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import { useDispatch } from 'react-redux';
-import { Link } from 'react-router-dom';
+import { Link } from 'react-router';
import SessionActions from 'src/commons/application/actions/SessionActions';
import { ProgressStatus, ProgressStatuses } from 'src/commons/assessment/AssessmentTypes';
import GradingFlex from 'src/commons/grading/GradingFlex';
diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx
index cec762c819..72f9f1b18d 100644
--- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx
+++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx
@@ -9,7 +9,7 @@ import classNames from 'classnames';
import { debounce } from 'lodash';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate } from 'react-router';
import { ProgressStatuses } from 'src/commons/assessment/AssessmentTypes';
import GradingFlex from 'src/commons/grading/GradingFlex';
import GradingText from 'src/commons/grading/GradingText';
diff --git a/src/pages/academy/teamFormation/subcomponents/TeamFormationActions.tsx b/src/pages/academy/teamFormation/subcomponents/TeamFormationActions.tsx
index 3fe8d8cede..6b47c08cc2 100644
--- a/src/pages/academy/teamFormation/subcomponents/TeamFormationActions.tsx
+++ b/src/pages/academy/teamFormation/subcomponents/TeamFormationActions.tsx
@@ -3,7 +3,7 @@ import { IconNames } from '@blueprintjs/icons';
import { Flex, Icon } from '@tremor/react';
import React, { useCallback } from 'react';
import { useDispatch } from 'react-redux';
-import { Link } from 'react-router-dom';
+import { Link } from 'react-router';
import SessionActions from 'src/commons/application/actions/SessionActions';
import { showSimpleConfirmDialog } from 'src/commons/utils/DialogHelper';
import { useSession } from 'src/commons/utils/Hooks';
diff --git a/src/pages/academy/teamFormation/subcomponents/TeamFormationForm.tsx b/src/pages/academy/teamFormation/subcomponents/TeamFormationForm.tsx
index c74b0fda47..5065d094c2 100644
--- a/src/pages/academy/teamFormation/subcomponents/TeamFormationForm.tsx
+++ b/src/pages/academy/teamFormation/subcomponents/TeamFormationForm.tsx
@@ -3,8 +3,7 @@ import '@tremor/react/dist/esm/tremor.css';
import { Button } from '@blueprintjs/core';
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { useNavigate } from 'react-router';
-import { Form, useParams } from 'react-router-dom';
+import { Form, useNavigate, useParams } from 'react-router';
import Select, { ActionMeta, MultiValue } from 'react-select';
import SessionActions from 'src/commons/application/actions/SessionActions';
import { User } from 'src/commons/application/types/SessionTypes';
diff --git a/src/pages/academy/teamFormation/subcomponents/TeamFormationImport.tsx b/src/pages/academy/teamFormation/subcomponents/TeamFormationImport.tsx
index ffd2186445..26b6481998 100644
--- a/src/pages/academy/teamFormation/subcomponents/TeamFormationImport.tsx
+++ b/src/pages/academy/teamFormation/subcomponents/TeamFormationImport.tsx
@@ -4,8 +4,7 @@ import { Button } from '@blueprintjs/core';
import { useState } from 'react';
import { FileUploader } from 'react-drag-drop-files';
import { useDispatch } from 'react-redux';
-import { useNavigate } from 'react-router';
-import { Form } from 'react-router-dom';
+import { Form, useNavigate } from 'react-router';
import Select from 'react-select';
import SessionActions from 'src/commons/application/actions/SessionActions';
import { AssessmentOverview } from 'src/commons/assessment/AssessmentTypes';
diff --git a/src/pages/githubCallback/__tests__/GitHubCallback.tsx b/src/pages/githubCallback/__tests__/GitHubCallback.tsx
index c75995789e..8f2a259e6c 100644
--- a/src/pages/githubCallback/__tests__/GitHubCallback.tsx
+++ b/src/pages/githubCallback/__tests__/GitHubCallback.tsx
@@ -1,8 +1,7 @@
import { render, screen } from '@testing-library/react';
import { MockedFunction } from 'jest-mock';
import { act } from 'react';
-import { Route, Routes } from 'react-router';
-import { StaticRouter } from 'react-router-dom/server';
+import { Route, Routes, StaticRouter } from 'react-router';
import Constants from '../../../commons/utils/Constants';
import { exchangeAccessCode } from '../../../features/github/GitHubUtils';
diff --git a/src/pages/login/__tests__/Login.tsx b/src/pages/login/__tests__/Login.tsx
index 1bbc802073..b1ba5c5474 100644
--- a/src/pages/login/__tests__/Login.tsx
+++ b/src/pages/login/__tests__/Login.tsx
@@ -1,7 +1,6 @@
import { Store } from '@reduxjs/toolkit';
import { render } from '@testing-library/react';
import { Provider, useDispatch } from 'react-redux';
-import * as ReactRouter from 'react-router';
import { createMemoryRouter, RouterProvider } from 'react-router';
import SessionActions from 'src/commons/application/actions/SessionActions';
import { OverallState } from 'src/commons/application/ApplicationTypes';
@@ -29,6 +28,13 @@ jest.mock('../../../commons/utils/Constants', () => {
};
});
+// https://stackoverflow.com/a/74525026
+const navigateSpy = jest.fn();
+jest.mock('react-router', () => ({
+ ...jest.requireActual('react-router'),
+ useNavigate: () => navigateSpy
+}));
+
const createTestComponent = (mockStore: Store, location: string) => {
const router = createMemoryRouter(
[
@@ -96,9 +102,6 @@ describe('Login', () => {
describe('When isLoggedIn and no course', () => {
test('/login redirects to /welcome', () => {
- const navigateSpy = jest.fn();
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(navigateSpy);
-
const store = mockInitialStore({
session: {
name: 'Bob'
@@ -111,9 +114,6 @@ describe('Login', () => {
});
test('/login/callback redirects to /welcome', () => {
- const navigateSpy = jest.fn();
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(navigateSpy);
-
const store = mockInitialStore({
session: {
name: 'Bob'
@@ -128,9 +128,6 @@ describe('Login', () => {
describe('When isLoggedIn and has course', () => {
test('/login redirects to /courses/', () => {
- const navigateSpy = jest.fn();
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(navigateSpy);
-
const courseId = 2;
const store = mockInitialStore({
session: {
@@ -145,9 +142,6 @@ describe('Login', () => {
});
test('/login/callback redirects to /courses/', () => {
- const navigateSpy = jest.fn();
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(navigateSpy);
-
const courseId = 2;
const store = mockInitialStore({
session: {
@@ -163,9 +157,6 @@ describe('Login', () => {
});
test('/login/callback redirects to /login when not isLoggedIn, nor code/ticket nor SAML redirect', () => {
- const navigateSpy = jest.fn();
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(navigateSpy);
-
const store = mockInitialStore();
const app = createTestComponent(store, '/login/callback');
render(app);
diff --git a/src/pages/playground/__tests__/Playground.tsx b/src/pages/playground/__tests__/Playground.tsx
index 17ee162373..8e63cddc6d 100644
--- a/src/pages/playground/__tests__/Playground.tsx
+++ b/src/pages/playground/__tests__/Playground.tsx
@@ -1,5 +1,4 @@
import { Dispatch, Store } from '@reduxjs/toolkit';
-import { Router } from '@remix-run/router';
import { render } from '@testing-library/react';
import { FSModule } from 'browserfs/dist/node/core/FS';
import { Chapter } from 'js-slang/dist/types';
@@ -11,6 +10,7 @@ import {
defaultPlayground,
OverallState
} from 'src/commons/application/ApplicationTypes';
+import { Router } from 'src/commons/application/types/CommonsTypes';
import { EditorBinding, WorkspaceSettingsContext } from 'src/commons/WorkspaceSettingsContext';
import { createStore } from 'src/pages/createStore';
diff --git a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
index ca028d396f..17e0741271 100644
--- a/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
+++ b/src/pages/playground/__tests__/__snapshots__/Playground.tsx.snap
@@ -1352,6 +1352,7 @@ and also the
Please
log in
@@ -2654,6 +2655,7 @@ and also the
Please
log in
diff --git a/src/pages/sicp/Sicp.tsx b/src/pages/sicp/Sicp.tsx
index 73798b81ca..431e2a94f4 100644
--- a/src/pages/sicp/Sicp.tsx
+++ b/src/pages/sicp/Sicp.tsx
@@ -4,8 +4,7 @@ import { Button, Classes, NonIdealState, Spinner } from '@blueprintjs/core';
import classNames from 'classnames';
import React, { useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { useLocation, useNavigate, useParams } from 'react-router';
-import { Link } from 'react-router-dom';
+import { Link, useLocation, useNavigate, useParams } from 'react-router';
import Constants from 'src/commons/utils/Constants';
import { useSession } from 'src/commons/utils/Hooks';
import { setLocalStorage } from 'src/commons/utils/LocalStorageHelper';
diff --git a/src/pages/sicp/__tests__/Sicp.tsx b/src/pages/sicp/__tests__/Sicp.tsx
index 0e516abb1c..5cf0d3f8bf 100644
--- a/src/pages/sicp/__tests__/Sicp.tsx
+++ b/src/pages/sicp/__tests__/Sicp.tsx
@@ -1,15 +1,20 @@
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
-import * as ReactRouter from 'react-router';
+import type { Location } from 'react-router';
import { mockInitialStore } from 'src/commons/mocks/StoreMocks';
import { shallowRender } from 'src/commons/utils/TestUtils';
import Sicp from '../Sicp';
+jest.mock('react-router', () => ({
+ ...jest.requireActual('react-router'),
+ useParams: jest.fn().mockReturnValue({ section: 'index' }),
+ useNavigate: jest.fn().mockReturnValue(jest.fn()),
+ useLocation: jest.fn().mockReturnValue({} as Location)
+}));
+
describe('Sicp renders', () => {
test('correctly', () => {
- jest.spyOn(ReactRouter, 'useParams').mockReturnValue({ section: 'index' });
-
const sicp = (
@@ -20,9 +25,6 @@ describe('Sicp renders', () => {
});
test('index section correctly', () => {
- jest.spyOn(ReactRouter, 'useParams').mockReturnValue({ section: 'index' });
- jest.spyOn(ReactRouter, 'useNavigate').mockReturnValue(jest.fn());
- jest.spyOn(ReactRouter, 'useLocation').mockReturnValue({} as ReactRouter.Location);
window.HTMLElement.prototype.scrollIntoView = function () {};
const sicp = (
diff --git a/src/pages/stories/Stories.tsx b/src/pages/stories/Stories.tsx
index db96feadb9..0d97386bea 100644
--- a/src/pages/stories/Stories.tsx
+++ b/src/pages/stories/Stories.tsx
@@ -2,7 +2,7 @@ import { Button, InputGroup, NonIdealState } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React, { useCallback, useState } from 'react';
import { useDispatch } from 'react-redux';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate } from 'react-router';
import { StoriesRole } from 'src/commons/application/ApplicationTypes';
import ContentDisplay from 'src/commons/ContentDisplay';
import GradingFlex from 'src/commons/grading/GradingFlex';
diff --git a/src/pages/stories/StoryActions.tsx b/src/pages/stories/StoryActions.tsx
index cedbef6218..17e67d8187 100644
--- a/src/pages/stories/StoryActions.tsx
+++ b/src/pages/stories/StoryActions.tsx
@@ -1,7 +1,7 @@
import { Button, Position, Tooltip } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate } from 'react-router';
import GradingFlex from 'src/commons/grading/GradingFlex';
type Props = {
diff --git a/src/setupTests.ts b/src/setupTests.ts
index 56c41020d0..e774ab1f74 100644
--- a/src/setupTests.ts
+++ b/src/setupTests.ts
@@ -1,5 +1,7 @@
import '@testing-library/jest-dom';
+import { TextDecoder, TextEncoder } from 'node:util';
+
// Mock ResizeObserver in tests
// eslint-disable-next-line @typescript-eslint/no-require-imports
global.ResizeObserver = require('resize-observer-polyfill');
@@ -16,3 +18,14 @@ jest.mock('java-slang', () => {
typeCheck: () => ({ hasTypeErrors: false, errorMsgs: [] })
};
});
+
+// Fix for react-router v7 and jest
+// https://stackoverflow.com/a/79332264
+
+if (!global.TextEncoder) {
+ global.TextEncoder = TextEncoder;
+}
+
+if (!global.TextDecoder) {
+ global.TextDecoder = TextDecoder as any;
+}
diff --git a/yarn.lock b/yarn.lock
index 5cd23ce2e6..2afe3a74e8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2889,13 +2889,6 @@ __metadata:
languageName: node
linkType: hard
-"@remix-run/router@npm:1.23.0":
- version: 1.23.0
- resolution: "@remix-run/router@npm:1.23.0"
- checksum: 10c0/eaef5cb46a1e413f7d1019a75990808307e08e53a39d4cf69c339432ddc03143d725decef3d6b9b5071b898da07f72a4a57c4e73f787005fcf10162973d8d7d7
- languageName: node
- linkType: hard
-
"@rollup/plugin-babel@npm:^5.2.0":
version: 5.3.1
resolution: "@rollup/plugin-babel@npm:5.3.1"
@@ -5977,6 +5970,13 @@ __metadata:
languageName: node
linkType: hard
+"cookie@npm:^1.0.1":
+ version: 1.0.2
+ resolution: "cookie@npm:1.0.2"
+ checksum: 10c0/fd25fe79e8fbcfcaf6aa61cd081c55d144eeeba755206c058682257cb38c4bd6795c6620de3f064c740695bb65b7949ebb1db7a95e4636efb8357a335ad3f54b
+ languageName: node
+ linkType: hard
+
"copy-to-clipboard@npm:^3.3.1":
version: 3.3.3
resolution: "copy-to-clipboard@npm:3.3.3"
@@ -7848,7 +7848,7 @@ __metadata:
react-papaparse: "npm:^4.0.2"
react-qr-reader: "npm:^3.0.0-beta-1"
react-redux: "npm:^8.1.1"
- react-router-dom: "npm:^6.14.1"
+ react-router: "npm:^7.6.2"
react-select: "npm:^5.7.3"
react-simple-keyboard: "npm:^3.6.27"
react-sortable-hoc: "npm:^2.0.0"
@@ -12233,27 +12233,19 @@ __metadata:
languageName: node
linkType: hard
-"react-router-dom@npm:^6.14.1":
- version: 6.30.1
- resolution: "react-router-dom@npm:6.30.1"
+"react-router@npm:^7.6.2":
+ version: 7.6.2
+ resolution: "react-router@npm:7.6.2"
dependencies:
- "@remix-run/router": "npm:1.23.0"
- react-router: "npm:6.30.1"
+ cookie: "npm:^1.0.1"
+ set-cookie-parser: "npm:^2.6.0"
peerDependencies:
- react: ">=16.8"
- react-dom: ">=16.8"
- checksum: 10c0/e9e1297236b0faa864424ad7d51c392fc6e118595d4dad4cd542fd217c479a81601a81c6266d5801f04f9e154de02d3b094fc22ccb544e755c2eb448fab4ec6b
- languageName: node
- linkType: hard
-
-"react-router@npm:6.30.1":
- version: 6.30.1
- resolution: "react-router@npm:6.30.1"
- dependencies:
- "@remix-run/router": "npm:1.23.0"
- peerDependencies:
- react: ">=16.8"
- checksum: 10c0/0414326f2d8e0c107fb4603cf4066dacba6a1f6f025c6e273f003e177b2f18888aca3de06d9b5522908f0e41de93be1754c37e82aa97b3a269c4742c08e82539
+ react: ">=18"
+ react-dom: ">=18"
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+ checksum: 10c0/c8ef65f2a378f38e3cba900d67fa2b80a41c1c3925102875ee07c12faa01ea40991cb3fbefaf3ff6914e724c755732e3d7dec2b1bdef09e0fddd00fccc85a06a
languageName: node
linkType: hard
@@ -13310,6 +13302,13 @@ __metadata:
languageName: node
linkType: hard
+"set-cookie-parser@npm:^2.6.0":
+ version: 2.7.1
+ resolution: "set-cookie-parser@npm:2.7.1"
+ checksum: 10c0/060c198c4c92547ac15988256f445eae523f57f2ceefeccf52d30d75dedf6bff22b9c26f756bd44e8e560d44ff4ab2130b178bd2e52ef5571bf7be3bd7632d9a
+ languageName: node
+ linkType: hard
+
"set-function-length@npm:^1.2.2":
version: 1.2.2
resolution: "set-function-length@npm:1.2.2"