From 9f37a8b29978eaac66c4c3647c806713ee35c9a5 Mon Sep 17 00:00:00 2001 From: paranoidi Date: Sun, 28 Jun 2026 16:04:14 +0300 Subject: [PATCH 01/22] Phase 1: Upgrade CI to Node 20 and Actions v4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Node 12.x (EOL) → 20.x in ci.yml and release.yml - actions/checkout@v2 → @v4 throughout - actions/setup-node@v1 → @v4 with built-in yarn cache (drops separate cache step) - codecov/codecov-action@v1 → @v4 - deliverybot/deployment-status@v1 (archived) → chrnorm/deployment-status@v2 - actions/github-script@0.9.0 → @v7; Octokit API: github.repos → github.rest.repos --- .github/workflows/ci.yml | 19 +++++-------------- .github/workflows/nightly.yml | 6 +++--- .github/workflows/release.yml | 23 ++++++++--------------- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e40c89aa..111c3391 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,30 +14,21 @@ jobs: strategy: matrix: node: - - 12.x + - 20.x steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - name: Cache Dependencies - id: cache-dependencies - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ matrix.node }}-deps-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ matrix.node }}-deps- + cache: 'yarn' - name: Install Dependencies - if: steps.cache-dependencies.outputs.cache-hit != 'true' run: yarn install --frozen-lockfile - name: Run Linter run: yarn lint - name: Run Tests run: yarn test --ci --coverage -i - name: Send to Code Cov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} - diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 72e6c107..b16a458e 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,13 +8,13 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/github-script@0.9.0 + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 name: Create Deployment with: github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} script: | - await github.repos.createDeployment({ + await github.rest.repos.createDeployment({ ...context.repo, ref: context.ref.slice(11), }); diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a5b01f8..ee896c50 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,13 +6,13 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: [12.x] + node: [20.x] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} fetch-depth: 0 - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 name: Checkout Flexget with: ref: develop @@ -28,21 +28,14 @@ jobs: GIT_EMAIL: ${{ secrets.GIT_EMAIL }} GIT_USERNAME: ${{ secrets.GIT_USERNAME }} - name: Use Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node }} - - name: Cache Dependencies - id: cache-dependencies - uses: actions/cache@v2 - with: - path: node_modules - key: ${{ matrix.node }}-deps-${{ hashFiles('yarn.lock') }} - restore-keys: | - ${{ matrix.node }}-deps- + cache: 'yarn' - name: Install Dependencies run: yarn install --frozen-lockfile - name: Set Deployment Status Pending - uses: deliverybot/deployment-status@v1 + uses: chrnorm/deployment-status@v2 with: state: pending token: ${{ github.token }} @@ -55,13 +48,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set Deployment Status Success - uses: deliverybot/deployment-status@v1 + uses: chrnorm/deployment-status@v2 with: state: success token: ${{ github.token }} - name: Set Deployment Status Failure if: failure() || cancelled() - uses: deliverybot/deployment-status@v1 + uses: chrnorm/deployment-status@v2 with: state: failure token: ${{ github.token }} From f574099ac4272d7e31b2adb80c08c2becff9df29 Mon Sep 17 00:00:00 2001 From: paranoidi Date: Sun, 28 Jun 2026 16:12:00 +0300 Subject: [PATCH 02/22] Phase 2: Upgrade TypeScript 5 + Prettier 3, reformat codebase MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - typescript ^4.1 → ^5.5 (installed 5.9.3) - prettier ^2.2 → ^3.0 (installed 3.9.1) - Remove parser: 'typescript' from prettier.config.js (removed in Prettier 3) - Add allowSyntheticDefaultImports: true to tsconfig.json (required explicitly in TS5) - Fix fetch-mock imports: default import → import * as (export= modules need import* in TS5) - Fix duplicate [TraktFields.ID] key in movies.ts (genuine bug, now caught by TS5) - Fix Autocomplete generic constraint: boolean → boolean | undefined - Fix PaperWrapper styled type to accept elevation prop (emotion v10 + TS5 union narrowing) - Reformat all source files with Prettier 3 (trailing-comma, arrow-parens semantics) --- .eslintrc.js | 18 ++-- package.json | 4 +- prettier.config.js | 1 - server.js | 2 +- src/common/inputs/formik/Autocomplete.tsx | 2 +- src/common/styles.tsx | 2 +- src/core/auth/LoginCard.spec.tsx | 2 +- src/core/entry/cards/BaseCard.tsx | 7 +- src/core/entry/cards/Episode.spec.tsx | 2 +- src/core/entry/cards/Movie.spec.tsx | 2 +- src/core/entry/cards/Series.spec.tsx | 2 +- src/core/entry/lookup/movies.ts | 2 +- src/core/entry/types.ts | 2 +- src/core/home/hooks.ts | 23 ++--- src/core/layout/AppBar/index.tsx | 9 +- src/core/layout/Layout.spec.tsx | 2 +- src/core/layout/Layout.tsx | 8 +- src/core/layout/SideNav/Version.spec.tsx | 2 +- src/core/layout/SideNav/index.spec.tsx | 2 +- src/core/layout/SideNav/index.tsx | 21 ++--- src/core/operations/Card.spec.tsx | 2 +- src/core/operations/Operations.spec.tsx | 2 +- src/core/operations/Operations.tsx | 21 +++-- src/plugins/config/Config.spec.tsx | 2 +- src/plugins/config/Editor.tsx | 2 +- src/plugins/history/FilterNav.spec.tsx | 2 +- src/plugins/history/History.spec.tsx | 2 +- src/plugins/history/HistoryList.spec.tsx | 2 +- src/plugins/history/HistoryList.tsx | 8 +- .../lists/base/AddFab/AddEntryDialog.spec.tsx | 2 +- .../lists/base/AddFab/AddListDialog.spec.tsx | 2 +- src/plugins/lists/base/Entries.spec.tsx | 2 +- .../lists/base/EntryList/EntryCard.spec.tsx | 2 +- .../lists/base/EntryList/EntryCard.tsx | 10 +-- .../base/EntryList/InjectEntryDialog.spec.tsx | 2 +- .../base/EntryList/RemoveEntryDialog.spec.tsx | 2 +- .../lists/base/RemoveListDialog.spec.tsx | 2 +- src/plugins/lists/base/hooks/entry.ts | 7 +- src/plugins/lists/entry/EntryList.spec.tsx | 2 +- src/plugins/lists/movies/MovieList.spec.tsx | 2 +- src/plugins/lists/pending/Card.spec.tsx | 2 +- .../lists/pending/PendingList.spec.tsx | 2 +- .../series/Episodes/EpisodeCard.spec.tsx | 2 +- src/plugins/series/Episodes/Releases.spec.tsx | 2 +- src/plugins/series/Episodes/index.spec.tsx | 2 +- src/plugins/series/RemoveDialog.spec.tsx | 2 +- src/plugins/series/Series.spec.tsx | 2 +- .../series/Shows/AddShowDialog.spec.tsx | 2 +- src/plugins/series/Shows/ShowCard.spec.tsx | 2 +- src/plugins/series/Shows/index.spec.tsx | 2 +- src/plugins/tasks/Card.spec.tsx | 2 +- src/plugins/tasks/Tasks.spec.tsx | 2 +- src/types/globals.d.ts | 7 +- src/utils/encoding.ts | 84 +++++++++---------- tsconfig.json | 1 + webpack.config.js | 2 +- yarn.lock | 16 ++-- 57 files changed, 167 insertions(+), 160 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0a03fd65..a97380ae 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { 'airbnb-typescript', 'plugin:jest-dom/recommended', 'plugin:testing-library/recommended', - "plugin:@typescript-eslint/eslint-recommended", + 'plugin:@typescript-eslint/eslint-recommended', // "plugin:@typescript-eslint/recommended", // "plugin:@typescript-eslint/recommended-requiring-type-checking", 'plugin:prettier/recommended', @@ -62,12 +62,16 @@ module.exports = { 'emotion/styled-import': 'error', // import - 'import/extensions': ['error', 'ignorePackages', { - ts: 'never', - tsx: 'never', - js: 'never', - jsx: 'never', - }], + 'import/extensions': [ + 'error', + 'ignorePackages', + { + ts: 'never', + tsx: 'never', + js: 'never', + jsx: 'never', + }, + ], 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 'import/no-named-as-default': 'off', 'import/prefer-default-export': 'off', diff --git a/package.json b/package.json index 8acabc8c..ba8edc78 100644 --- a/package.json +++ b/package.json @@ -99,13 +99,13 @@ "jest-emotion": "^10.0.27", "jest-extended": "^0.11.5", "mini-css-extract-plugin": "^0.9.0", - "prettier": "^2.2.1", + "prettier": "^3.0.0", "raf": "^3.4.1", "react-test-renderer": "^16.12.0", "style-loader": "^1.1.3", "ts-jest": "^25.1.0", "ts-loader": "^6.0.4", - "typescript": "^4.1.2", + "typescript": "^5.5.0", "url-loader": "^3.0.0", "webpack": "^4.36.1", "webpack-bundle-analyzer": "^3.3.2", diff --git a/prettier.config.js b/prettier.config.js index 434571f3..187ae54e 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -2,6 +2,5 @@ module.exports = { trailingComma: 'all', singleQuote: true, printWidth: 100, - parser: 'typescript', arrowParens: 'avoid', }; diff --git a/server.js b/server.js index 8fce4594..20616820 100755 --- a/server.js +++ b/server.js @@ -16,7 +16,7 @@ const server = new WebpackDevServer(webpack(config), { }, }); -server.listen(process.env.PORT || 8000, process.env.HOST || '0.0.0.0', (err) => { +server.listen(process.env.PORT || 8000, process.env.HOST || '0.0.0.0', err => { if (err) { console.log(err); } diff --git a/src/common/inputs/formik/Autocomplete.tsx b/src/common/inputs/formik/Autocomplete.tsx index 91247c0b..f371cc32 100644 --- a/src/common/inputs/formik/Autocomplete.tsx +++ b/src/common/inputs/formik/Autocomplete.tsx @@ -8,7 +8,7 @@ import { } from '@material-ui/lab'; import { TextFieldProps, TextField } from '@material-ui/core'; -type Props = Omit, 'renderInput'> & +type Props = Omit, 'renderInput'> & (Multiple extends true ? UseAutocompleteMultipleProps : UseAutocompleteSingleProps) & { name: string; InputProps: TextFieldProps; diff --git a/src/common/styles.tsx b/src/common/styles.tsx index 5bf3b36c..751488e5 100644 --- a/src/common/styles.tsx +++ b/src/common/styles.tsx @@ -6,7 +6,7 @@ import { Paper, Theme } from '@material-ui/core'; const styled = st as CreateStyled; -export const PaperWrapper = styled(Paper)` +export const PaperWrapper = styled(Paper)<{ elevation?: number }>` padding: 2.4rem; display: flex; flex: 1; diff --git a/src/core/auth/LoginCard.spec.tsx b/src/core/auth/LoginCard.spec.tsx index a55cb2e4..83818206 100644 --- a/src/core/auth/LoginCard.spec.tsx +++ b/src/core/auth/LoginCard.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { cleanup, fireEvent } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { AuthContainer } from 'core/auth/hooks'; import { renderWithWrapper } from 'utils/tests'; import LoginCard from './LoginCard'; diff --git a/src/core/entry/cards/BaseCard.tsx b/src/core/entry/cards/BaseCard.tsx index 5f40ed6c..897029fe 100644 --- a/src/core/entry/cards/BaseCard.tsx +++ b/src/core/entry/cards/BaseCard.tsx @@ -62,9 +62,10 @@ const BaseCard: FC = ({ loading = false, }) => { const imageCss = useCallback((theme: Theme) => [image, isPoster && poster(theme)], [isPoster]); - const textCss = useCallback((theme: Theme) => [text, isPoster && posterSkeletonWrapper(theme)], [ - isPoster, - ]); + const textCss = useCallback( + (theme: Theme) => [text, isPoster && posterSkeletonWrapper(theme)], + [isPoster], + ); const summaryCss = useCallback( (theme: Theme) => [summary(theme), isPoster && posterSkeletonWrapper(theme)], [isPoster], diff --git a/src/core/entry/cards/Episode.spec.tsx b/src/core/entry/cards/Episode.spec.tsx index fc6b50e5..6bc0765c 100644 --- a/src/core/entry/cards/Episode.spec.tsx +++ b/src/core/entry/cards/Episode.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { compose } from 'utils'; import { renderWithWrapper } from 'utils/tests'; import { cleanup } from '@testing-library/react'; diff --git a/src/core/entry/cards/Movie.spec.tsx b/src/core/entry/cards/Movie.spec.tsx index ab5b3b80..b7d51449 100644 --- a/src/core/entry/cards/Movie.spec.tsx +++ b/src/core/entry/cards/Movie.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { compose } from 'utils'; import { normalizeMinutes } from 'utils/time'; import { renderWithWrapper } from 'utils/tests'; diff --git a/src/core/entry/cards/Series.spec.tsx b/src/core/entry/cards/Series.spec.tsx index 42bc8bf8..45400153 100644 --- a/src/core/entry/cards/Series.spec.tsx +++ b/src/core/entry/cards/Series.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { compose } from 'utils'; import { renderWithWrapper } from 'utils/tests'; import { diff --git a/src/core/entry/lookup/movies.ts b/src/core/entry/lookup/movies.ts index 4d2198f4..fc6138c9 100644 --- a/src/core/entry/lookup/movies.ts +++ b/src/core/entry/lookup/movies.ts @@ -145,7 +145,7 @@ const traktToFields = (movie: TraktMovie): RawMovieFields => ({ [TraktFields.ID]: movie.id, [TraktFields.Description]: movie.overview, [IMDBFields.ID]: movie.imdbId, - [TraktFields.ID]: movie.tmdbId, + [TMDBFields.ID]: movie.tmdbId, }); export const useTraktLookup = (options: TraktOptions) => { diff --git a/src/core/entry/types.ts b/src/core/entry/types.ts index dd0bbce9..8ebfe4bb 100644 --- a/src/core/entry/types.ts +++ b/src/core/entry/types.ts @@ -21,7 +21,7 @@ type ArrayPairs>> = { export type Fields< U extends string, T extends ReadonlyArray>, - W extends Partial> + W extends Partial>, > = { readonly [P in Extract[1], string>]?: W[Extract, [any, P]>[0]]; }; diff --git a/src/core/home/hooks.ts b/src/core/home/hooks.ts index ebe69c84..eb5018ff 100644 --- a/src/core/home/hooks.ts +++ b/src/core/home/hooks.ts @@ -7,17 +7,18 @@ export const useGetHomeCards = () => { const { pluginMap } = useContainer(PluginContainer); const cards: CardInfo[] = useMemo( () => - Object.entries(pluginMap).flatMap(([path, { icon, component, cardComponent, displayName }]) => - cardComponent - ? [ - { - path: component && path, - Component: cardComponent, - name: displayName, - Icon: icon, - }, - ] - : [], + Object.entries(pluginMap).flatMap( + ([path, { icon, component, cardComponent, displayName }]) => + cardComponent + ? [ + { + path: component && path, + Component: cardComponent, + name: displayName, + Icon: icon, + }, + ] + : [], ), [pluginMap], ); diff --git a/src/core/layout/AppBar/index.tsx b/src/core/layout/AppBar/index.tsx index fd599f97..68e31c05 100644 --- a/src/core/layout/AppBar/index.tsx +++ b/src/core/layout/AppBar/index.tsx @@ -70,11 +70,10 @@ const AppBar: FC = ({ toggleSidebar, className }) => { setContextual(false); }, [contextualProps, setContextual]); - const menuClick = useMemo(() => (contextualMode ? handleContextualClose : icon.onClick), [ - contextualMode, - handleContextualClose, - icon.onClick, - ]); + const menuClick = useMemo( + () => (contextualMode ? handleContextualClose : icon.onClick), + [contextualMode, handleContextualClose, icon.onClick], + ); const menuLabel = contextualMode ? 'close context' : icon.label; const [mode, toggleMode] = useContainer(ThemeContainer); diff --git a/src/core/layout/Layout.spec.tsx b/src/core/layout/Layout.spec.tsx index e2d2304b..bfb22b0d 100644 --- a/src/core/layout/Layout.spec.tsx +++ b/src/core/layout/Layout.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { act, create, ReactTestRenderer } from 'react-test-renderer'; import { BaseProviders } from 'utils/tests'; import Layout from './Layout'; diff --git a/src/core/layout/Layout.tsx b/src/core/layout/Layout.tsx index b1ad99da..03b2c532 100644 --- a/src/core/layout/Layout.tsx +++ b/src/core/layout/Layout.tsx @@ -25,10 +25,10 @@ const Layout: React.FC = ({ children }) => { const sideTransition = sidebarOpen ? enterTransition : leavingTransition; const mainTransition = sidebarOpen ? leavingTransition : enterTransition; - const contentCss = useMemo(() => [content(theme), mainTransition(theme)], [ - mainTransition, - theme, - ]); + const contentCss = useMemo( + () => [content(theme), mainTransition(theme)], + [mainTransition, theme], + ); return ( diff --git a/src/core/layout/SideNav/Version.spec.tsx b/src/core/layout/SideNav/Version.spec.tsx index 1ce1b7b9..9027a9bd 100644 --- a/src/core/layout/SideNav/Version.spec.tsx +++ b/src/core/layout/SideNav/Version.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { cleanup } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import Version from './Version'; diff --git a/src/core/layout/SideNav/index.spec.tsx b/src/core/layout/SideNav/index.spec.tsx index bb3dbd00..2cf37131 100644 --- a/src/core/layout/SideNav/index.spec.tsx +++ b/src/core/layout/SideNav/index.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { themed, router } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import SideNav from './index'; describe('core/layout/SideNav', () => { diff --git a/src/core/layout/SideNav/index.tsx b/src/core/layout/SideNav/index.tsx index 45e7037f..879c7053 100644 --- a/src/core/layout/SideNav/index.tsx +++ b/src/core/layout/SideNav/index.tsx @@ -41,20 +41,21 @@ const SideNav: FC = ({ sidebarOpen = false, onClose, className }) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('xs')); - const drawerCss = useMemo(() => (sidebarOpen ? drawerOpen(theme) : drawerClose(theme)), [ - sidebarOpen, - theme, - ]); + const drawerCss = useMemo( + () => (sidebarOpen ? drawerOpen(theme) : drawerClose(theme)), + [sidebarOpen, theme], + ); const drawerRootCss = useMemo(() => [drawer(theme), drawerCss], [drawerCss, theme]); const handleClick = useCallback( - ({ path }: Route) => () => { - if (isMobile) { - onClose(); - } - history.push(path); - }, + ({ path }: Route) => + () => { + if (isMobile) { + onClose(); + } + history.push(path); + }, [history, isMobile, onClose], ); diff --git a/src/core/operations/Card.spec.tsx b/src/core/operations/Card.spec.tsx index cdfa2ddd..1037ac68 100644 --- a/src/core/operations/Card.spec.tsx +++ b/src/core/operations/Card.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { cleanup, fireEvent } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import Card from './Card'; diff --git a/src/core/operations/Operations.spec.tsx b/src/core/operations/Operations.spec.tsx index 676f1484..c3b187b7 100644 --- a/src/core/operations/Operations.spec.tsx +++ b/src/core/operations/Operations.spec.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { cleanup, fireEvent, RenderResult } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import InfoStatus from 'core/status/InfoStatus'; diff --git a/src/core/operations/Operations.tsx b/src/core/operations/Operations.tsx index f8a1d414..c0c0ef52 100644 --- a/src/core/operations/Operations.tsx +++ b/src/core/operations/Operations.tsx @@ -35,17 +35,20 @@ const Operations: FC = ({ open = false, onClose }) => { const { loading: pluginsLoading, plugins } = useGetPlugins(); const [pluginName, setPluginName] = useState(''); - const options = useMemo(() => plugins.map(plugin => ({ label: plugin, value: plugin })), [ - plugins, - ]); + const options = useMemo( + () => plugins.map(plugin => ({ label: plugin, value: plugin })), + [plugins], + ); - const vacuum = useCallback(() => performOperation({ operation: DatabaseOperation.Vacuum }), [ - performOperation, - ]); + const vacuum = useCallback( + () => performOperation({ operation: DatabaseOperation.Vacuum }), + [performOperation], + ); - const cleanup = useCallback(() => performOperation({ operation: DatabaseOperation.Cleanup }), [ - performOperation, - ]); + const cleanup = useCallback( + () => performOperation({ operation: DatabaseOperation.Cleanup }), + [performOperation], + ); const resetPLugin = useCallback( () => performOperation({ operation: DatabaseOperation.PluginReset, pluginName }), diff --git a/src/plugins/config/Config.spec.tsx b/src/plugins/config/Config.spec.tsx index e2596247..2280553c 100644 --- a/src/plugins/config/Config.spec.tsx +++ b/src/plugins/config/Config.spec.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { cleanup, fireEvent, wait } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; diff --git a/src/plugins/config/Editor.tsx b/src/plugins/config/Editor.tsx index faaa1591..707b3ce3 100644 --- a/src/plugins/config/Editor.tsx +++ b/src/plugins/config/Editor.tsx @@ -28,7 +28,7 @@ interface Props { schemas?: Schema[]; } -const { yaml } = ((languages ?? {}) as unknown) as { yaml?: YamlLanguage }; +const { yaml } = (languages ?? {}) as unknown as { yaml?: YamlLanguage }; const Editor: FC = ({ name, schemas }) => { const options: editor.IStandaloneEditorConstructionOptions = { diff --git a/src/plugins/history/FilterNav.spec.tsx b/src/plugins/history/FilterNav.spec.tsx index 9c4bb4b3..817d1ef3 100644 --- a/src/plugins/history/FilterNav.spec.tsx +++ b/src/plugins/history/FilterNav.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { Formik } from 'formik'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import renderer from 'react-test-renderer'; import { Direction } from 'utils/query'; import { BaseProviders } from 'utils/tests'; diff --git a/src/plugins/history/History.spec.tsx b/src/plugins/history/History.spec.tsx index 6f705182..7af551c2 100644 --- a/src/plugins/history/History.spec.tsx +++ b/src/plugins/history/History.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import { TaskContainer } from 'plugins/tasks/hooks'; diff --git a/src/plugins/history/HistoryList.spec.tsx b/src/plugins/history/HistoryList.spec.tsx index 654350da..613c2605 100644 --- a/src/plugins/history/HistoryList.spec.tsx +++ b/src/plugins/history/HistoryList.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import { Direction } from 'utils/query'; import HistoryList from './HistoryList'; diff --git a/src/plugins/history/HistoryList.tsx b/src/plugins/history/HistoryList.tsx index 8a33c5cd..618e7f94 100644 --- a/src/plugins/history/HistoryList.tsx +++ b/src/plugins/history/HistoryList.tsx @@ -23,10 +23,10 @@ interface Props { const HistoryList = forwardRef(({ options, loadMore }, scrollRef) => { const { items, total, loading } = useHistoryPlugin(options); - const history = useMemo(() => getGroupedHistory(items, options.grouping), [ - items, - options.grouping, - ]); + const history = useMemo( + () => getGroupedHistory(items, options.grouping), + [items, options.grouping], + ); return (
diff --git a/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx b/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx index dc88f157..1fee1e7a 100644 --- a/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx +++ b/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent, within, wait } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; import { makeRawEntry } from 'core/entry/fixtures'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { PendingListContainer } from 'plugins/lists/pending/hooks'; import { EntryListContainer } from 'plugins/lists/entry/hooks'; import { MovieListContainer } from 'plugins/lists/movies/hooks'; diff --git a/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx b/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx index 964c2134..a36fb241 100644 --- a/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx +++ b/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx @@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react'; import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent, within, wait } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { PendingListContainer } from 'plugins/lists/pending/hooks'; import { EntryListContainer } from 'plugins/lists/entry/hooks'; import { MovieListContainer } from 'plugins/lists/movies/hooks'; diff --git a/src/plugins/lists/base/Entries.spec.tsx b/src/plugins/lists/base/Entries.spec.tsx index 3c1449b8..836d2235 100644 --- a/src/plugins/lists/base/Entries.spec.tsx +++ b/src/plugins/lists/base/Entries.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup, fireEvent, within, getNodeText, wait } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import { TaskContainer } from 'plugins/tasks/hooks'; diff --git a/src/plugins/lists/base/EntryList/EntryCard.spec.tsx b/src/plugins/lists/base/EntryList/EntryCard.spec.tsx index 813c1a78..2ebcd89a 100644 --- a/src/plugins/lists/base/EntryList/EntryCard.spec.tsx +++ b/src/plugins/lists/base/EntryList/EntryCard.spec.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; import { makeRawEntry } from 'core/entry/fixtures'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { EntryListContainer } from 'plugins/lists/entry/hooks'; import { PendingListContainer } from 'plugins/lists/pending/hooks'; import { MovieListContainer } from 'plugins/lists/movies/hooks'; diff --git a/src/plugins/lists/base/EntryList/EntryCard.tsx b/src/plugins/lists/base/EntryList/EntryCard.tsx index 3a42edb0..f3c166a5 100644 --- a/src/plugins/lists/base/EntryList/EntryCard.tsx +++ b/src/plugins/lists/base/EntryList/EntryCard.tsx @@ -76,12 +76,10 @@ const EntryCard: FC = ({ entry, onInjectClick, onRemoveClick }) => { const selected = selectedIds.has(entry.id); - const toggle = useCallback(() => (selected ? unselectEntry(entry.id) : selectEntry(entry.id)), [ - entry.id, - selectEntry, - selected, - unselectEntry, - ]); + const toggle = useCallback( + () => (selected ? unselectEntry(entry.id) : selectEntry(entry.id)), + [entry.id, selectEntry, selected, unselectEntry], + ); const checkCircleStyles = useCallback( (theme: Theme) => [circleIcon(theme), selected && circleIconVisible], diff --git a/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx b/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx index 2d8eaa9f..94b33fcf 100644 --- a/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx +++ b/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent, within, wait } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; import { makeRawEntry } from 'core/entry/fixtures'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { PendingListContainer } from 'plugins/lists/pending/hooks'; import { EntryListContainer } from 'plugins/lists/entry/hooks'; import { MovieListContainer } from 'plugins/lists/movies/hooks'; diff --git a/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx b/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx index 0b966e66..9d98a7b4 100644 --- a/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx +++ b/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent, within, wait } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; import { makeRawEntry } from 'core/entry/fixtures'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { PendingListContainer } from 'plugins/lists/pending/hooks'; import { EntryListContainer } from 'plugins/lists/entry/hooks'; import { MovieListContainer } from 'plugins/lists/movies/hooks'; diff --git a/src/plugins/lists/base/RemoveListDialog.spec.tsx b/src/plugins/lists/base/RemoveListDialog.spec.tsx index ac7e0cda..cd0acaeb 100644 --- a/src/plugins/lists/base/RemoveListDialog.spec.tsx +++ b/src/plugins/lists/base/RemoveListDialog.spec.tsx @@ -2,7 +2,7 @@ import React, { FC, useEffect } from 'react'; import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent, within, wait } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import RemoveListDialog from './RemoveListDialog'; import { ListContainer, actions } from './hooks/list'; import { PendingListContainer } from '../pending/hooks'; diff --git a/src/plugins/lists/base/hooks/entry.ts b/src/plugins/lists/base/hooks/entry.ts index c61aaa49..d7766599 100644 --- a/src/plugins/lists/base/hooks/entry.ts +++ b/src/plugins/lists/base/hooks/entry.ts @@ -275,9 +275,10 @@ export const useEntryBulkSelect = () => { const [{ selectedIds }, dispatch] = useContainer(EntryContainer); const selectEntry = useCallback((id: number) => dispatch(actions.selectEntry(id)), [dispatch]); - const unselectEntry = useCallback((id: number) => dispatch(actions.unselectEntry(id)), [ - dispatch, - ]); + const unselectEntry = useCallback( + (id: number) => dispatch(actions.unselectEntry(id)), + [dispatch], + ); const clearSelected = useCallback(() => dispatch(actions.clearSelected()), [dispatch]); diff --git a/src/plugins/lists/entry/EntryList.spec.tsx b/src/plugins/lists/entry/EntryList.spec.tsx index 531e8ae8..bd4e6120 100644 --- a/src/plugins/lists/entry/EntryList.spec.tsx +++ b/src/plugins/lists/entry/EntryList.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import { TaskContainer } from 'plugins/tasks/hooks'; diff --git a/src/plugins/lists/movies/MovieList.spec.tsx b/src/plugins/lists/movies/MovieList.spec.tsx index 1f787aab..dd0595e9 100644 --- a/src/plugins/lists/movies/MovieList.spec.tsx +++ b/src/plugins/lists/movies/MovieList.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import { TaskContainer } from 'plugins/tasks/hooks'; diff --git a/src/plugins/lists/pending/Card.spec.tsx b/src/plugins/lists/pending/Card.spec.tsx index 30925a17..73855fbc 100644 --- a/src/plugins/lists/pending/Card.spec.tsx +++ b/src/plugins/lists/pending/Card.spec.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next'; import { cleanup, render, fireEvent } from '@testing-library/react'; import { BaseProviders } from 'utils/tests'; import { makeRawEntry } from 'core/entry/fixtures'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import EntryCard from '../base/EntryList/EntryCard'; import { ListContainer, actions } from '../base/hooks/list'; import { EntryContainer } from '../base/hooks/entry'; diff --git a/src/plugins/lists/pending/PendingList.spec.tsx b/src/plugins/lists/pending/PendingList.spec.tsx index fdf98ce8..ed4be997 100644 --- a/src/plugins/lists/pending/PendingList.spec.tsx +++ b/src/plugins/lists/pending/PendingList.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import { TaskContainer } from 'plugins/tasks/hooks'; diff --git a/src/plugins/series/Episodes/EpisodeCard.spec.tsx b/src/plugins/series/Episodes/EpisodeCard.spec.tsx index b0ea9f81..595ac572 100644 --- a/src/plugins/series/Episodes/EpisodeCard.spec.tsx +++ b/src/plugins/series/Episodes/EpisodeCard.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cleanup, fireEvent } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import EpisodeCard from './EpisodeCard'; import { Episode, Show, IdentifiedBy } from '../types'; import { EpisodeContainer } from '../hooks/episodes'; diff --git a/src/plugins/series/Episodes/Releases.spec.tsx b/src/plugins/series/Episodes/Releases.spec.tsx index 75128ea6..ce6135be 100644 --- a/src/plugins/series/Episodes/Releases.spec.tsx +++ b/src/plugins/series/Episodes/Releases.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import Releases from './Releases'; import { ReleaseContainer } from '../hooks/releases'; diff --git a/src/plugins/series/Episodes/index.spec.tsx b/src/plugins/series/Episodes/index.spec.tsx index 66a896b9..57fa91ac 100644 --- a/src/plugins/series/Episodes/index.spec.tsx +++ b/src/plugins/series/Episodes/index.spec.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect } from 'react'; import { useHistory, Route } from 'react-router-dom'; import { cleanup, fireEvent, getNodeText, wait } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import Episodes from './index'; diff --git a/src/plugins/series/RemoveDialog.spec.tsx b/src/plugins/series/RemoveDialog.spec.tsx index 64dc7113..cee208e8 100644 --- a/src/plugins/series/RemoveDialog.spec.tsx +++ b/src/plugins/series/RemoveDialog.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { cleanup, fireEvent, within, wait } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import RemoveDialog from './RemoveDialog'; import { EpisodeContainer, useRemoveEpisode } from './hooks/episodes'; diff --git a/src/plugins/series/Series.spec.tsx b/src/plugins/series/Series.spec.tsx index 77b52c93..fe124de0 100644 --- a/src/plugins/series/Series.spec.tsx +++ b/src/plugins/series/Series.spec.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect } from 'react'; import { cleanup } from '@testing-library/react'; import { useHistory, Route, Switch } from 'react-router'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import Series from './Series'; diff --git a/src/plugins/series/Shows/AddShowDialog.spec.tsx b/src/plugins/series/Shows/AddShowDialog.spec.tsx index 5a1807f3..68835aa4 100644 --- a/src/plugins/series/Shows/AddShowDialog.spec.tsx +++ b/src/plugins/series/Shows/AddShowDialog.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cleanup, fireEvent, within, wait } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import AddShowDialog from './AddShowDialog'; import { Show } from '../types'; import { ShowContainer } from '../hooks/shows'; diff --git a/src/plugins/series/Shows/ShowCard.spec.tsx b/src/plugins/series/Shows/ShowCard.spec.tsx index 93941e24..f1fe4bbc 100644 --- a/src/plugins/series/Shows/ShowCard.spec.tsx +++ b/src/plugins/series/Shows/ShowCard.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { cleanup, fireEvent } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import ShowCard from './ShowCard'; import { Show } from '../types'; import { ShowContainer } from '../hooks/shows'; diff --git a/src/plugins/series/Shows/index.spec.tsx b/src/plugins/series/Shows/index.spec.tsx index d415df71..4bbd271e 100644 --- a/src/plugins/series/Shows/index.spec.tsx +++ b/src/plugins/series/Shows/index.spec.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { cleanup, fireEvent, getNodeText, wait } from '@testing-library/react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import Shows from './index'; diff --git a/src/plugins/tasks/Card.spec.tsx b/src/plugins/tasks/Card.spec.tsx index 43c739dc..67dd6f53 100644 --- a/src/plugins/tasks/Card.spec.tsx +++ b/src/plugins/tasks/Card.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { cleanup } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import Card from './Card'; diff --git a/src/plugins/tasks/Tasks.spec.tsx b/src/plugins/tasks/Tasks.spec.tsx index e29bfec7..566878cb 100644 --- a/src/plugins/tasks/Tasks.spec.tsx +++ b/src/plugins/tasks/Tasks.spec.tsx @@ -1,7 +1,7 @@ import React, { FC, useEffect } from 'react'; import { cleanup } from '@testing-library/react'; import { useHistory, Route, Switch } from 'react-router'; -import fetchMock from 'fetch-mock'; +import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; import Tasks from './Tasks'; diff --git a/src/types/globals.d.ts b/src/types/globals.d.ts index 2b4d2d18..39ca103c 100644 --- a/src/types/globals.d.ts +++ b/src/types/globals.d.ts @@ -15,10 +15,9 @@ declare global { type SetState = Dispatch>; - type OptionalProps = Omit & - { - [P in K]?: T[P]; - }; + type OptionalProps = Omit & { + [P in K]?: T[P]; + }; type PropReturnType> = ReturnType; } diff --git a/src/utils/encoding.ts b/src/utils/encoding.ts index 0f71a444..950a343f 100644 --- a/src/utils/encoding.ts +++ b/src/utils/encoding.ts @@ -7,14 +7,14 @@ function b64ToUint6(nChr: number) { return nChr > 64 && nChr < 91 ? nChr - 65 : nChr > 96 && nChr < 123 - ? nChr - 71 - : nChr > 47 && nChr < 58 - ? nChr + 4 - : nChr === 43 - ? 62 - : nChr === 47 - ? 63 - : 0; + ? nChr - 71 + : nChr > 47 && nChr < 58 + ? nChr + 4 + : nChr === 43 + ? 62 + : nChr === 47 + ? 63 + : 0; } function base64DecToArr(sBase64: string, nBlockSize?: number) { @@ -49,14 +49,14 @@ function uint6ToB64(nUint6: number) { return nUint6 < 26 ? nUint6 + 65 : nUint6 < 52 - ? nUint6 + 71 - : nUint6 < 62 - ? nUint6 - 4 - : nUint6 === 62 - ? 43 - : nUint6 === 63 - ? 47 - : 65; + ? nUint6 + 71 + : nUint6 < 62 + ? nUint6 - 4 + : nUint6 === 62 + ? 43 + : nUint6 === 63 + ? 47 + : 65; } function base64EncArr(aBytes: Uint8Array) { @@ -100,24 +100,24 @@ function UTF8ArrToStr(aBytes: Uint8Array) { aBytes[++nIdx] - 128 : nPart > 247 && nPart < 252 && nIdx + 4 < nLen /* five bytes */ - ? ((nPart - 248) << 24) + - ((aBytes[++nIdx] - 128) << 18) + - ((aBytes[++nIdx] - 128) << 12) + - ((aBytes[++nIdx] - 128) << 6) + - aBytes[++nIdx] - - 128 - : nPart > 239 && nPart < 248 && nIdx + 3 < nLen /* four bytes */ - ? ((nPart - 240) << 18) + - ((aBytes[++nIdx] - 128) << 12) + - ((aBytes[++nIdx] - 128) << 6) + - aBytes[++nIdx] - - 128 - : nPart > 223 && nPart < 240 && nIdx + 2 < nLen /* three bytes */ - ? ((nPart - 224) << 12) + ((aBytes[++nIdx] - 128) << 6) + aBytes[++nIdx] - 128 - : nPart > 191 && nPart < 224 && nIdx + 1 < nLen /* two bytes */ - ? ((nPart - 192) << 6) + aBytes[++nIdx] - 128 /* nPart < 127 ? */ - : /* one byte */ - nPart, + ? ((nPart - 248) << 24) + + ((aBytes[++nIdx] - 128) << 18) + + ((aBytes[++nIdx] - 128) << 12) + + ((aBytes[++nIdx] - 128) << 6) + + aBytes[++nIdx] - + 128 + : nPart > 239 && nPart < 248 && nIdx + 3 < nLen /* four bytes */ + ? ((nPart - 240) << 18) + + ((aBytes[++nIdx] - 128) << 12) + + ((aBytes[++nIdx] - 128) << 6) + + aBytes[++nIdx] - + 128 + : nPart > 223 && nPart < 240 && nIdx + 2 < nLen /* three bytes */ + ? ((nPart - 224) << 12) + ((aBytes[++nIdx] - 128) << 6) + aBytes[++nIdx] - 128 + : nPart > 191 && nPart < 224 && nIdx + 1 < nLen /* two bytes */ + ? ((nPart - 192) << 6) + aBytes[++nIdx] - 128 /* nPart < 127 ? */ + : /* one byte */ + nPart, ); } @@ -136,14 +136,14 @@ function strToUTF8Arr(sDOMStr: string) { nChr < 0x80 ? 1 : nChr < 0x800 - ? 2 - : nChr < 0x10000 - ? 3 - : nChr < 0x200000 - ? 4 - : nChr < 0x4000000 - ? 5 - : 6; + ? 2 + : nChr < 0x10000 + ? 3 + : nChr < 0x200000 + ? 4 + : nChr < 0x4000000 + ? 5 + : 6; } const aBytes = new Uint8Array(nArrLen); diff --git a/tsconfig.json b/tsconfig.json index 3849f566..e05cbb24 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "dom.iterable" ], "esModuleInterop": true, + "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "experimentalDecorators": true }, diff --git a/webpack.config.js b/webpack.config.js index 49195551..5c227b51 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,7 +4,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); const webpack = require('webpack'); -const cfg = require('./package.json') +const cfg = require('./package.json'); const __DEBUG__ = !!process.env.DEBUG; const __DEV__ = process.env.NODE_ENV !== 'production'; diff --git a/yarn.lock b/yarn.lock index bfb628d9..aba420e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8598,10 +8598,10 @@ prettier@^1.19.1: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -prettier@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" - integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q== +prettier@^3.0.0: + version "3.9.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.9.1.tgz#5868bcf16456bfe145ddbfa107c623b6f39e332c" + integrity sha512-ppiDo2CSwexck1eyZUwJHg/N3nf1+6IRCv7W/VJ5vaLnVCmB7+3CdRfMwoCHBBX6xTrREDTksZ4OZl5SSf4zXA== pretty-error@^2.0.2: version "2.1.1" @@ -10463,10 +10463,10 @@ typeface-roboto@0.0.75: resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.75.tgz#98d5ba35ec234bbc7172374c8297277099cc712b" integrity sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg== -typescript@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" - integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== +typescript@^5.5.0: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== uglify-js@3.4.x: version "3.4.10" From 6b2bd19688136745db6088c23da0d96b29a1a319 Mon Sep 17 00:00:00 2001 From: paranoidi Date: Sun, 28 Jun 2026 16:31:25 +0300 Subject: [PATCH 03/22] Phase 3+6: Upgrade React 18 + React Router v6, remove react-hot-loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Combined because Router v5 types are incompatible with @types/react@18. - React 18: createRoot, explicit children props on FC, @types/react pinned to ~18.2 - Formik bumped to 2.4.9 for React 18 type compat - react-hot-loader + @hot-loader/react-dom removed from 10 files + webpack config - Router v6: Switch→Routes, component→element, useHistory→useNavigate, useRouteMatch→useHref, Redirect→Navigate, PrivateRoute rewritten - Emotion v10 and react-virtualized/react-monaco-editor styled components cast to FC as interim bridge; will be replaced in Phase 5 and Phase 7 --- package.json | 19 +- src/Root.tsx | 65 ++-- src/app.tsx | 6 +- src/common/LoadingSpinner.tsx | 1 + src/common/inputs/formik/Checkbox.tsx | 4 +- src/common/styles.tsx | 9 +- src/core/auth/Login.tsx | 12 +- src/core/entry/cards/BaseCard.tsx | 1 + src/core/entry/cards/Episode.tsx | 1 + src/core/entry/cards/Movie.tsx | 1 + src/core/entry/cards/Series.tsx | 1 + src/core/entry/cards/index.tsx | 1 + src/core/home/index.tsx | 3 +- src/core/layout/Layout.tsx | 2 +- src/core/layout/SideNav/index.tsx | 8 +- src/core/routes/PrivateRoute.tsx | 29 +- src/core/routes/Routes.spec.tsx | 26 +- src/core/routes/Routes.tsx | 21 +- src/core/theme/ThemeProvider.tsx | 4 +- src/plugins/config/Config.tsx | 3 +- src/plugins/config/Editor.tsx | 4 +- src/plugins/history/History.tsx | 3 +- .../lists/base/AddFab/AddEntryDialog.spec.tsx | 2 +- .../lists/base/AddFab/AddListDialog.spec.tsx | 2 +- .../lists/base/EntryList/EntryCard.spec.tsx | 2 +- .../base/EntryList/InjectEntryDialog.spec.tsx | 2 +- .../base/EntryList/RemoveEntryDialog.spec.tsx | 2 +- .../lists/base/RemoveListDialog.spec.tsx | 2 +- src/plugins/lists/entry/EntryList.tsx | 3 +- src/plugins/lists/movies/MovieList.tsx | 3 +- src/plugins/lists/pending/Card.spec.tsx | 2 +- src/plugins/lists/pending/PendingList.tsx | 3 +- src/plugins/log/Log.tsx | 3 +- src/plugins/log/LogTable.tsx | 6 +- src/plugins/series/Episodes/index.spec.tsx | 14 +- src/plugins/series/Episodes/index.tsx | 14 +- src/plugins/series/Series.spec.tsx | 16 +- src/plugins/series/Series.tsx | 21 +- src/plugins/series/Shows/ShowCard.tsx | 6 +- src/plugins/tasks/Latest.tsx | 10 +- src/plugins/tasks/TaskExecutions.tsx | 14 +- src/plugins/tasks/TaskTable.tsx | 2 +- src/plugins/tasks/Tasks.spec.tsx | 16 +- src/plugins/tasks/Tasks.tsx | 21 +- src/utils/tests/index.tsx | 2 +- webpack.config.js | 6 +- yarn.lock | 328 +++++++----------- 47 files changed, 299 insertions(+), 427 deletions(-) diff --git a/package.json b/package.json index ba8edc78..59b35a31 100644 --- a/package.json +++ b/package.json @@ -55,9 +55,9 @@ "@types/node-fetch": "^2.5.4", "@types/oboe": "^2.0.28", "@types/qs": "^6.5.3", - "@types/react": "^16.9.16", - "@types/react-router-dom": "^5.1.0", - "@types/react-test-renderer": "^16.9.1", + "@types/react": "~18.2.0", + "@types/react-dom": "~18.2.0", + "@types/react-test-renderer": "^18", "@types/react-virtualized": "^9.21.7", "@types/semver": "^7.1.0", "@types/semver-compare": "^1.0.1", @@ -101,7 +101,7 @@ "mini-css-extract-plugin": "^0.9.0", "prettier": "^3.0.0", "raf": "^3.4.1", - "react-test-renderer": "^16.12.0", + "react-test-renderer": "^18", "style-loader": "^1.1.3", "ts-jest": "^25.1.0", "ts-loader": "^6.0.4", @@ -118,7 +118,6 @@ "@babel/runtime-corejs3": "^7.5.5", "@emotion/core": "^10.0.14", "@emotion/styled": "^10.0.14", - "@hot-loader/react-dom": "^16.11.0", "@loadable/component": "^5.11.0", "@material-ui/core": "^4.9.0", "@material-ui/icons": "^4.5.1", @@ -126,7 +125,7 @@ "@material-ui/styles": "^4.9.0", "core-js": "^3.1.4", "emotion-theming": "^10.0.19", - "formik": "^2.1.1", + "formik": "^2.4.0", "humps": "^2.0.1", "jss": "^9.8.7", "jss-preset-default": "^4.5.0", @@ -137,13 +136,11 @@ "oboe": "^2.1.4", "polished": "^3.4.2", "qs": "^6.9.7", - "react": "^16.12.0", - "react-dom": "^16.12.0", - "react-hot-loader": "^4.12.10", + "react": "^18", + "react-dom": "^18", "react-infinite-scroller": "^1.2.4", "react-monaco-editor": "^0.32.1", - "react-router": "^5.1.2", - "react-router-dom": "^5.1.2", + "react-router-dom": "^6", "react-virtualized": "^9.21.1", "regenerator-runtime": "^0.13.3", "semver": "^7.1.2", diff --git a/src/Root.tsx b/src/Root.tsx index a72a5bb4..43323960 100644 --- a/src/Root.tsx +++ b/src/Root.tsx @@ -1,12 +1,10 @@ import React, { Suspense, FC, lazy } from 'react'; -import { hot } from 'react-hot-loader/root'; -import { StylesProvider } from '@material-ui/styles'; import { CssBaseline } from '@material-ui/core'; -import { Route, Switch, BrowserRouter } from 'react-router-dom'; +import { Routes, Route, BrowserRouter } from 'react-router-dom'; import { Global, css } from '@emotion/core'; import PrivateRoute from 'core/routes/PrivateRoute'; import Layout from 'core/layout/Layout'; -import Routes from 'core/routes/Routes'; +import AppRoutes from 'core/routes/Routes'; import registerHistory from 'plugins/history'; import registerLog from 'plugins/log'; import registerTasks from 'plugins/tasks'; @@ -61,36 +59,35 @@ const Root: FC = () => ( - - - - - - - - ( - - - - - - - - - - - - - )} - /> - - - - - - + + + + }> + + } /> + + + + }> + + } />} /> + } /> + + + + + + } + /> + + + + + ); -export default hot(Root); +export default Root; diff --git a/src/app.tsx b/src/app.tsx index df33eda2..9378cf66 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,9 +1,7 @@ -import 'core-js/stable'; -import 'regenerator-runtime/runtime'; import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import Root from 'Root'; import 'typeface-roboto'; // eslint-disable-line import/extensions import 'normalize.css'; -ReactDOM.render(, document.getElementById('react')); +createRoot(document.getElementById('react')!).render(); diff --git a/src/common/LoadingSpinner.tsx b/src/common/LoadingSpinner.tsx index 4577cd30..07e75389 100644 --- a/src/common/LoadingSpinner.tsx +++ b/src/common/LoadingSpinner.tsx @@ -15,6 +15,7 @@ const wrapper = css` interface Props { loading?: boolean; + children?: React.ReactNode; } const LoadingSpinner: FC = ({ loading = true, children }) => { diff --git a/src/common/inputs/formik/Checkbox.tsx b/src/common/inputs/formik/Checkbox.tsx index ed76195b..b5fec4b4 100644 --- a/src/common/inputs/formik/Checkbox.tsx +++ b/src/common/inputs/formik/Checkbox.tsx @@ -1,4 +1,4 @@ -import React, { FC, forwardRef, useCallback, ChangeEvent } from 'react'; +import React, { forwardRef, useCallback, ChangeEvent } from 'react'; import { Checkbox as BaseCheckbox, CheckboxProps } from '@material-ui/core'; import { useField } from 'formik'; @@ -6,7 +6,7 @@ export type Props = CheckboxProps & { name: string; }; -const Checkbox: FC = forwardRef(({ name, ...props }, ref) => { +const Checkbox = forwardRef(({ name, ...props }, ref) => { const [field, , { setValue }] = useField(name); const handleChange = useCallback( diff --git a/src/common/styles.tsx b/src/common/styles.tsx index 751488e5..fb5d395c 100644 --- a/src/common/styles.tsx +++ b/src/common/styles.tsx @@ -6,6 +6,7 @@ import { Paper, Theme } from '@material-ui/core'; const styled = st as CreateStyled; +// ponytail: casts to FC bridge emotion v10 / React 18 JSX type gap; remove in Phase 5 (Emotion v11) export const PaperWrapper = styled(Paper)<{ elevation?: number }>` padding: 2.4rem; display: flex; @@ -16,11 +17,11 @@ export const PaperWrapper = styled(Paper)<{ elevation?: number }>` ${({ theme }) => theme.breakpoints.up('sm')} { padding-top: 0; } -`; +` as unknown as React.FC<{ elevation?: number; children?: React.ReactNode }>; export const Spacer = styled.div` flex: 1; -`; +` as unknown as React.FC>; export const NoPaddingWrapper = styled.div` height: calc(100% + ${({ theme }) => theme.typography.pxToRem(theme.spacing(2))}); @@ -32,7 +33,7 @@ export const NoPaddingWrapper = styled.div` height: calc(100% + ${({ theme }) => theme.typography.pxToRem(theme.spacing(4))}); margin: -${({ theme }) => theme.typography.pxToRem(theme.spacing(2))}; } -`; +` as unknown as React.FC>; export const backgroundColor = (theme: Theme, color: string) => css` background-color: ${color}; @@ -40,5 +41,5 @@ export const backgroundColor = (theme: Theme, color: string) => css` `; export const Link = forwardRef((props, ref) => ( - + )); diff --git a/src/core/auth/Login.tsx b/src/core/auth/Login.tsx index 512896ed..9d5d4772 100644 --- a/src/core/auth/Login.tsx +++ b/src/core/auth/Login.tsx @@ -1,17 +1,15 @@ import React, { FC, useEffect } from 'react'; import { useContainer } from 'unstated-next'; -import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { StaticContext } from 'react-router'; +import { Navigate, useLocation } from 'react-router-dom'; import { AuthContainer } from 'core/auth/hooks'; import { useFlexgetAPI } from 'core/api'; import LoginCard from './LoginCard'; import SplashScreen from './Splash'; import Logo from './Logo'; -type Props = Partial>; - -const LoginPage: FC = ({ location }) => { - const { from } = location?.state ?? { from: { pathname: '/' } }; +const LoginPage: FC = () => { + const location = useLocation(); + const from = (location.state as { from?: { pathname: string } })?.from?.pathname ?? '/'; const [loggedIn, setLoggedIn] = useContainer(AuthContainer); const [loginState, checkLogin] = useFlexgetAPI('/user/token'); @@ -31,7 +29,7 @@ const LoginPage: FC = ({ location }) => { } if (loggedIn) { - return ; + return ; } return ( diff --git a/src/core/entry/cards/BaseCard.tsx b/src/core/entry/cards/BaseCard.tsx index 897029fe..2d6eeef7 100644 --- a/src/core/entry/cards/BaseCard.tsx +++ b/src/core/entry/cards/BaseCard.tsx @@ -10,6 +10,7 @@ interface Props { className?: string; label: string; loading?: boolean; + children?: React.ReactNode; } const image = css` diff --git a/src/core/entry/cards/Episode.tsx b/src/core/entry/cards/Episode.tsx index 2a7efb17..1f378d2a 100644 --- a/src/core/entry/cards/Episode.tsx +++ b/src/core/entry/cards/Episode.tsx @@ -12,6 +12,7 @@ interface Props { series: SeriesEntry; entry: EpisodeEntry; className?: string; + children?: React.ReactNode; } const summary = (theme: Theme) => css` diff --git a/src/core/entry/cards/Movie.tsx b/src/core/entry/cards/Movie.tsx index 8cd86f9f..27fa50c0 100644 --- a/src/core/entry/cards/Movie.tsx +++ b/src/core/entry/cards/Movie.tsx @@ -12,6 +12,7 @@ import { useMovieLookup } from '../lookup/movies'; interface Props { entry: MovieEntry; className?: string; + children?: React.ReactNode; } const summary = (theme: Theme) => css` diff --git a/src/core/entry/cards/Series.tsx b/src/core/entry/cards/Series.tsx index 176f63d7..31497026 100644 --- a/src/core/entry/cards/Series.tsx +++ b/src/core/entry/cards/Series.tsx @@ -11,6 +11,7 @@ import { useSeriesLookup } from '../lookup/series'; interface Props { entry: SeriesEntry; className?: string; + children?: React.ReactNode; } const summary = (theme: Theme) => css` diff --git a/src/core/entry/cards/index.tsx b/src/core/entry/cards/index.tsx index e7f9e05c..36044d6a 100644 --- a/src/core/entry/cards/index.tsx +++ b/src/core/entry/cards/index.tsx @@ -16,6 +16,7 @@ import DefaultCard from './Default'; interface Props { entry: RawEntry; className?: string; + children?: React.ReactNode; } const EntryCard: FC = ({ entry, className, children }) => { diff --git a/src/core/home/index.tsx b/src/core/home/index.tsx index 4db8bc6c..054f4ebe 100644 --- a/src/core/home/index.tsx +++ b/src/core/home/index.tsx @@ -1,5 +1,4 @@ import React, { Suspense } from 'react'; -import { hot } from 'react-hot-loader/root'; import { Grid } from '@material-ui/core'; import LoadingSpinner from 'common/LoadingSpinner'; import { useInjectPageTitle } from 'core/layout/AppBar/hooks'; @@ -22,4 +21,4 @@ const Home = () => { ); }; -export default hot(Home); +export default Home; diff --git a/src/core/layout/Layout.tsx b/src/core/layout/Layout.tsx index 03b2c532..ffb52862 100644 --- a/src/core/layout/Layout.tsx +++ b/src/core/layout/Layout.tsx @@ -17,7 +17,7 @@ import { } from './styles'; import { AppBarContainer } from './AppBar/hooks'; -const Layout: React.FC = ({ children }) => { +const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const theme = useTheme(); const matches = window.matchMedia && !window.matchMedia('(max-width: 600px)').matches; const [sidebarOpen, { close, toggle }] = useOverlayState(matches); diff --git a/src/core/layout/SideNav/index.tsx b/src/core/layout/SideNav/index.tsx index 879c7053..3d6c2b61 100644 --- a/src/core/layout/SideNav/index.tsx +++ b/src/core/layout/SideNav/index.tsx @@ -11,7 +11,7 @@ import { } from '@material-ui/core'; import { Settings } from '@material-ui/icons'; -import { useHistory } from 'react-router'; +import { useNavigate } from 'react-router-dom'; import { Route } from 'core/routes/types'; import { useGetRoutes } from 'core/routes/hooks'; @@ -37,7 +37,7 @@ interface Props { const SideNav: FC = ({ sidebarOpen = false, onClose, className }) => { const { routes } = useGetRoutes(); - const history = useHistory(); + const navigate = useNavigate(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('xs')); @@ -54,9 +54,9 @@ const SideNav: FC = ({ sidebarOpen = false, onClose, className }) => { if (isMobile) { onClose(); } - history.push(path); + navigate(path); }, - [history, isMobile, onClose], + [navigate, isMobile, onClose], ); const [anchorEl, setAnchorEl] = useState(); diff --git a/src/core/routes/PrivateRoute.tsx b/src/core/routes/PrivateRoute.tsx index be2d8807..327df816 100644 --- a/src/core/routes/PrivateRoute.tsx +++ b/src/core/routes/PrivateRoute.tsx @@ -1,31 +1,16 @@ -import React, { FC, ComponentType } from 'react'; -import { Route, Redirect, RouteProps } from 'react-router-dom'; +import React, { FC, ReactElement } from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; import { useContainer } from 'unstated-next'; import { AuthContainer } from 'core/auth/hooks'; -interface Props extends RouteProps { - component: ComponentType; +interface Props { + element: ReactElement; } -const PrivateRoute: FC = ({ component: Component, ...rest }) => { +const PrivateRoute: FC = ({ element }) => { const [loggedIn] = useContainer(AuthContainer); - return ( - - loggedIn ? ( - - ) : ( - - ) - } - /> - ); + const location = useLocation(); + return loggedIn ? element : ; }; export default PrivateRoute; diff --git a/src/core/routes/Routes.spec.tsx b/src/core/routes/Routes.spec.tsx index 00f5280a..f272cbc6 100644 --- a/src/core/routes/Routes.spec.tsx +++ b/src/core/routes/Routes.spec.tsx @@ -1,11 +1,11 @@ import React, { FC, useEffect } from 'react'; import { cleanup } from '@testing-library/react'; import { AuthContainer } from 'core/auth/hooks'; -import { MemoryRouter, Route, Switch, useHistory } from 'react-router'; +import { MemoryRouter, Routes, Route, useNavigate } from 'react-router-dom'; import { Home, PowerSettingsNew } from '@material-ui/icons'; import { useContainer } from 'unstated-next'; import { renderWithWrapper } from 'utils/tests'; -import Routes from './Routes'; +import AppRoutes from './Routes'; import { registerPlugin } from '../plugins/registry'; const LoginPage: FC = () =>
Login Page
; @@ -35,17 +35,17 @@ const TestRoutes: FC = ({ loggedIn = false }) => { setAuth(loggedIn); }, [loggedIn, setAuth]); - const { push } = useHistory(); + const navigate = useNavigate(); useEffect(() => { - push('/home'); - }, [push]); + navigate('/home'); + }, [navigate]); return ( - - - - + + } /> + } /> + ); }; @@ -57,9 +57,7 @@ describe('core/routes/PrivateRoute', () => { it('renders correctly when logged in', async () => { const { findByText } = renderWithWrapper( - - - + , ); @@ -69,9 +67,7 @@ describe('core/routes/PrivateRoute', () => { it('renders correctly when logged out', async () => { const { findByText } = renderWithWrapper( - - - + , ); expect(await findByText('Login Page')).toBeInTheDocument(); diff --git a/src/core/routes/Routes.tsx b/src/core/routes/Routes.tsx index 0971a8cc..c7003978 100644 --- a/src/core/routes/Routes.tsx +++ b/src/core/routes/Routes.tsx @@ -1,24 +1,27 @@ import React, { useEffect, FC } from 'react'; import { useContainer } from 'unstated-next'; -import { Switch, useLocation } from 'react-router-dom'; +import { Routes, Route, useLocation } from 'react-router-dom'; import { StatusContainer, actions } from 'core/status/hooks'; import { useGetRoutes } from 'core/routes/hooks'; import PrivateRoute from './PrivateRoute'; -const Routes: FC = () => { +const AppRoutes: FC = () => { const { routes } = useGetRoutes(); - const location = useLocation(); const [, dispatch] = useContainer(StatusContainer); useEffect(() => dispatch(actions.clear()), [dispatch, location]); return ( - - {routes.map(props => { - return ; - })} - + + {routes.map(({ path, component: Component }) => ( + } />} + /> + ))} + ); }; -export default Routes; +export default AppRoutes; diff --git a/src/core/theme/ThemeProvider.tsx b/src/core/theme/ThemeProvider.tsx index 640e9202..6c34722e 100644 --- a/src/core/theme/ThemeProvider.tsx +++ b/src/core/theme/ThemeProvider.tsx @@ -4,7 +4,7 @@ import { ThemeProvider as MuiThemeProvider } from '@material-ui/core'; import { ThemeProvider as EmotionThemeProvider } from 'emotion-theming'; import { createTheme, ThemeContainer } from './index'; -const Theme: FC = ({ children }) => { +const Theme: FC<{ children?: React.ReactNode }> = ({ children }) => { const [type] = useContainer(ThemeContainer); const theme = useMemo(() => createTheme(type), [type]); @@ -16,7 +16,7 @@ const Theme: FC = ({ children }) => { ); }; -const ThemeProvider: FC = ({ children }) => ( +const ThemeProvider: FC<{ children?: React.ReactNode }> = ({ children }) => ( {children} diff --git a/src/plugins/config/Config.tsx b/src/plugins/config/Config.tsx index cbf0b09f..6ca0b688 100644 --- a/src/plugins/config/Config.tsx +++ b/src/plugins/config/Config.tsx @@ -1,5 +1,4 @@ import React, { useState, useCallback, useMemo, FC } from 'react'; -import { hot } from 'react-hot-loader/root'; import { Formik } from 'formik'; import { useInjectPageTitle } from 'core/layout/AppBar/hooks'; import { Button, Theme, Link } from '@material-ui/core'; @@ -110,4 +109,4 @@ const Config: FC = () => { ); }; -export default hot(Config); +export default Config; diff --git a/src/plugins/config/Editor.tsx b/src/plugins/config/Editor.tsx index 707b3ce3..66195fb2 100644 --- a/src/plugins/config/Editor.tsx +++ b/src/plugins/config/Editor.tsx @@ -1,7 +1,9 @@ import React, { FC, useCallback, useEffect } from 'react'; import { useField } from 'formik'; import { useTheme } from '@material-ui/core'; -import MonacoEditor from 'react-monaco-editor'; +// ponytail: cast bridges react-monaco-editor / React 18 JSX type gap; replaced in Phase 7 +import MonacoEditor_ from 'react-monaco-editor'; +const MonacoEditor = MonacoEditor_ as unknown as React.FC; import { languages, editor } from 'monaco-editor/esm/vs/editor/editor.api'; import './monaco'; diff --git a/src/plugins/history/History.tsx b/src/plugins/history/History.tsx index 124e8653..0452e840 100644 --- a/src/plugins/history/History.tsx +++ b/src/plugins/history/History.tsx @@ -1,6 +1,5 @@ import React, { FC, useRef, useEffect, useCallback } from 'react'; import { findDOMNode } from 'react-dom'; -import { hot } from 'react-hot-loader/root'; import { useInjectPageTitle } from 'core/layout/AppBar/hooks'; import { NoPaddingWrapper } from 'common/styles'; import { useMergeState } from 'utils/hooks'; @@ -53,4 +52,4 @@ const History: FC = () => { ); }; -export default hot(History); +export default History; diff --git a/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx b/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx index 1fee1e7a..ae4996d1 100644 --- a/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx +++ b/src/plugins/lists/base/AddFab/AddEntryDialog.spec.tsx @@ -29,7 +29,7 @@ describe('plugins/lists/base/AddFab/AddEntryDialog', () => { ${'entry'} | ${'entry_list'} | ${'entries'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${'movies'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider, itemPrefix }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx b/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx index a36fb241..15a8d408 100644 --- a/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx +++ b/src/plugins/lists/base/AddFab/AddListDialog.spec.tsx @@ -26,7 +26,7 @@ describe('plugins/lists/base/AddFab/AddListDialog', () => { ${'entry'} | ${'entry_list'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( {children} diff --git a/src/plugins/lists/base/EntryList/EntryCard.spec.tsx b/src/plugins/lists/base/EntryList/EntryCard.spec.tsx index 2ebcd89a..42a4f69d 100644 --- a/src/plugins/lists/base/EntryList/EntryCard.spec.tsx +++ b/src/plugins/lists/base/EntryList/EntryCard.spec.tsx @@ -29,7 +29,7 @@ describe('plugins/lists/base/EntryList/EntryCard', () => { ${'entry'} | ${'entry_list'} | ${'entries'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${'movies'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx b/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx index 94b33fcf..359f52ac 100644 --- a/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx +++ b/src/plugins/lists/base/EntryList/InjectEntryDialog.spec.tsx @@ -30,7 +30,7 @@ describe('plugins/lists/base/EntryList/InjectEntryDialog', () => { ${'entry'} | ${'entry_list'} | ${'entries'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${'movies'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider, itemPrefix }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx b/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx index 9d98a7b4..345c391b 100644 --- a/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx +++ b/src/plugins/lists/base/EntryList/RemoveEntryDialog.spec.tsx @@ -29,7 +29,7 @@ describe('plugins/lists/base/EntryList/RemoveEntryDialog', () => { ${'entry'} | ${'entry_list'} | ${'entries'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${'movies'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider, itemPrefix }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/src/plugins/lists/base/RemoveListDialog.spec.tsx b/src/plugins/lists/base/RemoveListDialog.spec.tsx index cd0acaeb..1fdd3888 100644 --- a/src/plugins/lists/base/RemoveListDialog.spec.tsx +++ b/src/plugins/lists/base/RemoveListDialog.spec.tsx @@ -26,7 +26,7 @@ describe('plugins/lists/base/EntryListHeader/RemoveListDialog', () => { ${'entry'} | ${'entry_list'} | ${EntryListContainer.Provider} ${'movie'} | ${'movie_list'} | ${MovieListContainer.Provider} `('$name', ({ prefix, Provider }) => { - const wrapper: FC = ({ children }) => ( + const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( {children} diff --git a/src/plugins/lists/entry/EntryList.tsx b/src/plugins/lists/entry/EntryList.tsx index 8b1bf401..eaac9769 100644 --- a/src/plugins/lists/entry/EntryList.tsx +++ b/src/plugins/lists/entry/EntryList.tsx @@ -1,5 +1,4 @@ import React, { FC } from 'react'; -import { hot } from 'react-hot-loader/root'; import ManagedList from '../base'; import { EntryListContainer } from './hooks'; @@ -11,4 +10,4 @@ const PendingList: FC = () => { ); }; -export default hot(PendingList); +export default PendingList; diff --git a/src/plugins/lists/movies/MovieList.tsx b/src/plugins/lists/movies/MovieList.tsx index dda67eb5..a49accda 100644 --- a/src/plugins/lists/movies/MovieList.tsx +++ b/src/plugins/lists/movies/MovieList.tsx @@ -1,5 +1,4 @@ import React, { FC } from 'react'; -import { hot } from 'react-hot-loader/root'; import ManagedList from '../base'; import { MovieListContainer } from './hooks'; @@ -11,4 +10,4 @@ const PendingList: FC = () => { ); }; -export default hot(PendingList); +export default PendingList; diff --git a/src/plugins/lists/pending/Card.spec.tsx b/src/plugins/lists/pending/Card.spec.tsx index 73855fbc..cd5ecd16 100644 --- a/src/plugins/lists/pending/Card.spec.tsx +++ b/src/plugins/lists/pending/Card.spec.tsx @@ -20,7 +20,7 @@ const TestEntryCard: typeof EntryCard = props => { return ; }; -const wrapper: FC = ({ children }) => ( +const wrapper: FC<{ children?: React.ReactNode }> = ({ children }) => ( diff --git a/src/plugins/lists/pending/PendingList.tsx b/src/plugins/lists/pending/PendingList.tsx index 99ad0819..73923efa 100644 --- a/src/plugins/lists/pending/PendingList.tsx +++ b/src/plugins/lists/pending/PendingList.tsx @@ -1,5 +1,4 @@ import React, { FC } from 'react'; -import { hot } from 'react-hot-loader/root'; import ManagedList from '../base'; import { PendingListContainer } from './hooks'; @@ -11,4 +10,4 @@ const PendingList: FC = () => { ); }; -export default hot(PendingList); +export default PendingList; diff --git a/src/plugins/log/Log.tsx b/src/plugins/log/Log.tsx index 413f4268..0d92690e 100644 --- a/src/plugins/log/Log.tsx +++ b/src/plugins/log/Log.tsx @@ -1,6 +1,5 @@ import React, { FC, useEffect } from 'react'; import { Formik } from 'formik'; -import { hot } from 'react-hot-loader/root'; import { css } from '@emotion/core'; import { PaperWrapper } from 'common/styles'; import { useInjectPageTitle } from 'core/layout/AppBar/hooks'; @@ -43,4 +42,4 @@ const LogPage: FC = () => { ); }; -export default hot(LogPage); +export default LogPage; diff --git a/src/plugins/log/LogTable.tsx b/src/plugins/log/LogTable.tsx index aaea274f..5f1e1e62 100644 --- a/src/plugins/log/LogTable.tsx +++ b/src/plugins/log/LogTable.tsx @@ -1,5 +1,9 @@ import React, { FC } from 'react'; -import { AutoSizer, Column, Table } from 'react-virtualized'; +// ponytail: casts bridge react-virtualized / React 18 JSX type gap; remove when lib upgrades or replaced +import { AutoSizer as AutoSizer_, Column as Column_, Table as Table_ } from 'react-virtualized'; +const AutoSizer = AutoSizer_ as unknown as React.FC; +const Table = Table_ as unknown as React.FC; +const Column = Column_ as unknown as React.FC; import 'react-virtualized/styles.css'; import { css, ClassNames } from '@emotion/core'; import { Theme, useTheme } from '@material-ui/core'; diff --git a/src/plugins/series/Episodes/index.spec.tsx b/src/plugins/series/Episodes/index.spec.tsx index 57fa91ac..72d09ab4 100644 --- a/src/plugins/series/Episodes/index.spec.tsx +++ b/src/plugins/series/Episodes/index.spec.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect } from 'react'; -import { useHistory, Route } from 'react-router-dom'; +import { useNavigate, Routes, Route } from 'react-router-dom'; import { cleanup, fireEvent, getNodeText, wait } from '@testing-library/react'; import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; @@ -7,18 +7,18 @@ import AppBar from 'core/layout/AppBar'; import Episodes from './index'; const TestEpisodes: FC = () => { - const { push } = useHistory(); + const navigate = useNavigate(); useEffect(() => { - push('/series/1'); - }, [push]); + navigate('/series/1'); + }, [navigate]); return ( <> - - - + + } /> + ); }; diff --git a/src/plugins/series/Episodes/index.tsx b/src/plugins/series/Episodes/index.tsx index 0fb85dd3..c3e65b5b 100644 --- a/src/plugins/series/Episodes/index.tsx +++ b/src/plugins/series/Episodes/index.tsx @@ -1,5 +1,5 @@ import React, { FC, useCallback, useMemo } from 'react'; -import { useParams, useHistory } from 'react-router'; +import { useParams, useNavigate } from 'react-router-dom'; import { Formik } from 'formik'; import { useInjectPageTitle, useSetAppBarIcon } from 'core/layout/AppBar/hooks'; import { Direction } from 'utils/query'; @@ -11,21 +11,17 @@ import Header from './Header'; import EpisodeList from './EpisodeList'; import { useGetShowDetail } from '../hooks/shows'; -interface Match { - showId: string; -} - const EpisodesPage: FC = () => { - const showId = parseInt(useParams().showId, 10); + const showId = parseInt(useParams().showId!, 10); - const { push } = useHistory(); + const navigate = useNavigate(); const { show } = useGetShowDetail(showId); const title = show ? `${show.name} - Episodes` : 'Episodes'; const onClick = useCallback(() => { - push('/series'); - }, [push]); + navigate('/series'); + }, [navigate]); const icon = useMemo( () => ({ diff --git a/src/plugins/series/Series.spec.tsx b/src/plugins/series/Series.spec.tsx index fe124de0..5f21e019 100644 --- a/src/plugins/series/Series.spec.tsx +++ b/src/plugins/series/Series.spec.tsx @@ -1,6 +1,6 @@ import React, { FC, useEffect } from 'react'; import { cleanup } from '@testing-library/react'; -import { useHistory, Route, Switch } from 'react-router'; +import { useNavigate, Routes, Route } from 'react-router-dom'; import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; @@ -11,18 +11,16 @@ interface Props { } const TestSeries: FC = ({ path }) => { - const { push } = useHistory(); + const navigate = useNavigate(); useEffect(() => { - push(path); - }, [path, push]); + navigate(path); + }, [path, navigate]); return ( <> - - - - - + + } /> + ); }; diff --git a/src/plugins/series/Series.tsx b/src/plugins/series/Series.tsx index b83497d4..bbb62d75 100644 --- a/src/plugins/series/Series.tsx +++ b/src/plugins/series/Series.tsx @@ -1,18 +1,13 @@ import React, { FC } from 'react'; -import { hot } from 'react-hot-loader/root'; -import { Route, Switch, useRouteMatch } from 'react-router'; +import { Routes, Route } from 'react-router-dom'; import Shows from './Shows'; import Episodes from './Episodes'; -const Series: FC = () => { - const { path } = useRouteMatch(); +const Series: FC = () => ( + + } /> + } /> + +); - return ( - - - - - ); -}; - -export default hot(Series); +export default Series; diff --git a/src/plugins/series/Shows/ShowCard.tsx b/src/plugins/series/Shows/ShowCard.tsx index 0ebf392a..217efcf6 100644 --- a/src/plugins/series/Shows/ShowCard.tsx +++ b/src/plugins/series/Shows/ShowCard.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import { useRouteMatch } from 'react-router'; +import { useHref } from 'react-router-dom'; import { css } from '@emotion/core'; import { Card, @@ -51,10 +51,10 @@ const cardActions = (theme: Theme) => css` `; const ShowCard: FC = ({ show, onRemoveClick }) => { - const { url } = useRouteMatch(); + const basePath = useHref('.'); return ( - + {!!show.beginEpisode && ( diff --git a/src/plugins/tasks/Latest.tsx b/src/plugins/tasks/Latest.tsx index 03f0e389..4aed2e5b 100644 --- a/src/plugins/tasks/Latest.tsx +++ b/src/plugins/tasks/Latest.tsx @@ -1,6 +1,6 @@ import React, { FC, useMemo, useState } from 'react'; import { Formik } from 'formik'; -import { useHistory, useRouteMatch } from 'react-router'; +import { useNavigate, useHref } from 'react-router-dom'; import { CheckCircle, Error } from '@material-ui/icons'; import { useInjectPageTitle } from 'core/layout/AppBar/hooks'; import { Direction } from 'utils/query'; @@ -58,8 +58,8 @@ const headers = [ const Latest: FC = () => { useInjectPageTitle('Tasks - Latest Executions'); - const { push } = useHistory(); - const { url } = useRouteMatch(); + const navigate = useNavigate(); + const basePath = useHref('.'); const [options, setOptions] = useState({ page: 0, perPage: 10, @@ -105,12 +105,12 @@ const Latest: FC = () => { ), }, props: { - onClick: () => push(`${url}/${id}`), + onClick: () => navigate(`${basePath}/${id}`), hover: true, }, }), ), - [push, tasks, url], + [navigate, tasks, basePath], ); return ( diff --git a/src/plugins/tasks/TaskExecutions.tsx b/src/plugins/tasks/TaskExecutions.tsx index b1406309..8c4c4375 100644 --- a/src/plugins/tasks/TaskExecutions.tsx +++ b/src/plugins/tasks/TaskExecutions.tsx @@ -1,5 +1,5 @@ import React, { FC, useCallback, useState, useMemo } from 'react'; -import { useHistory, useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router-dom'; import { ArrowBackIos, CheckCircle, Error } from '@material-ui/icons'; import { useInjectPageTitle, useSetAppBarIcon } from 'core/layout/AppBar/hooks'; import { useGetTask, useGetTaskExecutions } from 'plugins/tasks/hooks'; @@ -8,10 +8,6 @@ import { TaskExecutionOptions, SortByStatus } from 'plugins/tasks/types'; import { Formik } from 'formik'; import TaskTable from 'plugins/tasks/TaskTable'; -interface Match { - taskId: string; -} - const headers = [ { id: SortByStatus.Succeeded, @@ -60,7 +56,7 @@ const headers = [ ]; const TaskExecutions: FC = () => { - const taskId = parseInt(useParams().taskId, 10); + const taskId = parseInt(useParams().taskId!, 10); const [options, setOptions] = useState({ page: 0, perPage: 10, @@ -71,11 +67,11 @@ const TaskExecutions: FC = () => { const { task } = useGetTask(taskId); const { executions, total } = useGetTaskExecutions(taskId, options); - const { push } = useHistory(); + const navigate = useNavigate(); const onClick = useCallback(() => { - push('/tasks'); - }, [push]); + navigate('/tasks'); + }, [navigate]); const icon = useMemo( () => ({ diff --git a/src/plugins/tasks/TaskTable.tsx b/src/plugins/tasks/TaskTable.tsx index 03f7d31c..5a2d74ec 100644 --- a/src/plugins/tasks/TaskTable.tsx +++ b/src/plugins/tasks/TaskTable.tsx @@ -101,7 +101,7 @@ const TaskTable = ({ total, headers, rows }: Props) => { {headers.map(({ id, numeric }) => ( - {data[id]} + {data[id] as React.ReactNode} ))} diff --git a/src/plugins/tasks/Tasks.spec.tsx b/src/plugins/tasks/Tasks.spec.tsx index 566878cb..706189a5 100644 --- a/src/plugins/tasks/Tasks.spec.tsx +++ b/src/plugins/tasks/Tasks.spec.tsx @@ -1,6 +1,6 @@ import React, { FC, useEffect } from 'react'; import { cleanup } from '@testing-library/react'; -import { useHistory, Route, Switch } from 'react-router'; +import { useNavigate, Routes, Route } from 'react-router-dom'; import * as fetchMock from 'fetch-mock'; import { renderWithWrapper } from 'utils/tests'; import AppBar from 'core/layout/AppBar'; @@ -12,18 +12,16 @@ interface Props { } const TestTasks: FC = ({ path }) => { - const { push } = useHistory(); + const navigate = useNavigate(); useEffect(() => { - push(path); - }, [path, push]); + navigate(path); + }, [path, navigate]); return ( - - - - - + + } /> + ); }; diff --git a/src/plugins/tasks/Tasks.tsx b/src/plugins/tasks/Tasks.tsx index b7e29e0a..1dd45bae 100644 --- a/src/plugins/tasks/Tasks.tsx +++ b/src/plugins/tasks/Tasks.tsx @@ -1,18 +1,13 @@ import React, { FC } from 'react'; -import { hot } from 'react-hot-loader/root'; -import { useRouteMatch, Switch, Route } from 'react-router'; +import { Routes, Route } from 'react-router-dom'; import Latest from './Latest'; import TaskExecutions from './TaskExecutions'; -const Task: FC = () => { - const { path } = useRouteMatch(); +const Task: FC = () => ( + + } /> + } /> + +); - return ( - - - - - ); -}; - -export default hot(Task); +export default Task; diff --git a/src/utils/tests/index.tsx b/src/utils/tests/index.tsx index 0619ffa3..e4171da6 100644 --- a/src/utils/tests/index.tsx +++ b/src/utils/tests/index.tsx @@ -11,7 +11,7 @@ export const themed = (component: ReactNode) => {component} {component}; -export const BaseProviders: FC = ({ children }) => { +export const BaseProviders: FC<{ children?: ReactNode }> = ({ children }) => { return ( diff --git a/webpack.config.js b/webpack.config.js index 5c227b51..322f8a42 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -12,7 +12,7 @@ const mode = __DEV__ ? 'development' : 'production'; const __VERSION__ = process.env.VERSION || cfg.version; const entry = { - main: [...(__DEV__ ? ['react-hot-loader/patch'] : []), 'whatwg-fetch', './src/app.tsx'], + main: ['whatwg-fetch', './src/app.tsx'], }; const output = { @@ -69,9 +69,7 @@ const config = { resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'], modules: [path.resolve('./src'), 'node_modules'], - alias: { - 'react-dom': '@hot-loader/react-dom', - }, + alias: {}, }, optimization: { sideEffects: true, diff --git a/yarn.lock b/yarn.lock index aba420e7..f0a86ee3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -993,13 +993,6 @@ core-js-pure "^3.0.0" regenerator-runtime "^0.13.2" -"@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" - integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== - dependencies: - regenerator-runtime "^0.13.2" - "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2": version "7.12.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" @@ -1007,6 +1000,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.3": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" + integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.10.4", "@babel/template@^7.3.3", "@babel/template@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -1227,16 +1227,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@hot-loader/react-dom@^16.11.0": - version "16.11.0" - resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.11.0.tgz#c0b483923b289db5431516f56ee2a69448ebf9bd" - integrity sha512-cIOVB8YgT4EVCNiXK+gGuYl6adW/TKlW3N7GvgY5QgpL2NTWagF/oJxVscHSdR3O7NjJsoxdseB5spqwCHNIhA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.17.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1696,6 +1686,11 @@ dependencies: "@types/node" ">= 8" +"@remix-run/router@1.23.3": + version "1.23.3" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.23.3.tgz#957c098d4393d301a8aa7dccf3ef28ea5430e36a" + integrity sha512-4An71tdz9X8+3sI4Qqqd2LWd9vS39J7sqd9EU4Scw7TJE/qB10Flv/UuqbPVgfQV9XoK8Np6jNquZitnZq5i+Q== + "@sheerun/mutationobserver-shim@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" @@ -1837,10 +1832,12 @@ dependencies: "@types/node" "*" -"@types/history@*": - version "4.7.5" - resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.5.tgz#527d20ef68571a4af02ed74350164e7a67544860" - integrity sha512-wLD/Aq2VggCJXSjxEwrMafIP51Z+13H78nXIX0ABEuIGhmB5sNGbR113MOKo+yfw+RDo1ZU3DM6yfnnRF/+ouw== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.7" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz#306e3a3a73828522efa1341159da4846e7573a6c" + integrity sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g== + dependencies: + hoist-non-react-statics "^3.3.0" "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" @@ -1953,29 +1950,19 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^5.1.0": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.3.tgz#b5d28e7850bd274d944c0fbbe5d57e6b30d71196" - integrity sha512-pCq7AkOvjE65jkGS5fQwQhvUp4+4PVD9g39gXLZViP2UqFiFzsEpB3PKf0O6mdbKsewSK8N14/eegisa/0CwnA== - dependencies: - "@types/history" "*" - "@types/react" "*" - "@types/react-router" "*" - -"@types/react-router@*": - version "5.1.4" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.4.tgz#7d70bd905543cb6bcbdcc6bd98902332054f31a6" - integrity sha512-PZtnBuyfL07sqCJvGg3z+0+kt6fobc/xmle08jBiezLS8FrmGeiGkJnuxL/8Zgy9L83ypUhniV5atZn/L8n9MQ== +"@types/react-dom@~18.2.0": + version "18.2.25" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.25.tgz#2946a30081f53e7c8d585eb138277245caedc521" + integrity sha512-o/V48vf4MQh7juIKZU2QGDfli6p1+OOi5oXx36Hffpc9adsHeXjVp8rHuPkjd8VT8sOJ2Zp05HR7CdpGTIUFUA== dependencies: - "@types/history" "*" "@types/react" "*" -"@types/react-test-renderer@^16.9.1": - version "16.9.2" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" - integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw== +"@types/react-test-renderer@^18": + version "18.3.1" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.3.1.tgz#225bfe8d4ad7ee3b04c2fa27642bb74274a5961d" + integrity sha512-vAhnk0tG2eGa37lkU9+s5SoroCsRI08xnsWFiAXOuPH2jqzMbcXvKExXViPi1P5fIklDeCvXqyrdmipFaSkZrA== dependencies: - "@types/react" "*" + "@types/react" "^18" "@types/react-transition-group@^4.2.0": version "4.2.3" @@ -2000,10 +1987,18 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@^16.9.16": - version "16.9.51" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60" - integrity sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ== +"@types/react@^18": + version "18.3.31" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.31.tgz#b5e95e28ffcceab8d982f33f2eb076e17653c2a4" + integrity sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw== + dependencies: + "@types/prop-types" "*" + csstype "^3.2.2" + +"@types/react@~18.2.0": + version "18.2.79" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.79.tgz#c40efb4f255711f554d47b449f796d1c7756d865" + integrity sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -3912,10 +3907,10 @@ csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.5, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431" integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA== -csstype@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" - integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== +csstype@^3.0.2, csstype@^3.2.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== cyclist@^1.0.1: version "1.0.1" @@ -4205,11 +4200,6 @@ dom-serializer@~0.1.1: domelementtype "^1.3.0" entities "^1.1.1" -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= - domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -5305,19 +5295,19 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formik@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.3.tgz#bd168011436393c8426a70cf8dfa5ddbbb95fd23" - integrity sha512-twK9sxWt+M2NSXxHBw6TiXNJ72iPVhgUUjeZMD8RYQC+VU7Hrxu6ElCz1xGmbT0q2DbHk4IxoupCkGEPwERzEA== +formik@^2.4.0: + version "2.4.9" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.9.tgz#7e5b81e9c9e215d0ce2ac8fed808cf7fba0cd204" + integrity sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og== dependencies: + "@types/hoist-non-react-statics" "^3.3.1" deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" - lodash "^4.17.14" - lodash-es "^4.17.14" + lodash "^4.17.21" + lodash-es "^4.17.21" react-fast-compare "^2.0.1" - scheduler "^0.18.0" tiny-warning "^1.0.2" - tslib "^1.10.0" + tslib "^2.0.0" forwarded@~0.1.2: version "0.1.2" @@ -5526,14 +5516,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@^4.3.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" - integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== - dependencies: - min-document "^2.19.0" - process "^0.11.10" - globals@^11.1.0: version "11.12.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" @@ -5579,11 +5561,6 @@ growly@^1.3.0: resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== - gzip-size@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -5697,18 +5674,6 @@ he@1.2.x: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== -history@^4.9.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" - integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== - dependencies: - "@babel/runtime" "^7.1.2" - loose-envify "^1.2.0" - resolve-pathname "^3.0.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" - value-equal "^1.0.1" - hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -5718,7 +5683,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: +hoist-non-react-statics@^3.2.1, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -6388,11 +6353,6 @@ is-wsl@^2.1.1: dependencies: is-docker "^2.0.0" -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= - isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7348,10 +7308,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.14: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash-es@^4.17.21: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.18.1.tgz#b962eeb80d9d983a900bf342961fb7418ca10b1d" + integrity sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A== lodash.escape@^4.0.1: version "4.0.1" @@ -7398,6 +7358,11 @@ lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17. resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +lodash@^4.17.21: + version "4.18.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" + integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== + loglevel@^1.6.8: version "1.7.0" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" @@ -7410,7 +7375,7 @@ lolex@^5.0.0: dependencies: "@sinonjs/commons" "^1.7.0" -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7607,27 +7572,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= - dependencies: - dom-walk "^0.1.0" - min-indent@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= -mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== - dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" - mini-css-extract-plugin@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz#47f2cf07aa165ab35733b1fc97d4c46c0564339e" @@ -8384,13 +8333,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== - dependencies: - isarray "0.0.1" - path-to-regexp@^2.2.1: version "2.4.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" @@ -8681,7 +8623,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8848,35 +8790,19 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -react-dom@^16.12.0: - version "16.12.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" - integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== +react-dom@^18: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.18.0" + scheduler "^0.23.2" react-fast-compare@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== -react-hot-loader@^4.12.10: - version "4.12.19" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.19.tgz#99a1c763352828f404fa51cd887c5e16bb5b74d1" - integrity sha512-p8AnA4QE2GtrvkdmqnKrEiijtVlqdTIDCHZOwItkI9kW51bt5XnQ/4Anz8giiWf9kqBpEQwsmnChDCAFBRyR/Q== - dependencies: - fast-levenshtein "^2.0.6" - global "^4.3.0" - hoist-non-react-statics "^3.3.0" - loader-utils "^1.1.0" - prop-types "^15.6.1" - react-lifecycles-compat "^3.0.4" - shallowequal "^1.1.0" - source-map "^0.7.3" - react-infinite-scroller@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz#f67eaec4940a4ce6417bebdd6e3433bfc38826e9" @@ -8889,7 +8815,12 @@ react-is@^16.12.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: +"react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.3.1: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.12.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== @@ -8907,36 +8838,30 @@ react-monaco-editor@^0.32.1: "@types/react" "^15.x || ^16.x" prop-types "^15.0.0" -react-router-dom@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" - integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== +react-router-dom@^6: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.30.4.tgz#f7167bf3da6c7d9132130ea985dd06def25e84d5" + integrity sha512-q4HvNl+mmDdkS0g+MqiBZNteQJCuimWoOyHMy4T/RQLAn9Z29+E91QXRaxOujeMl2HTzRSS0KFPd7lxX3PjV0Q== dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - loose-envify "^1.3.1" - prop-types "^15.6.2" - react-router "5.1.2" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" + "@remix-run/router" "1.23.3" + react-router "6.30.4" -react-router@5.1.2, react-router@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== - dependencies: - "@babel/runtime" "^7.1.2" - history "^4.9.0" - hoist-non-react-statics "^3.1.0" - loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" - path-to-regexp "^1.7.0" - prop-types "^15.6.2" - react-is "^16.6.0" - tiny-invariant "^1.0.2" - tiny-warning "^1.0.0" +react-router@6.30.4: + version "6.30.4" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.30.4.tgz#638f35176527bd243d96d81d35d33b757bad46c2" + integrity sha512-SVUsDe+DybHM/WmYKIVYhZh1o5Dcuf16yM6WjG02Q9XVFMZIJyHYhwrr6bFBXZkVP6z69kNkMyBCujt8FaFLJA== + dependencies: + "@remix-run/router" "1.23.3" -react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0: +react-shallow-renderer@^16.15.0: + version "16.15.0" + resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz#48fb2cf9b23d23cde96708fe5273a7d3446f4457" + integrity sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA== + dependencies: + object-assign "^4.1.1" + react-is "^16.12.0 || ^17.0.0 || ^18.0.0" + +react-test-renderer@^16.0.0-0: version "16.12.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f" integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w== @@ -8946,6 +8871,15 @@ react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0: react-is "^16.8.6" scheduler "^0.18.0" +react-test-renderer@^18: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-18.3.1.tgz#e693608a1f96283400d4a3afead6893f958b80b4" + integrity sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA== + dependencies: + react-is "^18.3.1" + react-shallow-renderer "^16.15.0" + scheduler "^0.23.2" + react-transition-group@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" @@ -8968,14 +8902,12 @@ react-virtualized@^9.21.1: prop-types "^15.6.0" react-lifecycles-compat "^3.0.4" -react@^16.12.0: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^18: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" read-pkg-up@^2.0.0: version "2.0.0" @@ -9292,11 +9224,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-pathname@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" - integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -9435,14 +9362,6 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" -scheduler@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe" - integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4" @@ -9451,6 +9370,13 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + schema-utils@^0.4.0: version "0.4.7" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" @@ -9602,11 +9528,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -shallowequal@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" - integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ== - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -10232,12 +10153,7 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" -tiny-invariant@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" - integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== - -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== @@ -10365,7 +10281,7 @@ tsconfig-paths@^3.9.0: minimist "^1.2.0" strip-bom "^3.0.0" -tslib@^1.10.0, tslib@^1.8.1: +tslib@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== @@ -10375,6 +10291,11 @@ tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== +tslib@^2.0.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -10686,11 +10607,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -value-equal@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" - integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== - vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" From 64a9cfcffc2ec5631feddf733ad85d756c867d06 Mon Sep 17 00:00:00 2001 From: paranoidi Date: Sun, 28 Jun 2026 21:08:30 +0300 Subject: [PATCH 04/22] Phase 4: Jest 29 + RTL v14 + Enzyme removal + faker-js migration - Upgrade jest to ^29, ts-jest to ^29, @testing-library/react to ^14, @testing-library/jest-dom to ^6, @testing-library/user-event to ^14 - Replace faker with @faker-js/faker in 3 fixture files - Remove enzyme, enzyme-adapter-react-16, enzyme-to-json, react-hot-loader/babel - Rewrite jest.config.js: ts-jest transform format, jest-extended/all, testEnvironmentOptions, updated serializers and setupFilesAfterEnv - Update tsconfig.jest.json: module commonjs for fetch-mock default import; add tsconfig.json spec exclude to prevent TS1192 errors - Fix all 34 spec files: fetchMock default import (prototype chain fix) - Rewrite 5 Enzyme spec files to RTL (StatusBar, ErrorStatus, InfoStatus, LoadingBar, AppBar) - Update 9 snapshots to Jest 29 format (Object { -> {) - Fix LoginCard.spec: form role + async login state assertion - Fix Routes.spec: remove double MemoryRouter, fix navigate effect dependency (RR v6 useNavigate is unstable across locations; use [] not [navigate]) - Fix Config.spec: correct waitFor URL and async button queries - Add src/types/testing.d.ts for jest-dom + jest-extended type references --- babel.config.js | 1 - jest.config.js | 20 +- package.json | 23 +- src/core/auth/LoginCard.spec.tsx | 13 +- .../auth/__snapshots__/Login.spec.tsx.snap | 157 +- .../auth/__snapshots__/Splash.spec.tsx.snap | 2 +- src/core/entry/cards/Episode.spec.tsx | 2 +- src/core/entry/cards/Movie.spec.tsx | 2 +- src/core/entry/cards/Series.spec.tsx | 2 +- src/core/entry/fixtures.ts | 134 +- src/core/layout/AppBar/AppBar.spec.tsx | 107 +- src/core/layout/Layout.spec.tsx | 2 +- src/core/layout/SideNav/Version.spec.tsx | 2 +- src/core/layout/SideNav/index.spec.tsx | 2 +- .../layout/__snapshots__/Layout.spec.tsx.snap | 24 +- src/core/operations/Card.spec.tsx | 2 +- src/core/operations/Operations.spec.tsx | 2 +- src/core/routes/Routes.spec.tsx | 18 +- src/core/status/ErrorStatus.spec.tsx | 55 +- src/core/status/InfoStatus.spec.tsx | 56 +- src/core/status/LoadingBar.spec.tsx | 49 +- src/core/status/StatusBar.spec.tsx | 10 +- src/plugins/config/Config.spec.tsx | 28 +- src/plugins/history/FilterNav.spec.tsx | 2 +- src/plugins/history/History.spec.tsx | 2 +- src/plugins/history/HistoryList.spec.tsx | 2 +- .../__snapshots__/FilterNav.spec.tsx.snap | 12 +- src/plugins/history/fixtures.ts | 12 +- .../lists/base/AddFab/AddEntryDialog.spec.tsx | 14 +- .../lists/base/AddFab/AddListDialog.spec.tsx | 14 +- src/plugins/lists/base/Entries.spec.tsx | 8 +- .../lists/base/EntryList/EntryCard.spec.tsx | 2 +- .../base/EntryList/InjectEntryDialog.spec.tsx | 14 +- .../base/EntryList/RemoveEntryDialog.spec.tsx | 14 +- .../lists/base/RemoveListDialog.spec.tsx | 14 +- src/plugins/lists/entry/EntryList.spec.tsx | 2 +- src/plugins/lists/movies/MovieList.spec.tsx | 2 +- src/plugins/lists/pending/Card.spec.tsx | 2 +- .../lists/pending/PendingList.spec.tsx | 2 +- .../log/__snapshots__/Header.spec.tsx.snap | 4 +- src/plugins/log/fixtures.ts | 10 +- .../series/Episodes/EpisodeCard.spec.tsx | 2 +- src/plugins/series/Episodes/Releases.spec.tsx | 2 +- src/plugins/series/Episodes/index.spec.tsx | 6 +- src/plugins/series/RemoveDialog.spec.tsx | 16 +- src/plugins/series/Series.spec.tsx | 2 +- .../series/Shows/AddShowDialog.spec.tsx | 14 +- src/plugins/series/Shows/ShowCard.spec.tsx | 2 +- src/plugins/series/Shows/index.spec.tsx | 10 +- src/plugins/tasks/Card.spec.tsx | 2 +- src/plugins/tasks/Tasks.spec.tsx | 2 +- src/types/testing.d.ts | 2 + src/utils/tests/setupFiles.ts | 10 +- tsconfig.jest.json | 4 +- tsconfig.json | 4 + yarn.lock | 3995 +++++++++-------- 56 files changed, 2482 insertions(+), 2436 deletions(-) create mode 100644 src/types/testing.d.ts diff --git a/babel.config.js b/babel.config.js index db6bd9b4..972e17f1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -19,7 +19,6 @@ module.exports = function config(api) { '@babel/plugin-proposal-optional-chaining', '@babel/plugin-proposal-class-properties', 'babel-plugin-polished', - 'react-hot-loader/babel', ]; return { diff --git a/jest.config.js b/jest.config.js index 8d66577f..7c0b82a5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -21,16 +21,11 @@ module.exports = { globals: { __DEV__: false, __VERSION__: '2.0.28', - 'ts-jest': { - tsConfig: 'tsconfig.jest.json', - babelConfig: true, - diagnostics: { - warnOnly: true, - }, - }, + }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { tsconfig: 'tsconfig.jest.json', diagnostics: { warnOnly: true } }], }, moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], - preset: 'ts-jest', moduleDirectories: ['node_modules', 'src'], moduleNameMapper: { '\\.css$': 'identity-obj-proxy', @@ -38,8 +33,9 @@ module.exports = { 'monaco-editor': '/node_modules/react-monaco-editor', '^worker-loader!': '/src/__mocks__/worker.ts', }, - snapshotSerializers: ['enzyme-to-json/serializer', 'jest-emotion'], - setupFiles: ['raf/polyfill', '/src/utils/tests/setupFiles.ts'], - setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect', 'jest-extended'], - testURL: 'http://localhost/', + snapshotSerializers: ['jest-emotion'], + setupFiles: ['/src/utils/tests/setupFiles.ts'], + setupFilesAfterEnv: ['@testing-library/jest-dom', 'jest-extended/all'], + testEnvironment: 'jsdom', + testEnvironmentOptions: { url: 'http://localhost/' }, }; diff --git a/package.json b/package.json index 59b35a31..ed589fb6 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,11 @@ "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.7.7", "@emotion/babel-preset-css-prop": "^10.0.14", + "@faker-js/faker": "^9", "@octokit/rest": "^16.33.0", - "@testing-library/jest-dom": "^5.1.0", - "@testing-library/react": "^9.4.0", - "@types/enzyme": "^3.10.3", - "@types/enzyme-adapter-react-16": "^1.0.5", - "@types/enzyme-to-json": "^1.5.3", + "@testing-library/jest-dom": "^6", + "@testing-library/react": "^14", + "@testing-library/user-event": "^14", "@types/faker": "^4.1.5", "@types/fetch-mock": "^7.3.2", "@types/jest": "^25.1.1", @@ -66,16 +65,13 @@ "@typescript-eslint/eslint-plugin": "^4.11.0", "@typescript-eslint/parser": "^4.11.0", "babel-eslint": "^10.0.3", - "babel-jest": "^25.1.0", + "babel-jest": "^29", "babel-loader": "^8.0.0", "babel-plugin-dynamic-import-node": "^2.3.0", "babel-plugin-polished": "^1.1.0", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", "codecov": "^3.7.1", "css-loader": "^3.1.0", - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.14.0", - "enzyme-to-json": "^3.3.5", "eslint": "^7.16.0", "eslint-config-airbnb-typescript": "^12.0.0", "eslint-config-prettier": "^7.1.0", @@ -89,21 +85,20 @@ "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^4.2.0", "eslint-plugin-testing-library": "^2.2.0", - "faker": "^4.1.0", "fetch-mock": "^9.0.0-beta.2", "file-loader": "^5.0.2", "fork-ts-checker-webpack-plugin": "^4.0.3", "html-webpack-plugin": "^3.2.0", "identity-obj-proxy": "^3.0.0", - "jest": "^25.1.0", + "jest": "^29", "jest-emotion": "^10.0.27", - "jest-extended": "^0.11.5", + "jest-environment-jsdom": "^29", + "jest-extended": "^4", "mini-css-extract-plugin": "^0.9.0", "prettier": "^3.0.0", - "raf": "^3.4.1", "react-test-renderer": "^18", "style-loader": "^1.1.3", - "ts-jest": "^25.1.0", + "ts-jest": "^29", "ts-loader": "^6.0.4", "typescript": "^5.5.0", "url-loader": "^3.0.0", diff --git a/src/core/auth/LoginCard.spec.tsx b/src/core/auth/LoginCard.spec.tsx index 83818206..78ca4299 100644 --- a/src/core/auth/LoginCard.spec.tsx +++ b/src/core/auth/LoginCard.spec.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { cleanup, fireEvent } from '@testing-library/react'; -import * as fetchMock from 'fetch-mock'; +import { cleanup, fireEvent, screen, waitFor } from '@testing-library/react'; +import fetchMock from 'fetch-mock'; import { AuthContainer } from 'core/auth/hooks'; import { renderWithWrapper } from 'utils/tests'; import LoginCard from './LoginCard'; @@ -27,11 +27,12 @@ describe('core/Login/LoginCard', () => { }); it('should call handleSubmit on submit', async () => { - const { findByLabelText, getByRole } = renderWithWrapper(); - expect(await findByLabelText('login-state')).toHaveTextContent('false'); + const { container } = renderWithWrapper(); + expect(screen.getByLabelText('login-state')).toHaveTextContent('false'); - const form = getByRole('form'); + // eslint-disable-next-line testing-library/no-container, testing-library/no-node-access + const form = container.querySelector('form')!; fireEvent.submit(form); - expect(await findByLabelText('login-state')).toHaveTextContent('true'); + await waitFor(() => expect(screen.getByLabelText('login-state')).toHaveTextContent('true')); }); }); diff --git a/src/core/auth/__snapshots__/Login.spec.tsx.snap b/src/core/auth/__snapshots__/Login.spec.tsx.snap index 0ca76af5..12d86b11 100644 --- a/src/core/auth/__snapshots__/Login.spec.tsx.snap +++ b/src/core/auth/__snapshots__/Login.spec.tsx.snap @@ -1,13 +1,15 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`core/auth/Login renders correctly when logged in 1`] = ` +exports[`core/auth/Login renders correctly when logged in 1`] = `null`; + +exports[`core/auth/Login renders correctly when logged out 1`] = `
`; - -exports[`core/auth/Login renders correctly when logged out 1`] = ` -
-
-
-
-
-
-
- -
- -
-
-
- -
- -
-
-
-
- -
- -
-
-`; diff --git a/src/core/auth/__snapshots__/Splash.spec.tsx.snap b/src/core/auth/__snapshots__/Splash.spec.tsx.snap index cb642913..d105ee63 100644 --- a/src/core/auth/__snapshots__/Splash.spec.tsx.snap +++ b/src/core/auth/__snapshots__/Splash.spec.tsx.snap @@ -7,7 +7,7 @@ exports[`pages/splash renders correctly 1`] = ` />
({ - originalUrl: internet.url(), - title: random.words(), - quality: random.words(), + originalUrl: faker.internet.url(), + title: faker.word.words(), + quality: faker.word.words(), }); export const withMovieRawEntry = (e: RawEntry) => ({ ...e, - movieName: random.words(), - movieYear: date.past().getFullYear(), + movieName: faker.word.words(), + movieYear: faker.date.past().getFullYear(), }); export const withIMDBFields = (e: RawMovieEntry) => ({ ...e, - [IMDBFields.Genres]: random.words().split(' '), - [IMDBFields.Posters]: image.imageUrl(), - [IMDBFields.Rating]: random.number(10), - [IMDBFields.Votes]: random.number(100), - [IMDBFields.Description]: lorem.paragraph(), - [IMDBFields.Url]: internet.url(), - [IMDBFields.Runtime]: random.number(150), - [IMDBFields.ID]: random.word(), + [IMDBFields.Genres]: faker.word.words().split(' '), + [IMDBFields.Posters]: faker.image.url(), + [IMDBFields.Rating]: faker.number.int({ max: 10 }), + [IMDBFields.Votes]: faker.number.int({ max: 100 }), + [IMDBFields.Description]: faker.lorem.paragraph(), + [IMDBFields.Url]: faker.internet.url(), + [IMDBFields.Runtime]: faker.number.int({ max: 150 }), + [IMDBFields.ID]: faker.word.sample(), }); export const withTraktFields = (e: RawMovieEntry) => ({ ...e, - [TraktFields.Genres]: random.words().split(' '), - [TraktFields.Rating]: random.number(10), - [TraktFields.Votes]: random.number(100), - [TraktFields.Description]: lorem.paragraph(), - [TraktFields.Url]: internet.url(), - [TraktFields.ID]: random.word(), - [TraktFields.Runtime]: random.number(150), + [TraktFields.Genres]: faker.word.words().split(' '), + [TraktFields.Rating]: faker.number.int({ max: 10 }), + [TraktFields.Votes]: faker.number.int({ max: 100 }), + [TraktFields.Description]: faker.lorem.paragraph(), + [TraktFields.Url]: faker.internet.url(), + [TraktFields.ID]: faker.word.sample(), + [TraktFields.Runtime]: faker.number.int({ max: 150 }), }); export const withTMDBFields = (e: RawMovieEntry) => ({ ...e, - [TMDBFields.Genres]: random.words().split(' '), - [TMDBFields.Posters]: image.imageUrl(), - [TMDBFields.Backdrops]: image.imageUrl(), - [TMDBFields.Runtime]: random.number(150), - [TMDBFields.Rating]: random.number(10), - [TMDBFields.Votes]: random.number(100), - [TMDBFields.Url]: internet.url(), - [TMDBFields.ID]: random.word(), + [TMDBFields.Genres]: faker.word.words().split(' '), + [TMDBFields.Posters]: faker.image.url(), + [TMDBFields.Backdrops]: faker.image.url(), + [TMDBFields.Runtime]: faker.number.int({ max: 150 }), + [TMDBFields.Rating]: faker.number.int({ max: 10 }), + [TMDBFields.Votes]: faker.number.int({ max: 100 }), + [TMDBFields.Url]: faker.internet.url(), + [TMDBFields.ID]: faker.word.sample(), }); export const withEpisodeRawEntry = (e: RawEntry): RawEpisodeEntry => ({ ...e, seriesEpisode: 1, seriesSeason: 2, - seriesName: random.word(), + seriesName: faker.word.sample(), seriesId: 'S02E01', }); export const withTraktEpisodeFields = (e: RawEpisodeEntry) => ({ ...e, - [TraktEpisodeFields.Name]: random.words(), - [TraktEpisodeFields.Description]: lorem.paragraph(), - [TraktEpisodeFields.ID]: random.word(), + [TraktEpisodeFields.Name]: faker.word.words(), + [TraktEpisodeFields.Description]: faker.lorem.paragraph(), + [TraktEpisodeFields.ID]: faker.word.sample(), }); export const withTVDBEpisodeFields = (e: RawEpisodeEntry) => ({ ...e, - [TVDBEpisodeFields.Image]: image.imageUrl(), - [TVDBEpisodeFields.Description]: lorem.paragraph(), - [TVDBEpisodeFields.ID]: random.word(), + [TVDBEpisodeFields.Image]: faker.image.url(), + [TVDBEpisodeFields.Description]: faker.lorem.paragraph(), + [TVDBEpisodeFields.ID]: faker.word.sample(), }); export const withTVMazeEpisodeFields = (e: RawEpisodeEntry) => ({ ...e, - [TVMazeEpisodeFields.Name]: random.words(), - [TVMazeEpisodeFields.Image]: image.imageUrl(), - [TVMazeEpisodeFields.Description]: lorem.paragraph(), - [TVMazeEpisodeFields.Url]: internet.url(), - [TVMazeEpisodeFields.ID]: random.word(), + [TVMazeEpisodeFields.Name]: faker.word.words(), + [TVMazeEpisodeFields.Image]: faker.image.url(), + [TVMazeEpisodeFields.Description]: faker.lorem.paragraph(), + [TVMazeEpisodeFields.Url]: faker.internet.url(), + [TVMazeEpisodeFields.ID]: faker.word.sample(), }); export const withSeriesRawEntry = (e: RawEntry) => ({ ...e, - seriesName: random.word(), - seriesYear: date.past().getFullYear(), + seriesName: faker.word.sample(), + seriesYear: faker.date.past().getFullYear(), }); export const withTraktSeriesFields = (e: RawSeriesEntry) => ({ ...e, - [TraktSeriesFields.Genres]: random.words().split(' '), - [TraktSeriesFields.Rating]: random.number(10), - [TraktSeriesFields.Votes]: random.number(100), - [TraktSeriesFields.Description]: lorem.paragraph(), - [TraktSeriesFields.ID]: random.word(), - [TraktSeriesFields.ContentRating]: random.words(), - [TraktSeriesFields.Url]: internet.url(), - [TraktSeriesFields.Network]: random.words(), + [TraktSeriesFields.Genres]: faker.word.words().split(' '), + [TraktSeriesFields.Rating]: faker.number.int({ max: 10 }), + [TraktSeriesFields.Votes]: faker.number.int({ max: 100 }), + [TraktSeriesFields.Description]: faker.lorem.paragraph(), + [TraktSeriesFields.ID]: faker.word.sample(), + [TraktSeriesFields.ContentRating]: faker.word.words(), + [TraktSeriesFields.Url]: faker.internet.url(), + [TraktSeriesFields.Network]: faker.word.words(), }); export const withTVDBSeriesFields = (e: RawSeriesEntry) => ({ ...e, - [TVDBSeriesFields.Genres]: random.words().split(' '), - [TVDBSeriesFields.Posters]: image.imageUrl(), - [TVDBSeriesFields.Rating]: random.number(10), - [TVDBSeriesFields.Description]: lorem.paragraph(), - [TVDBSeriesFields.Url]: internet.url(), - [TVDBSeriesFields.Backdrops]: image.imageUrl(), - [TVDBSeriesFields.ContentRating]: random.words(), - [TVDBSeriesFields.Network]: random.words(), - [TVDBSeriesFields.ID]: random.word(), + [TVDBSeriesFields.Genres]: faker.word.words().split(' '), + [TVDBSeriesFields.Posters]: faker.image.url(), + [TVDBSeriesFields.Rating]: faker.number.int({ max: 10 }), + [TVDBSeriesFields.Description]: faker.lorem.paragraph(), + [TVDBSeriesFields.Url]: faker.internet.url(), + [TVDBSeriesFields.Backdrops]: faker.image.url(), + [TVDBSeriesFields.ContentRating]: faker.word.words(), + [TVDBSeriesFields.Network]: faker.word.words(), + [TVDBSeriesFields.ID]: faker.word.sample(), }); export const withTVMazeSeriesFields = (e: RawSeriesEntry) => ({ ...e, - [TVMazeSeriesFields.Genres]: random.words().split(' '), - [TVMazeSeriesFields.Posters]: image.imageUrl(), - [TVMazeSeriesFields.Rating]: random.number(10), - [TVMazeSeriesFields.Description]: lorem.paragraph(), - [TVMazeSeriesFields.Url]: internet.url(), - [TVMazeSeriesFields.Network]: random.words(), - [TVMazeSeriesFields.ID]: random.word(), + [TVMazeSeriesFields.Genres]: faker.word.words().split(' '), + [TVMazeSeriesFields.Posters]: faker.image.url(), + [TVMazeSeriesFields.Rating]: faker.number.int({ max: 10 }), + [TVMazeSeriesFields.Description]: faker.lorem.paragraph(), + [TVMazeSeriesFields.Url]: faker.internet.url(), + [TVMazeSeriesFields.Network]: faker.word.words(), + [TVMazeSeriesFields.ID]: faker.word.sample(), }); diff --git a/src/core/layout/AppBar/AppBar.spec.tsx b/src/core/layout/AppBar/AppBar.spec.tsx index 110bb643..c6374dfc 100644 --- a/src/core/layout/AppBar/AppBar.spec.tsx +++ b/src/core/layout/AppBar/AppBar.spec.tsx @@ -1,40 +1,29 @@ import React, { FC, useEffect } from 'react'; -import { mount } from 'enzyme'; -import { IconButton } from '@material-ui/core'; import { Check } from '@material-ui/icons'; -import { BaseProviders } from 'utils/tests'; -import AppBar from './index'; +import { fireEvent, screen } from '@testing-library/react'; +import { renderWithWrapper } from 'utils/tests'; import { useInjectPageTitle, useInjectContent, useContextualAppBar, ContextualProps, } from './hooks'; +import AppBar from './index'; describe('core/layout/AppBar', () => { it('calls toggle when the hamburger button is pressed', () => { const toggle = jest.fn(); - const wrapper = mount( - - - , - ); + renderWithWrapper(); - wrapper - .findWhere(el => el.props()['aria-label'] === 'toggle sidebar' && el.type() === IconButton) - .simulate('click'); + fireEvent.click(screen.getByRole('button', { name: 'toggle sidebar' })); expect(toggle).toHaveBeenCalled(); }); it('renders correct title', () => { - const wrapper = mount( - - - , - ); + renderWithWrapper(); - expect(wrapper.text()).toBe('Flexget Manager'); + expect(screen.getByText('Flexget Manager')).toBeInTheDocument(); }); describe('injectable app bar', () => { @@ -44,31 +33,31 @@ describe('core/layout/AppBar', () => { return null; }; - const wrapper = mount( - + renderWithWrapper( + <> - , + , ); - expect(wrapper.text()).toBe('New Title'); + expect(screen.getByText('New Title')).toBeInTheDocument(); }); - it('will render additonal content', () => { - const content =
Hello
; + it('will render additional content', () => { + const injectedContent =
Hello
; const OverrideContent: FC = () => { - useInjectContent(content); + useInjectContent(injectedContent); return null; }; - const wrapper = mount( - + renderWithWrapper( + <> - , + , ); - expect(wrapper.text()).toInclude('Hello'); + expect(screen.getByText('Hello')).toBeInTheDocument(); }); }); @@ -102,71 +91,55 @@ describe('core/layout/AppBar', () => { describe('enabled', () => { it('calls closeFn when the button is pressed', () => { - const wrapper = mount( - + renderWithWrapper( + <> - , + , ); - wrapper - .findWhere(el => el.props()['aria-label'] === 'close context' && el.type() === IconButton) - .simulate('click'); + fireEvent.click(screen.getByRole('button', { name: 'close context' })); expect(onClose).toHaveBeenCalled(); }); it('renders normal content when no contextualProps are specified', () => { - const wrapper = mount( - + renderWithWrapper( + <> - , + , ); - expect(wrapper.text()).toInclude('Flexget Manager'); - expect( - wrapper - .findWhere(el => el.props()['aria-label'] === 'My Icon' && el.type() === IconButton) - .exists(), - ).toBeFalse(); + expect(screen.getByText('Flexget Manager')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'My Icon' })).not.toBeInTheDocument(); }); }); + describe('not enabled', () => { it('renders normal content when not enabled', () => { - const wrapper = mount( - + renderWithWrapper( + <> - , + , ); - expect(wrapper.text()).toInclude('Flexget Manager'); - expect( - wrapper - .findWhere(el => el.props()['aria-label'] === 'My Icon' && el.type() === IconButton) - .exists(), - ).toBeFalse(); + + expect(screen.getByText('Flexget Manager')).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'My Icon' })).not.toBeInTheDocument(); }); }); - it('render contextual content when enabled', () => { - const wrapper = mount( - + it('renders contextual content when enabled', () => { + renderWithWrapper( + <> - , + , ); - expect( - wrapper - .findWhere(el => el.props()['aria-label'] === 'My Icon' && el.type() === IconButton) - .exists(), - ).toBeTrue(); - expect( - wrapper - .findWhere(el => el.props()['aria-label'] === 'config editor' && el.type() === IconButton) - .exists(), - ).toBeFalse(); + expect(screen.getByRole('button', { name: 'My Icon' })).toBeInTheDocument(); + expect(screen.queryByRole('button', { name: 'config editor' })).not.toBeInTheDocument(); }); }); }); diff --git a/src/core/layout/Layout.spec.tsx b/src/core/layout/Layout.spec.tsx index bfb22b0d..e2d2304b 100644 --- a/src/core/layout/Layout.spec.tsx +++ b/src/core/layout/Layout.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import * as fetchMock from 'fetch-mock'; +import fetchMock from 'fetch-mock'; import { act, create, ReactTestRenderer } from 'react-test-renderer'; import { BaseProviders } from 'utils/tests'; import Layout from './Layout'; diff --git a/src/core/layout/SideNav/Version.spec.tsx b/src/core/layout/SideNav/Version.spec.tsx index 9027a9bd..1ce1b7b9 100644 --- a/src/core/layout/SideNav/Version.spec.tsx +++ b/src/core/layout/SideNav/Version.spec.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import * as fetchMock from 'fetch-mock'; +import fetchMock from 'fetch-mock'; import { cleanup } from '@testing-library/react'; import { renderWithWrapper } from 'utils/tests'; import Version from './Version'; diff --git a/src/core/layout/SideNav/index.spec.tsx b/src/core/layout/SideNav/index.spec.tsx index 2cf37131..bb3dbd00 100644 --- a/src/core/layout/SideNav/index.spec.tsx +++ b/src/core/layout/SideNav/index.spec.tsx @@ -1,7 +1,7 @@ import React from 'react'; import renderer from 'react-test-renderer'; import { themed, router } from 'utils/tests'; -import * as fetchMock from 'fetch-mock'; +import fetchMock from 'fetch-mock'; import SideNav from './index'; describe('core/layout/SideNav', () => { diff --git a/src/core/layout/__snapshots__/Layout.spec.tsx.snap b/src/core/layout/__snapshots__/Layout.spec.tsx.snap index b217a194..3b77f14f 100644 --- a/src/core/layout/__snapshots__/Layout.spec.tsx.snap +++ b/src/core/layout/__snapshots__/Layout.spec.tsx.snap @@ -9,7 +9,7 @@ exports[`common/layout renders correctly 1`] = `