fix(client): move to react-router 7.18.2 (#1804)

* refactor(client): opt into the v7 router behaviour while still on v6

Turning the behaviour on separately from the version bump keeps the two failure
modes apart: red here means a behaviour change, red on the bump means the import
rewrite. The suite is unchanged either way — 527 files, 11099 tests.

Only the two flags a non-data router understands are set; the rest belong to
createBrowserRouter, which this app doesn't use. Both are applied at all six
router mounts, so the tests exercise the same behaviour as production rather than
validating v6 while prod runs v7.

* fix(client): move to react-router 7.18.2

6.30.4 is affected by GHSA-wrjc-x8rr-h8h6 — open redirect through a backslash in
Link and useNavigate, which this app uses in 41 places — and the 6.x line has no
fix: react-router-dom 6.30.2 to 6.30.4 carries GHSA-jjmj-jmhj-qwj2 with no patched
version at all. Staying is not an option, and 7.0 to 7.17 only swaps it for
GHSA-chx6-hx7r-mcp5.

The package rename comes with it: react-router-dom is a re-export shim in v7, so
using it would only defer the same rewrite to the v8 jump. 77 occurrences across
60 files, all of them import specifiers and vi.mock strings.

The surface is small — useNavigate, useParams, useSearchParams, useLocation,
Routes, Route, Link, Navigate, useMatch, MemoryRouter, BrowserRouter — no data
router, no loaders or actions. Nothing else had to change; the future flags from
the previous commit are simply how v7 behaves, so they come back out.

npm audit still reports GHSA-qwww-vcr4-c8h2 on react-router (RSC mode CSRF, fixed
in 8.3.0). It needs RSC mode, which needs a server; this is a static SPA. Closing
it means the v8 major, which is its own piece of work.
This commit is contained in:
Maurice
2026-08-06 23:01:58 +02:00
committed by GitHub
parent e606ebd9b9
commit 6c63a9bb60
94 changed files with 158 additions and 158 deletions
+1 -1
View File
@@ -50,7 +50,7 @@
"react-leaflet": "^5.0.0",
"react-leaflet-cluster": "^4.1.3",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.22.2",
"react-router": "^7.18.2",
"rehype-sanitize": "^6.0.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import { MemoryRouter } from 'react-router-dom'
import { MemoryRouter } from 'react-router'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { http, HttpResponse } from 'msw'
import { server } from '../tests/helpers/msw/server'
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect, ReactNode } from 'react'
import { Routes, Route, Navigate, useLocation } from 'react-router-dom'
import { Routes, Route, Navigate, useLocation } from 'react-router'
import { useAuthStore } from './store/authStore'
import { useSettingsStore } from './store/settingsStore'
import { applyAppearance } from './theme/applyAppearance'
@@ -8,8 +8,8 @@ import { useBackgroundTasksStore, type BackgroundImportTask } from '../../store/
import BackgroundTasksWidget from './BackgroundTasksWidget'
const navigate = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router')
return { ...actual, useNavigate: () => navigate }
})
vi.mock('../../api/websocket', () => ({ addListener: vi.fn(), removeListener: vi.fn() }))
@@ -1,6 +1,6 @@
import ReactDOM from 'react-dom'
import { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { Loader2, CheckCircle2, AlertCircle, X } from 'lucide-react'
import { useTranslation } from '../../i18n'
import { addListener, removeListener } from '../../api/websocket'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useSearchParams } from 'react-router'
import { ArrowDown, ArrowUp, BarChart3, Plus, Search, ArrowRight, ArrowLeftRight, Check, RotateCcw, Pencil, Trash2, AlertCircle, Download } from 'lucide-react'
import { useTripStore } from '../../store/tripStore'
import { useAuthStore } from '../../store/authStore'
@@ -9,8 +9,8 @@ import type { SaveToCollectionTarget } from '../../store/saveToCollectionStore';
import MSaveToCollectionSheet from './MSaveToCollectionSheet';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { Bookmark, BookmarkCheck, Check, Loader2, Plus, X } from 'lucide-react'
import MSheet from '../../mobile/components/MSheet'
import MIconBtn from '../../mobile/components/MIconBtn'
@@ -9,8 +9,8 @@ import type { SaveToCollectionTarget } from '../../store/saveToCollectionStore';
import SaveToCollectionModal from './SaveToCollectionModal';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { Bookmark, BookmarkCheck, Check, Loader2, Plus } from 'lucide-react'
import Modal from '../shared/Modal'
import { useToast } from '../shared/Toast'
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import { Bookmark, ArrowRight, MapPin } from 'lucide-react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { useTranslation } from '../../i18n'
import { collectionsApi } from '../../api/collections'
import { entityGradient } from '../../utils/gradients'
@@ -11,8 +11,8 @@ import { JourneySettingsDialog } from './JourneyDetailPageSettingsDialog'
type ToastKind = 'success' | 'error' | 'warning' | 'info'
const mockNavigate = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router')
return { ...actual, useNavigate: () => mockNavigate }
})
@@ -1,5 +1,5 @@
import { useState, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { X, ImagePlus, Plus, Trash2, UserPlus, Archive, ArchiveRestore } from 'lucide-react'
import { useJourneyStore } from '../../store/journeyStore'
import { useTranslation } from '../../i18n'
@@ -11,8 +11,8 @@ vi.mock('../../api/websocket', () => ({
}));
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
+1 -1
View File
@@ -1,4 +1,4 @@
import { useNavigate, useLocation, useMatch } from 'react-router-dom'
import { useNavigate, useLocation, useMatch } from 'react-router'
import { useAddonStore } from '../../store/addonStore'
import { usePluginStore } from '../../store/pluginStore'
import { useSettingsStore } from '../../store/settingsStore'
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { Bell, Trash2, CheckCheck } from 'lucide-react'
import { useTranslation } from '../../i18n'
import { useInAppNotificationStore } from '../../store/inAppNotificationStore.ts'
@@ -3,8 +3,8 @@
vi.mock('./InAppNotificationBell', () => ({ default: () => null }));
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
@@ -1,6 +1,6 @@
import { useState, useEffect } from 'react'
import { createPortal } from 'react-dom'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { useAuthStore } from '../../store/authStore'
import { useInAppNotificationStore } from '../../store/inAppNotificationStore'
import { useTranslation } from '../../i18n'
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'
import { Link, useNavigate, useLocation } from 'react-router-dom'
import { Link, useNavigate, useLocation } from 'react-router'
import { useAuthStore } from '../../store/authStore'
import { useSettingsStore } from '../../store/settingsStore'
import { useAddonStore } from '../../store/addonStore'
@@ -1,5 +1,5 @@
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { User, Check, X, ArrowRight, Trash2, CheckCheck } from 'lucide-react'
import { useTranslation } from '../../i18n'
import { useInAppNotificationStore, InAppNotification } from '../../store/inAppNotificationStore'
@@ -20,9 +20,9 @@ import { ReservationModal } from './ReservationModal';
import type { BookingReviewDraft } from './parsedItemToDraft';
import type { TripMember } from '../Budget/BudgetPanelMemberChips';
// Mock react-router-dom useParams
vi.mock('react-router-dom', async (importActual) => {
const actual = await importActual<typeof import('react-router-dom')>();
// Mock react-router useParams
vi.mock('react-router', async (importActual) => {
const actual = await importActual<typeof import('react-router')>();
return { ...actual, useParams: () => ({ id: '1' }) };
});
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef, useMemo } from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import apiClient from '../../api/client'
import { useTripStore } from '../../store/tripStore'
import { useAddonStore } from '../../store/addonStore'
@@ -21,8 +21,8 @@ import type { Day, Reservation } from '../../types';
import type { BookingReviewDraft } from './parsedItemToDraft';
import type { TripMember } from '../Budget/BudgetPanelMemberChips';
vi.mock('react-router-dom', async (importActual) => {
const actual = await importActual<typeof import('react-router-dom')>();
vi.mock('react-router', async (importActual) => {
const actual = await importActual<typeof import('react-router')>();
return { ...actual, useParams: () => ({ id: '1' }) };
});
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import { Plane, Train, Car, Ship, Bus, Sailboat, Bike, CarTaxiFront, Route, TramFront, Paperclip, FileText, X, ExternalLink, Link2, Plus, Trash2 } from 'lucide-react'
import Modal from '../shared/Modal'
import CustomSelect from '../shared/CustomSelect'
@@ -18,7 +18,7 @@ const host = vi.hoisted(() => ({
trip: null as Record<string, unknown> | null,
}));
vi.mock('react-router-dom', () => ({ useNavigate: () => navigate }));
vi.mock('react-router', () => ({ useNavigate: () => navigate }));
vi.mock('../shared/Toast', () => ({ useToast: () => toast }));
vi.mock('../../i18n', () => ({ useTranslation: () => ({ locale: 'en', t: (k: string) => k }) }));
vi.mock('../../store/authStore', () => ({ useAuthStore: (sel: (s: unknown) => unknown) => sel({ user: host.user }) }));
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { useTranslation } from '../../i18n'
import { useAuthStore } from '../../store/authStore'
import { useSettingsStore } from '../../store/settingsStore'
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import { User, Save, Lock, KeyRound, AlertTriangle, Shield, Camera, Trash2, Copy, Download, Printer } from 'lucide-react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router'
import { useTranslation } from '../../i18n'
import { useAuthStore } from '../../store/authStore'
import { useToast } from '../shared/Toast'
@@ -1,5 +1,5 @@
import React, { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import { Lock } from 'lucide-react'
import { useTranslation } from '../../i18n'
import { notificationsApi, settingsApi } from '../../api/client'
@@ -7,8 +7,8 @@ import { registerNoticeAction } from './noticeActions'
import { BannerRenderer, ToastRenderer } from './SystemNoticeBanner'
const navigate = vi.fn()
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom')
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router')
return { ...actual, useNavigate: () => navigate }
})
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { Info, AlertTriangle, AlertOctagon, X } from 'lucide-react';
import { useSystemNoticeStore } from '../../store/systemNoticeStore.js';
import type { SystemNoticeDTO } from '../../store/systemNoticeStore.js';
@@ -10,8 +10,8 @@ import { ModalRenderer } from './SystemNoticeModal';
import type { SystemNoticeDTO } from '../../store/systemNoticeStore';
const routerMocks = vi.hoisted(() => ({ navigate: vi.fn((_to: string) => {}) }));
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => routerMocks.navigate };
});
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react';
import { flushSync } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router';
import { Info, AlertTriangle, AlertOctagon, X, ChevronLeft, ChevronRight, Coffee } from 'lucide-react';
import * as LucideIcons from 'lucide-react';
import remarkGfm from 'remark-gfm';
@@ -1,6 +1,6 @@
// FE-W4SNA-001 to FE-W4SNA-005
import { describe, it, expect, vi, afterEach } from 'vitest'
import type { NavigateFunction } from 'react-router-dom'
import type { NavigateFunction } from 'react-router'
import { registerNoticeAction, runNoticeAction } from './noticeActions'
const navigate = vi.fn() as unknown as NavigateFunction
@@ -1,4 +1,4 @@
import type { NavigateFunction } from 'react-router-dom';
import type { NavigateFunction } from 'react-router';
export interface NoticeActionContext {
navigate: NavigateFunction;
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { BrowserRouter } from 'react-router'
import App from './App'
// Self-hosted Poppins (bundled, same-origin) so the app font can't be blocked by
// ad/tracker blockers the way the Google Fonts CDN can.
+1 -1
View File
@@ -1,5 +1,5 @@
import { ReactNode } from 'react'
import { useMatch } from 'react-router-dom'
import { useMatch } from 'react-router'
import BottomNav from '../components/Layout/BottomNav'
import MBottomNav from './components/MBottomNav'
import MToastHost from './components/MToastHost'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate, useLocation, useMatch } from 'react-router-dom'
import { useNavigate, useLocation, useMatch } from 'react-router'
import { useSettingsStore } from '../../store/settingsStore'
import { useTranslation } from '../../i18n'
import { ChevronRight, MoreHorizontal, Plus, Search } from 'lucide-react'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useSearchParams } from 'react-router'
import { X } from 'lucide-react'
import { useAtlas } from '../../../pages/atlas/useAtlas'
import MIconBtn from '../../components/MIconBtn'
@@ -1,5 +1,5 @@
import { ReactNode, useEffect, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useSearchParams } from 'react-router'
import {
Bookmark, Check, CheckCheck, CheckSquare, ChevronDown, Copy, CopyPlus,
FolderInput, Layers, List, Loader2, Map as MapIcon, Pencil, Plus, Search, Share2,
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
ArrowRight, ArrowRightLeft, Bookmark, Calendar, ChevronDown, ChevronRight,
Clock, Hotel, MapPin, Plane, Plus, RefreshCw, Ticket, Utensils, X,
@@ -1,5 +1,5 @@
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { LogOut, Moon, Settings2, Shield, Sun, SunMoon } from 'lucide-react'
import type { LucideIcon } from 'lucide-react'
import { useTranslation } from '../../../i18n'
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
X, Image, Plus, Trash2, UserPlus, Link as LinkIcon,
List, Grid3x3, MapPin, Archive, ArchiveRestore,
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { ArrowRight, Bell, Check, Trash2, X } from 'lucide-react'
import { useTranslation } from '../../../i18n'
import { useInAppNotificationStore, InAppNotification } from '../../../store/inAppNotificationStore'
@@ -1,4 +1,4 @@
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { ArrowLeft, CheckCheck, Trash2 } from 'lucide-react'
import MDancingTrek from '../../components/MDancingTrek'
import { useTranslation } from '../../../i18n'
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import {
Bell,
ChevronDown,
@@ -16,7 +16,7 @@ import {
User,
X,
} from 'lucide-react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router'
import { startRegistration } from '@simplewebauthn/browser'
import { useTranslation } from '../../../i18n'
import { useAuthStore } from '../../../store/authStore'
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { Bell, Link2, Send } from 'lucide-react'
import { useTranslation } from '../../../i18n'
import { notificationsApi, settingsApi } from '../../../api/client'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { useVacay } from '../../../pages/vacay/useVacay'
import { useVacayStore } from '../../../store/vacayStore'
import { useAuthStore } from '../../../store/authStore'
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, waitFor, act } from '../../tests/helpers/render';
import { Route, Routes } from 'react-router-dom';
import { Route, Routes } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../tests/helpers/msw/server';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import PageShell from '../components/Layout/PageShell'
import { PageSpinner } from '../components/shared/Spinner'
import FileManager from '../components/Files/FileManager'
+1 -1
View File
@@ -1,5 +1,5 @@
import { Children, type ReactNode } from 'react'
import { Link } from 'react-router-dom'
import { Link } from 'react-router'
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { Search, ChevronRight, Loader2, AlertCircle, BookOpen, PanelLeft, X } from 'lucide-react'
+2 -2
View File
@@ -68,8 +68,8 @@ vi.mock('../components/shared/ConfirmDialog', () => ({
}));
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return {
...actual,
useParams: () => ({ id: '1' }),
+2 -2
View File
@@ -15,8 +15,8 @@ import JourneyPage from './JourneyPage';
// ── Mocks ────────────────────────────────────────────────────────────────────
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
+3 -3
View File
@@ -2,7 +2,7 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import React from 'react';
import { render, screen, waitFor, fireEvent } from '../../tests/helpers/render';
import { Routes, Route } from 'react-router-dom';
import { Routes, Route } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../tests/helpers/msw/server';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
@@ -11,8 +11,8 @@ import userEvent from '@testing-library/user-event';
// ── Mocks ────────────────────────────────────────────────────────────────────
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return { ...actual, useParams: () => ({ token: 'test-share-token' }) };
});
@@ -6,8 +6,8 @@ import { resetAllStores } from '../../tests/helpers/store';
import LoginPage from './LoginPage';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
+1 -1
View File
@@ -1,5 +1,5 @@
import React from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import PageShell from '../components/Layout/PageShell'
import PluginFrame from '../components/Plugins/PluginFrame'
import { usePluginStore } from '../store/pluginStore'
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { render, screen, waitFor, fireEvent } from '../../tests/helpers/render';
import { Routes, Route } from 'react-router-dom';
import { Routes, Route } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../tests/helpers/msw/server';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
+1 -1
View File
@@ -1,7 +1,7 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import React from 'react';
import { render, screen, waitFor, act, fireEvent } from '../../tests/helpers/render';
import { Routes, Route } from 'react-router-dom';
import { Routes, Route } from 'react-router';
import { resetAllStores, seedStore } from '../../tests/helpers/store';
import { buildUser, buildTrip, buildDay, buildPlace, buildAssignment } from '../../tests/helpers/factories';
import { useAuthStore } from '../store/authStore';
+1 -1
View File
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { useParams, useNavigate, useSearchParams } from 'react-router'
import { useTripStore } from '../store/tripStore'
import { useCanDo } from '../store/permissionsStore'
import { useSettingsStore } from '../store/settingsStore'
+1 -1
View File
@@ -2,7 +2,7 @@
import { http, HttpResponse } from 'msw';
import React from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { MemoryRouter } from 'react-router-dom';
import { MemoryRouter } from 'react-router';
import { buildAdmin, buildUser } from '../../../tests/helpers/factories';
import { server } from '../../../tests/helpers/msw/server';
import { act, renderHook, waitFor } from '../../../tests/helpers/render';
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import apiClient, { adminApi, authApi } from '../../api/client'
import { useAuthStore } from '../../store/authStore'
import { useSettingsStore } from '../../store/settingsStore'
+1 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useState, useRef } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { getIntlLanguage, getLocaleForLanguage, useTranslation } from '../../i18n'
import { useSettingsStore } from '../../store/settingsStore'
import apiClient, { mapsApi, pluginsApi, type PluginAtlasLayer } from '../../api/client'
@@ -16,7 +16,7 @@ import { useCollections } from './useCollections'
const navigate = vi.fn()
let routeParams: { id?: string } = {}
vi.mock('react-router-dom', () => ({
vi.mock('react-router', () => ({
useNavigate: () => navigate,
useParams: () => routeParams,
}))
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useNavigate, useParams } from 'react-router'
import { useTranslation } from '../../i18n'
import { useElementSize } from '../../hooks/useElementSize'
import { useElementRect } from '../../hooks/useElementRect'
@@ -1,7 +1,7 @@
import React from 'react';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { renderHook, act, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { MemoryRouter } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../../tests/helpers/msw/server';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router'
import { tripsApi, authApi, reservationsApi } from '../../api/client'
import { tripRepo } from '../../repo/tripRepo'
import { useAuthStore } from '../../store/authStore'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useParams, useNavigate } from 'react-router'
import { useTripStore } from '../../store/tripStore'
import { tripRepo } from '../../repo/tripRepo'
import { placeRepo } from '../../repo/placeRepo'
@@ -5,7 +5,7 @@ import { authApi } from '../../api/client';
import { useForgotPassword } from './useForgotPassword';
const navigate = vi.fn();
vi.mock('react-router-dom', () => ({ useNavigate: () => navigate }));
vi.mock('react-router', () => ({ useNavigate: () => navigate }));
const submitEvent = () => ({ preventDefault: vi.fn() }) as unknown as FormEvent;
@@ -1,5 +1,5 @@
import { useState, useEffect, type FormEvent } from 'react'
import { useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router'
import { authApi } from '../../api/client'
/**
+1 -1
View File
@@ -4,7 +4,7 @@ import { helpApi, type HelpNavSection, type HelpPageData } from '../../api/clien
import { useHelp } from './useHelp';
let routeParams: { slug?: string } = {};
vi.mock('react-router-dom', () => ({ useParams: () => routeParams }));
vi.mock('react-router', () => ({ useParams: () => routeParams }));
const SECTIONS: HelpNavSection[] = [
{ title: 'Getting started', pages: [{ slug: 'Home', title: 'Welcome' }, { slug: 'Install', title: 'Installation' }] },
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import { helpApi, type HelpNavSection, type HelpPageData } from '../../api/client'
/** State + data loading for the in-app help wiki (see PATTERN.md). */
+1 -1
View File
@@ -6,7 +6,7 @@ import { useJoinTrip } from './useJoinTrip';
const navigate = vi.fn();
let params: { token?: string } = { token: 'inv-1' };
vi.mock('react-router-dom', () => ({
vi.mock('react-router', () => ({
useNavigate: () => navigate,
useParams: () => params,
}));
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { useNavigate, useParams } from 'react-router'
import { tripInviteApi } from '../../api/client'
export type JoinTripState = 'loading' | 'ready' | 'joining' | 'invalid'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useEffect, useState, useMemo, useRef } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router'
import { useJourneyStore } from '../../store/journeyStore'
import { journeyApi } from '../../api/client'
import { useToast } from '../../components/shared/Toast'
@@ -1,6 +1,6 @@
// FE-JRN-DETHOOK-001 to FE-JRN-DETHOOK-022
import { describe, it, expect, beforeEach, afterEach, vi, type Mock } from 'vitest';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../../tests/helpers/msw/server';
import { render, screen, fireEvent, waitFor } from '../../../tests/helpers/render';
@@ -13,8 +13,8 @@ let routeParams: { id?: string } = { id: '7' };
const mockNavigate = vi.fn();
let mockIsMobile = false;
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useParams: () => routeParams, useNavigate: () => mockNavigate };
});
@@ -1,5 +1,5 @@
import { useEffect, useState, useRef, useCallback, useMemo } from 'react'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { useParams, useNavigate, useSearchParams } from 'react-router'
import { useJourneyStore } from '../../store/journeyStore'
import { useTranslation } from '../../i18n'
import { addListener, removeListener } from '../../api/websocket'
@@ -1,5 +1,5 @@
import { useEffect, useState, useMemo, useRef, useCallback } from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import { journeyApi } from '../../api/client'
import { useSettingsStore } from '../../store/settingsStore'
import type { JourneyMapHandle } from '../../components/Journey/JourneyMap'
+3 -3
View File
@@ -2,7 +2,7 @@
import React from 'react';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { renderHook, act, waitFor } from '../../../tests/helpers/render';
import { MemoryRouter, type MemoryRouterProps } from 'react-router-dom';
import { MemoryRouter, type MemoryRouterProps } from 'react-router';
import { http, HttpResponse } from 'msw';
import { server } from '../../../tests/helpers/msw/server';
import { resetAllStores } from '../../../tests/helpers/store';
@@ -14,8 +14,8 @@ import { startAuthentication } from '@simplewebauthn/browser';
import { useLogin } from './useLogin';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
+1 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect, useMemo, useRef } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import { useNavigate, useLocation } from 'react-router'
import { useAuthStore } from '../../store/authStore'
import { useSettingsStore, hasStoredLanguage } from '../../store/settingsStore'
import { useTranslation, detectBrowserLanguage } from '../../i18n'
@@ -7,7 +7,7 @@ import { useResetPassword } from './useResetPassword';
const navigate = vi.fn();
let search = new URLSearchParams('token=abc123');
vi.mock('react-router-dom', () => ({
vi.mock('react-router', () => ({
useNavigate: () => navigate,
useSearchParams: () => [search],
}));
@@ -1,5 +1,5 @@
import { useState, useEffect, type FormEvent } from 'react'
import { useNavigate, useSearchParams } from 'react-router-dom'
import { useNavigate, useSearchParams } from 'react-router'
import { authApi } from '../../api/client'
import { getApiErrorMessage } from '../../types'
import { useTranslation } from '../../i18n'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useSearchParams } from 'react-router'
import { authApi } from '../../api/client'
import { useAddonStore } from '../../store/addonStore'
+1 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { useParams } from 'react-router'
import { shareApi } from '../../api/client'
import { useExchangeRates } from '../../hooks/useExchangeRates'
@@ -31,7 +31,7 @@ const setSearchParams = vi.fn((updater: (p: URLSearchParams) => URLSearchParams)
searchParams = typeof updater === 'function' ? updater(next) : next
})
vi.mock('react-router-dom', () => ({
vi.mock('react-router', () => ({
useParams: () => routeParams,
useNavigate: () => navigate,
useSearchParams: () => [searchParams, setSearchParams],
@@ -1,5 +1,5 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react'
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
import { useParams, useNavigate, useSearchParams } from 'react-router'
import { useTripStore } from '../../store/tripStore'
import { useCanDo } from '../../store/permissionsStore'
import { useSettingsStore } from '../../store/settingsStore'
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { render, type RenderOptions } from '@testing-library/react';
import { MemoryRouter, type MemoryRouterProps } from 'react-router-dom';
import { MemoryRouter, type MemoryRouterProps } from 'react-router';
import { TranslationProvider } from '../../src/i18n/TranslationContext';
interface RenderWithProvidersOptions extends Omit<RenderOptions, 'wrapper'> {
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { describe, it, expect, beforeEach } from 'vitest';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import { render, screen, fireEvent } from '../../helpers/render';
import MBottomNav from '../../../src/mobile/components/MBottomNav';
import { useAddonStore } from '../../../src/store/addonStore';
@@ -1,6 +1,6 @@
import React from 'react';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { Route, Routes } from 'react-router-dom';
import { Route, Routes } from 'react-router';
import { http, HttpResponse } from 'msw';
import { fireEvent, render, screen, waitFor } from '../../../helpers/render';
import { server } from '../../../helpers/msw/server';
@@ -1,7 +1,7 @@
import React from 'react';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { renderHook, act } from '@testing-library/react';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import { http, HttpResponse } from 'msw';
import { render, screen, fireEvent, waitFor } from '../../../helpers/render';
import { server } from '../../../helpers/msw/server';
@@ -7,8 +7,8 @@ import { useJourneyStore } from '../../../../src/store/journeyStore';
import type { JourneyDetail } from '../../../../src/store/journeyStore';
const mockNavigate = vi.fn();
vi.mock('react-router-dom', async () => {
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
vi.mock('react-router', async () => {
const actual = await vi.importActual<typeof import('react-router')>('react-router');
return { ...actual, useNavigate: () => mockNavigate };
});
@@ -15,8 +15,8 @@ import MSettingsAccount from '../../../../src/mobile/screens/settings/MSettingsA
const navigateMock = vi.fn();
vi.mock('react-router-dom', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-router-dom')>();
vi.mock('react-router', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-router')>();
return { ...actual, useNavigate: () => navigateMock };
});
@@ -1,6 +1,6 @@
// FE-MOB-SETNOTIF-001 onwards
import { describe, it, expect, beforeEach } from 'vitest';
import { useLocation } from 'react-router-dom';
import { useLocation } from 'react-router';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { render, screen, waitFor, within } from '../../../helpers/render';
@@ -11,8 +11,8 @@ import type { VacayEntry, VacayPlan, VacayStat, VacayUser } from '../../../../sr
const navigateMock = vi.fn();
vi.mock('react-router-dom', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-router-dom')>();
vi.mock('react-router', async (importOriginal) => {
const actual = await importOriginal<typeof import('react-router')>();
return { ...actual, useNavigate: () => navigateMock };
});
+42 -42
View File
@@ -50,7 +50,7 @@
"react-leaflet": "^5.0.0",
"react-leaflet-cluster": "^4.1.3",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.22.2",
"react-router": "^7.18.2",
"rehype-sanitize": "^6.0.0",
"remark-breaks": "^4.0.0",
"remark-gfm": "^4.0.1",
@@ -95,6 +95,19 @@
"vitest": "^4.1.9"
}
},
"client/node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"client/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
@@ -108,6 +121,34 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"client/node_modules/react-router": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
"integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"client/node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
},
"client/node_modules/vite": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz",
@@ -5812,15 +5853,6 @@
"react-dom": "^19.0.0"
}
},
"node_modules/@remix-run/router": {
"version": "1.23.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.3.tgz",
"integrity": "sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.2.tgz",
@@ -16849,38 +16881,6 @@
"react": ">=18"
}
},
"node_modules/react-router": {
"version": "6.30.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz",
"integrity": "sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.30.4",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.4.tgz",
"integrity": "sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q==",
"license": "MIT",
"dependencies": {
"@remix-run/router": "1.23.3",
"react-router": "6.30.4"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",