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"