From b9c9a10a788cbc0afc6d5667d42df27e5724577a Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Tue, 7 Jan 2025 14:41:45 +0100 Subject: [PATCH 1/7] start by using correct ignore --- src/components/authentication/utils/authService.ts | 4 ++-- src/components/flatParameters/FlatParameters.tsx | 2 +- .../inputs/reactHookForm/DirectoryItemsInput.tsx | 2 +- .../notifications/test/NotificationsProvider.test.tsx | 10 +++++----- src/components/topBar/TopBar.tsx | 6 +++--- src/hooks/useLocalizedCountries.ts | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/authentication/utils/authService.ts b/src/components/authentication/utils/authService.ts index 77f97b1e..e792d30c 100644 --- a/src/components/authentication/utils/authService.ts +++ b/src/components/authentication/utils/authService.ts @@ -40,7 +40,7 @@ type CustomUserManager = UserManager & { }; // set as a global variable to allow log level configuration at runtime -// @ts-ignore +// @ts-expect-error window.OIDCLog = Log; const hackAuthorityKey = 'oidc.hack.authority'; @@ -69,7 +69,7 @@ function reload() { function reloadTimerOnExpiresIn(user: User, userManager: UserManager, expiresIn: number) { // Not allowed by TS because expires_in is supposed to be readonly - // @ts-ignore + // @ts-expect-error // eslint-disable-next-line no-param-reassign user.expires_in = expiresIn; userManager.storeUser(user).then(() => { diff --git a/src/components/flatParameters/FlatParameters.tsx b/src/components/flatParameters/FlatParameters.tsx index d7762760..c577f0d0 100644 --- a/src/components/flatParameters/FlatParameters.tsx +++ b/src/components/flatParameters/FlatParameters.tsx @@ -382,7 +382,7 @@ export function FlatParameters({ /> ); - // @ts-ignore fallthrough is the expected behavior + // @ts-expect-error fallthrough is the expected behavior case 'STRING': if (param.possibleValues) { return ( diff --git a/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx b/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx index 129c6212..1557cd42 100644 --- a/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx +++ b/src/components/inputs/reactHookForm/DirectoryItemsInput.tsx @@ -169,7 +169,7 @@ export function DirectoryItemsInput({ { } const reconnectingWebSocketClass = {}; - // @ts-ignore + // @ts-expect-error ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { @@ -78,7 +78,7 @@ describe('NotificationsProvider', () => { }); const event = { test: 'test' }; act(() => { - // @ts-ignore + // @ts-expect-error reconnectingWebSocketClass.onmessage(event); }); @@ -95,7 +95,7 @@ describe('NotificationsProvider', () => { } const reconnectingWebSocketClass = {}; - // @ts-ignore + // @ts-expect-error ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { @@ -106,7 +106,7 @@ describe('NotificationsProvider', () => { ); }); act(() => { - // @ts-ignore + // @ts-expect-error reconnectingWebSocketClass.onmessage({ test: 'test' }); }); @@ -126,7 +126,7 @@ describe('NotificationsProvider', () => { return

empty

; } - // @ts-ignore + // @ts-expect-error ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { diff --git a/src/components/topBar/TopBar.tsx b/src/components/topBar/TopBar.tsx index 2c29d1dc..bc96ed03 100644 --- a/src/components/topBar/TopBar.tsx +++ b/src/components/topBar/TopBar.tsx @@ -261,7 +261,7 @@ export function TopBar({ const [isAboutDialogOpen, setAboutDialogOpen] = useState(false); const onAboutClicked = () => { - // @ts-ignore should be an Element, or maybe we should use null ? + // @ts-expect-error should be an Element, or maybe we should use null ? setAnchorElSettingsMenu(false); if (onAboutClick) { onAboutClick(); @@ -289,7 +289,7 @@ export function TopBar({ ); return ( - // @ts-ignore appBar style is not defined + // @ts-expect-error appBar style is not defined {logoClickable} @@ -363,7 +363,7 @@ export function TopBar({ {user !== null ? abbreviationFromUserName( - // @ts-ignore name could be undefined, how to handle this case ? + // @ts-expect-error name could be undefined, how to handle this case ? user.profile.name ) : ''} diff --git a/src/hooks/useLocalizedCountries.ts b/src/hooks/useLocalizedCountries.ts index 0ee8b5ad..7e6210ec 100644 --- a/src/hooks/useLocalizedCountries.ts +++ b/src/hooks/useLocalizedCountries.ts @@ -7,9 +7,9 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import localizedCountries, { LocalizedCountries } from 'localized-countries'; -// @ts-ignore +// @ts-expect-error import countriesFr from 'localized-countries/data/fr'; -// @ts-ignore +// @ts-expect-error import countriesEn from 'localized-countries/data/en'; import { LANG_ENGLISH, LANG_FRENCH, LANG_SYSTEM } from '../utils/constants/browserConstants'; From 7a53e1c6c4b99256d8527510c1aae0ce9c666ac7 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 09:26:30 +0100 Subject: [PATCH 2/7] fix lib declaration --- src/hooks/useLocalizedCountries.ts | 2 -- src/module-localized-countries.d.ts | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/module-localized-countries.d.ts diff --git a/src/hooks/useLocalizedCountries.ts b/src/hooks/useLocalizedCountries.ts index 7e6210ec..bf644538 100644 --- a/src/hooks/useLocalizedCountries.ts +++ b/src/hooks/useLocalizedCountries.ts @@ -7,9 +7,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import localizedCountries, { LocalizedCountries } from 'localized-countries'; -// @ts-expect-error import countriesFr from 'localized-countries/data/fr'; -// @ts-expect-error import countriesEn from 'localized-countries/data/en'; import { LANG_ENGLISH, LANG_FRENCH, LANG_SYSTEM } from '../utils/constants/browserConstants'; diff --git a/src/module-localized-countries.d.ts b/src/module-localized-countries.d.ts new file mode 100644 index 00000000..6f541a96 --- /dev/null +++ b/src/module-localized-countries.d.ts @@ -0,0 +1,16 @@ +/* + * Copyright © 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +declare module 'localized-countries/data/en' { + const countries: Readonly; + export default countries; +} + +declare module 'localized-countries/data/fr' { + const countries: Readonly; + export default countries; +} From 95f9fa7265efeee5175ce11f277ade4163a63093 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 11:56:09 +0100 Subject: [PATCH 3/7] remove unused library was used with react-virtualized --- package-lock.json | 6 ------ package.json | 1 - 2 files changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index f577277d..1f49fe72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,6 @@ "clsx": "^2.1.0", "jwt-decode": "^4.0.0", "localized-countries": "^2.0.0", - "memoize-one": "^6.0.0", "oidc-client": "^1.11.5", "prop-types": "^15.8.1", "react-beautiful-dnd": "^13.1.1", @@ -12910,11 +12909,6 @@ "tmpl": "1.0.5" } }, - "node_modules/memoize-one": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index fd06df19..0f7822f2 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,6 @@ "clsx": "^2.1.0", "jwt-decode": "^4.0.0", "localized-countries": "^2.0.0", - "memoize-one": "^6.0.0", "oidc-client": "^1.11.5", "prop-types": "^15.8.1", "react-beautiful-dnd": "^13.1.1", From fcd2091f4ed27384ce2078c98bd6b5acee838e60 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 13:56:40 +0100 Subject: [PATCH 4/7] reduce imports dependencies to oidc-client --- .../authentication/SignInCallbackHandler.tsx | 2 +- .../authentication/SilentRenewCallbackHandler.tsx | 2 +- .../authentication/authenticationType.ts | 2 +- .../authentication/utils/userManagerMock.ts | 14 +++++++------- src/components/topBar/TopBar.tsx | 2 +- src/components/topBar/UserInformationDialog.tsx | 2 +- src/redux/actions/authActions.ts | 2 +- src/redux/commonStore.ts | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/components/authentication/SignInCallbackHandler.tsx b/src/components/authentication/SignInCallbackHandler.tsx index 59e35507..c956222c 100644 --- a/src/components/authentication/SignInCallbackHandler.tsx +++ b/src/components/authentication/SignInCallbackHandler.tsx @@ -6,7 +6,7 @@ */ import { useEffect } from 'react'; -import { UserManager } from 'oidc-client'; +import type { UserManager } from 'oidc-client'; export interface SignInCallbackHandlerProps { userManager: UserManager | null; diff --git a/src/components/authentication/SilentRenewCallbackHandler.tsx b/src/components/authentication/SilentRenewCallbackHandler.tsx index 8e2e8cd5..37152ed2 100644 --- a/src/components/authentication/SilentRenewCallbackHandler.tsx +++ b/src/components/authentication/SilentRenewCallbackHandler.tsx @@ -6,7 +6,7 @@ */ import { useEffect } from 'react'; -import { UserManager } from 'oidc-client'; +import type { UserManager } from 'oidc-client'; export interface SilentRenewCallbackHandlerProps { userManager: UserManager | null; diff --git a/src/components/authentication/authenticationType.ts b/src/components/authentication/authenticationType.ts index 5b88fe8d..b74be8f8 100644 --- a/src/components/authentication/authenticationType.ts +++ b/src/components/authentication/authenticationType.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { UserManager } from 'oidc-client'; +import type { UserManager } from 'oidc-client'; import { Dispatch } from 'react'; import { NavigateFunction, Location } from 'react-router-dom'; import { AuthenticationActions } from '../../redux/actions/authActions'; diff --git a/src/components/authentication/utils/userManagerMock.ts b/src/components/authentication/utils/userManagerMock.ts index 3ff604af..bcbc5359 100644 --- a/src/components/authentication/utils/userManagerMock.ts +++ b/src/components/authentication/utils/userManagerMock.ts @@ -8,15 +8,15 @@ /* eslint-disable max-classes-per-file, class-methods-use-this */ import { - MetadataService, - SigninRequest, - SigninResponse, - SignoutRequest, - SignoutResponse, - User, + type MetadataService, + type SigninRequest, + type SigninResponse, + type SignoutRequest, + type SignoutResponse, + type User, UserManager, UserManagerEvents, - UserManagerSettings, + type UserManagerSettings, } from 'oidc-client'; class Events implements UserManagerEvents { diff --git a/src/components/topBar/TopBar.tsx b/src/components/topBar/TopBar.tsx index bc96ed03..dad58c7d 100644 --- a/src/components/topBar/TopBar.tsx +++ b/src/components/topBar/TopBar.tsx @@ -44,7 +44,7 @@ import { } from '@mui/icons-material'; import { styled } from '@mui/system'; -import { User } from 'oidc-client'; +import type { User } from 'oidc-client'; import { GridLogo, GridLogoProps } from './GridLogo'; import { AboutDialog, AboutDialogProps } from './AboutDialog'; import { LogoutProps } from '../authentication/Logout'; diff --git a/src/components/topBar/UserInformationDialog.tsx b/src/components/topBar/UserInformationDialog.tsx index 91cfcf97..ece8e948 100644 --- a/src/components/topBar/UserInformationDialog.tsx +++ b/src/components/topBar/UserInformationDialog.tsx @@ -8,7 +8,7 @@ import { Dialog, DialogActions, DialogContent, DialogTitle, Grid, Typography } from '@mui/material'; import { Box } from '@mui/system'; import { FormattedMessage } from 'react-intl'; -import { User } from 'oidc-client'; +import type { User } from 'oidc-client'; import { useEffect, useState } from 'react'; import { CancelButton } from '../inputs/reactHookForm/utils/CancelButton'; import fetchUserDetails from '../../services/userAdmin'; diff --git a/src/redux/actions/authActions.ts b/src/redux/actions/authActions.ts index 298cf5bd..de44bbcd 100644 --- a/src/redux/actions/authActions.ts +++ b/src/redux/actions/authActions.ts @@ -4,7 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { User } from 'oidc-client'; +import type { User } from 'oidc-client'; // Is redux or isn't redux, that is the question, 🎭 export interface Action { diff --git a/src/redux/commonStore.ts b/src/redux/commonStore.ts index d9a53c09..efc402bc 100644 --- a/src/redux/commonStore.ts +++ b/src/redux/commonStore.ts @@ -5,7 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { User } from 'oidc-client'; +import type { User } from 'oidc-client'; export type CommonStoreState = { user: User | null; From 664b4977e1d87624d7035d68653b1cc6a3a72316 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 15:05:45 +0100 Subject: [PATCH 5/7] clean a little classes --- .../authentication/utils/userManagerMock.ts | 163 ++++++++---------- .../cardErrorBoundary/CardErrorBoundary.tsx | 26 ++- .../agGridTable/cellEditors/numericEditor.ts | 37 ++-- src/tests/testsUtils.test.tsx | 55 +++--- 4 files changed, 128 insertions(+), 153 deletions(-) diff --git a/src/components/authentication/utils/userManagerMock.ts b/src/components/authentication/utils/userManagerMock.ts index bcbc5359..121d82ca 100644 --- a/src/components/authentication/utils/userManagerMock.ts +++ b/src/components/authentication/utils/userManagerMock.ts @@ -9,6 +9,9 @@ import { type MetadataService, + type OidcClientSettings, + type Profile, + type SessionStatus, type SigninRequest, type SigninResponse, type SignoutRequest, @@ -20,22 +23,23 @@ import { } from 'oidc-client'; class Events implements UserManagerEvents { - userLoadedCallbacks: ((data: any) => void)[] = []; + public userLoadedCallbacks: UserManagerEvents.UserLoadedCallback[] = []; - addUserLoaded(callback: (data: any) => void) { - this.userLoadedCallbacks.push(callback); - } + load() {} - // eslint-disable-next-line - addSilentRenewError(callback: (data: any) => void) { - // Nothing to do - } + unload() {} - load(/* container: User */) { - // not implemented - } + addAccessTokenExpiring() {} - unload() {} + removeAccessTokenExpiring() {} + + addAccessTokenExpired() {} + + removeAccessTokenExpired() {} + + addUserLoaded(callback: UserManagerEvents.UserLoadedCallback) { + this.userLoadedCallbacks.push(callback); + } removeUserLoaded() {} @@ -43,6 +47,8 @@ class Events implements UserManagerEvents { removeUserUnloaded() {} + addSilentRenewError() {} + removeSilentRenewError() {} addUserSignedIn() {} @@ -56,23 +62,15 @@ class Events implements UserManagerEvents { addUserSessionChanged() {} removeUserSessionChanged() {} - - addAccessTokenExpiring() {} - - removeAccessTokenExpiring() {} - - addAccessTokenExpired() {} - - removeAccessTokenExpired() {} } export class UserManagerMock implements UserManager { - settings; + events: Events; - events; + readonly settings: OidcClientSettings; - user: User = { - profile: { + private static readonly user = Object.freeze({ + profile: Object.freeze({ name: 'John Doe', email: 'Jhon.Doe@rte-france.com', iss: '', @@ -80,7 +78,7 @@ export class UserManagerMock implements UserManager { aud: '', exp: Number.MAX_SAFE_INTEGER, iat: 0, - }, + }), id_token: 'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IllNRUxIVDBndmIwbXhvU0RvWWZvbWpxZmpZVSJ9.eyJhdWQiOiI5YzQwMjQ2MS1iMmFiLTQ3NjctOWRiMy02Njg1OWJiMGZjZDAiLCJpc3MiOiJodHRwczovL2' + 'xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vNzUwMmRhZDUtZDY0Yy00NmM3LTlkNDctYjE2ZjU4MGZjZmE5L3YyLjAiLCJpYXQiOjE1ODUzMzEyNDksIm5iZiI6MTU4NTMzMTI0OSwiZXhwIjoyNTg1MzM1MTQ5LCJhaW8iOiJBV1FB' + @@ -111,132 +109,123 @@ export class UserManagerMock implements UserManager { expired: false, state: null, toStorageString: () => 'Mock of UserManager', - }; + }); + + readonly metadataService = null as unknown as MetadataService; - readonly metadataService: MetadataService = null as unknown as MetadataService; + private static readonly STORAGE_KEY = 'powsybl-gridsuite-mock-user'; constructor(settings: UserManagerSettings) { this.settings = settings; this.events = new Events(); } - // eslint-disable-next-line class-methods-use-this - getUser() { - return Promise.resolve(JSON.parse(sessionStorage.getItem('powsybl-gridsuite-mock-user') ?? 'null')); + async getUser() { + return JSON.parse(sessionStorage.getItem(UserManagerMock.STORAGE_KEY) ?? 'null') as User | null; } - async signinSilent(): Promise { + async signinSilent() { console.info('signinSilent..............'); - const localStorageUser = JSON.parse(localStorage.getItem('powsybl-gridsuite-mock-user') ?? 'null'); + const localStorageUser = JSON.parse(localStorage.getItem(UserManagerMock.STORAGE_KEY) ?? 'null') as User | null; if (localStorageUser === null) { throw new Error('End-User authentication required'); } - sessionStorage.setItem('powsybl-gridsuite-mock-user', JSON.stringify(localStorageUser)); + sessionStorage.setItem(UserManagerMock.STORAGE_KEY, JSON.stringify(localStorageUser)); this.events.userLoadedCallbacks.forEach((c) => c(localStorageUser)); return localStorageUser; } - // eslint-disable-next-line class-methods-use-this signinSilentCallback() { console.error('Unsupported, iframe signinSilentCallback in UserManagerMock (dev mode)'); return Promise.reject(); } - signinRedirect() { - localStorage.setItem('powsybl-gridsuite-mock-user', JSON.stringify(this.user)); + async signinRedirect() { + localStorage.setItem(UserManagerMock.STORAGE_KEY, JSON.stringify(UserManagerMock.user)); window.location.href = './sign-in-callback'; - return Promise.resolve(); } - // eslint-disable-next-line class-methods-use-this - signoutRedirect() { - sessionStorage.removeItem('powsybl-gridsuite-mock-user'); - localStorage.removeItem('powsybl-gridsuite-mock-user'); + async signoutRedirect() { + sessionStorage.removeItem(UserManagerMock.STORAGE_KEY); + localStorage.removeItem(UserManagerMock.STORAGE_KEY); window.location.href = '.'; - return Promise.resolve(); } - signinRedirectCallback() { - sessionStorage.setItem('powsybl-gridsuite-mock-user', JSON.stringify(this.user)); - this.events.userLoadedCallbacks.forEach((c) => c(this.user)); - return Promise.resolve(this.user); + async signinRedirectCallback() { + sessionStorage.setItem(UserManagerMock.STORAGE_KEY, JSON.stringify(UserManagerMock.user)); + this.events.userLoadedCallbacks.forEach((c) => c(UserManagerMock.user)); + return UserManagerMock.user; } - clearStaleState() { - return Promise.resolve(); + async clearStaleState() { + // do nothing (@typescript-eslint/no-empty-function). } - storeUser() { - return Promise.resolve(); + async storeUser() { + // do nothing (@typescript-eslint/no-empty-function). } - removeUser() { - return Promise.resolve(); + async removeUser() { + // do nothing (@typescript-eslint/no-empty-function). } - signinPopup() { - return Promise.resolve(this.user); + async signinPopup() { + return UserManagerMock.user; } - signinPopupCallback() { - return Promise.resolve(undefined); + async signinPopupCallback() { + return undefined; } - signoutRedirectCallback() { - return Promise.resolve({} as SignoutResponse); + async signoutRedirectCallback() { + return {} as SignoutResponse; } - signoutPopup() { - return Promise.resolve(); + async signoutPopup() { + // do nothing (@typescript-eslint/no-empty-function). } - signoutPopupCallback() { - return Promise.resolve(); + async signoutPopupCallback() { + // do nothing (@typescript-eslint/no-empty-function). } - signinCallback() { - return Promise.resolve(this.user); + async signinCallback() { + return UserManagerMock.user; } - signoutCallback() { - return Promise.resolve(undefined); + async signoutCallback() { + return undefined; } - querySessionStatus() { - return Promise.resolve({ + async querySessionStatus() { + return { session_state: '', sub: '', sid: undefined, - }); + } satisfies SessionStatus; } - revokeAccessToken() { - return Promise.resolve(); + async revokeAccessToken() { + // do nothing (@typescript-eslint/no-empty-function). } - startSilentRenew() { - return Promise.resolve(); - } + startSilentRenew() {} - stopSilentRenew() { - return Promise.resolve(); - } + stopSilentRenew() {} - createSigninRequest() { - return Promise.resolve({} as SigninRequest); + async createSigninRequest() { + return {} as SigninRequest; } - processSigninResponse() { - return Promise.resolve({} as SigninResponse); + async processSigninResponse() { + return {} as SigninResponse; } - createSignoutRequest() { - return Promise.resolve({} as SignoutRequest); + async createSignoutRequest() { + return {} as SignoutRequest; } - processSignoutResponse() { - return Promise.resolve({} as SignoutResponse); + async processSignoutResponse() { + return {} as SignoutResponse; } } - -export default UserManagerMock; diff --git a/src/components/cardErrorBoundary/CardErrorBoundary.tsx b/src/components/cardErrorBoundary/CardErrorBoundary.tsx index 9684573d..41143563 100644 --- a/src/components/cardErrorBoundary/CardErrorBoundary.tsx +++ b/src/components/cardErrorBoundary/CardErrorBoundary.tsx @@ -18,12 +18,12 @@ import { CardHeader, Collapse, IconButton, - IconButtonProps, + type IconButtonProps, + styled, Theme, Typography, - styled, } from '@mui/material'; -import { Component, ErrorInfo, ReactNode } from 'react'; +import { Component, type ErrorInfo, type PropsWithChildren } from 'react'; import { FormattedMessage } from 'react-intl'; export interface ExpandMoreProps extends IconButtonProps { @@ -43,10 +43,6 @@ const ExpandMore = styled((props: ExpandMoreProps) => { // Extracted from https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries/ for types -interface Props { - children?: ReactNode; -} - type CardErrorBoundaryStateError = { hasError: true; error: Error; @@ -60,8 +56,8 @@ type CardErrorBoundaryState = (CardErrorBoundaryStateError | CardErrorBoundarySt expanded: boolean; }; -export class CardErrorBoundary extends Component { - constructor(props: Props) { +export class CardErrorBoundary extends Component, CardErrorBoundaryState> { + constructor(props: PropsWithChildren<{}>) { super(props); this.state = { hasError: false, @@ -76,18 +72,16 @@ export class CardErrorBoundary extends Component return { hasError: true, error }; } - componentDidCatch(error: Error, errorInfo: ErrorInfo) { + override componentDidCatch(error: Error, errorInfo: ErrorInfo) { // You can also log the error to an error reporting service console.error('CardErrorBoundary caught: ', error, errorInfo); } - handleExpandClick() { - this.setState((state: CardErrorBoundaryState) => ({ - expanded: !state.expanded, - })); + private handleExpandClick() { + this.setState((state: CardErrorBoundaryState) => ({ expanded: !state.expanded })); } - handleReloadClick() { + private handleReloadClick() { this.setState(() => ({ hasError: false, expanded: false, @@ -95,7 +89,7 @@ export class CardErrorBoundary extends Component })); } - render() { + override render() { const { hasError } = this.state; const { children } = this.props; if (hasError) { diff --git a/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts b/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts index e4d8cb9a..01877572 100644 --- a/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts +++ b/src/components/inputs/reactHookForm/agGridTable/cellEditors/numericEditor.ts @@ -4,10 +4,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { ICellEditorComp, ICellEditorParams } from 'ag-grid-community'; - -// backspace starts the editor on Windows -const KEY_BACKSPACE = 'Backspace'; +import { ICellEditorComp, type ICellEditorParams, KeyCode } from 'ag-grid-community'; /** * This is a modified version of the cell editor proposed by AGGrid itself : @@ -17,9 +14,9 @@ const KEY_BACKSPACE = 'Backspace'; * https://www.ag-grid.com/react-data-grid/component-cell-editor/#cell-editor-example */ export class NumericEditor implements ICellEditorComp { - eInput!: HTMLInputElement; + private eInput!: HTMLInputElement; - cancelBeforeStart!: boolean; + private cancelBeforeStart!: boolean; // gets called once before the renderer is used init(params: ICellEditorParams) { @@ -27,7 +24,8 @@ export class NumericEditor implements ICellEditorComp { this.eInput = document.createElement('input'); this.eInput.classList.add('numeric-input'); - if (params.eventKey === KEY_BACKSPACE) { + // backspace starts the editor on Windows + if (params.eventKey === KeyCode.BACKSPACE) { this.eInput.value = ''; } else if (NumericEditor.isCharNumeric(params.eventKey)) { this.eInput.value = params.eventKey!; @@ -56,11 +54,11 @@ export class NumericEditor implements ICellEditorComp { this.cancelBeforeStart = !!isNotANumber; } - static isBackspace(event: any) { - return event.key === KEY_BACKSPACE; + private static isBackspace(event: KeyboardEvent) { + return event.key === KeyCode.BACKSPACE; } - static isNavigationKey(event: any) { + private static isNavigationKey(event: KeyboardEvent) { return event.key === 'ArrowLeft' || event.key === 'ArrowRight'; } @@ -81,24 +79,17 @@ export class NumericEditor implements ICellEditorComp { // returns the new value after editing getValue() { - const { value } = this.eInput; // FM : some modifications here - const tmp = value?.replace(',', '.') || ''; - return parseFloat(tmp) || null; + const result = parseFloat(this.eInput.value.replace(',', '.')); + return Number.isNaN(result) ? null : result; } - static isCharNumeric(charStr: string | null) { + private static isCharNumeric(charStr: string | null) { // FM : I added ',' and '.' - return charStr && !!/\d|,|\./.test(charStr); - } - - static isNumericKey(event: any) { - const charStr = event.key; - return NumericEditor.isCharNumeric(charStr); + return charStr && /\d|,|\./.test(charStr); } - // force call when focus is leaving the editor - static focusOut() { - return true; + private static isNumericKey(event: KeyboardEvent) { + return NumericEditor.isCharNumeric(event.key); } } diff --git a/src/tests/testsUtils.test.tsx b/src/tests/testsUtils.test.tsx index 04aab50b..e007c0f8 100644 --- a/src/tests/testsUtils.test.tsx +++ b/src/tests/testsUtils.test.tsx @@ -6,27 +6,27 @@ */ import { render as rtlRender } from '@testing-library/react'; -import { createTheme, ThemeProvider } from '@mui/material/styles'; +import { type ReactElement } from 'react'; +import { createTheme, type Theme, ThemeProvider } from '@mui/material'; import { IntlProvider } from 'react-intl'; -import { Theme } from '@mui/system'; import { - loginEn, - reportViewerEn, - topBarEn, - tableEn, - treeviewFinderEn, + cardErrorBoundaryEn, + commonButtonEn, + csvEn, + descriptionEn, + directoryItemsInputEn, elementSearchEn, equipmentSearchEn, + equipmentsEn, filterEn, filterExpertEn, - descriptionEn, - equipmentsEn, - csvEn, - cardErrorBoundaryEn, flatParametersEn, + loginEn, multipleSelectionDialogEn, - commonButtonEn, - directoryItemsInputEn, + reportViewerEn, + tableEn, + topBarEn, + treeviewFinderEn, } from '../translations/en'; const fullTrad = { @@ -48,11 +48,13 @@ const fullTrad = { ...commonButtonEn, ...directoryItemsInputEn, }; -const renderWithTranslation = (ui: React.ReactElement, trad: Record = fullTrad) => ( - - {ui} - -); +function renderWithTranslation(ui: ReactElement, trad: Record) { + return ( + + {ui} + + ); +} const lightTheme = createTheme( { @@ -85,19 +87,18 @@ const lightTheme = createTheme( }, } ); -const renderWithTheme = (ui: React.ReactElement, theme: Theme = lightTheme) => ( - {ui} -); +function renderWithTheme(ui: ReactElement, theme: Theme) { + return {ui}; +} -// eslint-disable-next-line import/prefer-default-export export class RenderBuilder { - renderWithTheme: boolean = false; + private renderWithTheme = false; - theme: Theme = lightTheme; + private theme = lightTheme; - renderWithTrad: boolean = false; + private renderWithTrad = false; - trad: Record = fullTrad; + private trad: Record = fullTrad; withTrad(trad?: Record) { this.renderWithTrad = true; @@ -115,7 +116,7 @@ export class RenderBuilder { return this; } - render(ui: React.ReactElement) { + render(ui: ReactElement) { let newUi = this.renderWithTrad ? renderWithTranslation(ui, this.trad) : ui; newUi = this.renderWithTheme ? renderWithTheme(newUi, this.theme) : newUi; return rtlRender(newUi); From 604633860a8e9219e5cc91e1f5005586cddcaae1 Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 17:44:23 +0100 Subject: [PATCH 6/7] fix typing of tests --- .../test/NotificationsProvider.test.tsx | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/components/notifications/test/NotificationsProvider.test.tsx b/src/components/notifications/test/NotificationsProvider.test.tsx index 5c27f7cd..abf5c08f 100644 --- a/src/components/notifications/test/NotificationsProvider.test.tsx +++ b/src/components/notifications/test/NotificationsProvider.test.tsx @@ -5,13 +5,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import { createRoot } from 'react-dom/client'; -import { waitFor, act } from '@testing-library/react'; +import { act, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, jest, test } from '@jest/globals'; import ReconnectingWebSocket from 'reconnecting-websocket'; import { NotificationsProvider } from '../NotificationsProvider'; import { useNotificationsListener } from '../hooks/useNotificationsListener'; jest.mock('reconnecting-websocket'); +const MockedReconnectingWebSocket = ReconnectingWebSocket as jest.MockedClass; + let container: Element; declare global { @@ -65,9 +67,8 @@ describe('NotificationsProvider', () => { return

empty

; } - const reconnectingWebSocketClass = {}; - // @ts-expect-error - ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); + const reconnectingWebSocketClass = {} as jest.Mocked; + MockedReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { root.render( @@ -76,10 +77,9 @@ describe('NotificationsProvider', () => { ); }); - const event = { test: 'test' }; + const event = { data: 'test' } as MessageEvent; act(() => { - // @ts-expect-error - reconnectingWebSocketClass.onmessage(event); + reconnectingWebSocketClass.onmessage?.(event); }); waitFor(() => expect(eventCallback).toBeCalledWith(event)); @@ -94,9 +94,8 @@ describe('NotificationsProvider', () => { return

empty

; } - const reconnectingWebSocketClass = {}; - // @ts-expect-error - ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); + const reconnectingWebSocketClass = {} as jest.Mocked; + MockedReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { root.render( @@ -106,8 +105,7 @@ describe('NotificationsProvider', () => { ); }); act(() => { - // @ts-expect-error - reconnectingWebSocketClass.onmessage({ test: 'test' }); + reconnectingWebSocketClass.onmessage?.({ data: 'test' } as MessageEvent); }); expect(eventCallback).not.toBeCalled(); @@ -119,15 +117,14 @@ describe('NotificationsProvider', () => { const onOpenCallback = jest.fn(); const reconnectingWebSocketClass = { onopen: onOpenCallback, - }; + } as Partial as jest.Mocked; const eventCallback = jest.fn(); function NotificationsConsumer() { useNotificationsListener('Fake_Key', { listenerCallbackOnReopen: eventCallback }); return

empty

; } - // @ts-expect-error - ReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); + MockedReconnectingWebSocket.mockImplementation(() => reconnectingWebSocketClass); act(() => { root.render( From 3451effd0a6290968eac4fde28716b993ec2b88a Mon Sep 17 00:00:00 2001 From: Tristan Chuine Date: Wed, 22 Jan 2025 17:49:59 +0100 Subject: [PATCH 7/7] resolve last ignores --- .../authentication/utils/authService.ts | 12 ++++--- .../flatParameters/FlatParameters.tsx | 28 +++++++-------- src/components/topBar/TopBar.tsx | 35 +++++++------------ 3 files changed, 34 insertions(+), 41 deletions(-) diff --git a/src/components/authentication/utils/authService.ts b/src/components/authentication/utils/authService.ts index e792d30c..71651d5a 100644 --- a/src/components/authentication/utils/authService.ts +++ b/src/components/authentication/utils/authService.ts @@ -39,8 +39,13 @@ type CustomUserManager = UserManager & { }; }; +declare global { + interface Window { + OIDCLog?: Log; + } +} + // set as a global variable to allow log level configuration at runtime -// @ts-expect-error window.OIDCLog = Log; const hackAuthorityKey = 'oidc.hack.authority'; @@ -69,9 +74,8 @@ function reload() { function reloadTimerOnExpiresIn(user: User, userManager: UserManager, expiresIn: number) { // Not allowed by TS because expires_in is supposed to be readonly - // @ts-expect-error - // eslint-disable-next-line no-param-reassign - user.expires_in = expiresIn; + // @ts-expect-error TS2540: Cannot assign to expires_in because it is a read-only property. + user.expires_in = expiresIn; // eslint-disable-line no-param-reassign userManager.storeUser(user).then(() => { userManager.getUser(); }); diff --git a/src/components/flatParameters/FlatParameters.tsx b/src/components/flatParameters/FlatParameters.tsx index c577f0d0..671dcadb 100644 --- a/src/components/flatParameters/FlatParameters.tsx +++ b/src/components/flatParameters/FlatParameters.tsx @@ -262,6 +262,7 @@ export function FlatParameters({ const renderField = (param: Parameter) => { const fieldValue = mixInitAndDefault(param); + // eslint-disable-next-line default-case switch (param.type) { case 'BOOLEAN': return onFieldChange(e.target.checked, param)} />; @@ -381,8 +382,6 @@ export function FlatParameters({ renderInput={(inputProps) => } /> ); - - // @ts-expect-error fallthrough is the expected behavior case 'STRING': if (param.possibleValues) { return ( @@ -404,20 +403,19 @@ export function FlatParameters({ ); } - // else fallthrough to default - default: - return ( - onUncommitted(param, true)} - onBlur={() => onUncommitted(param, false)} - onChange={(e) => onFieldChange(e.target.value, param)} - variant={variant} - /> - ); + // else continu/fallthrough to default } + return ( + onUncommitted(param, true)} + onBlur={() => onUncommitted(param, false)} + onChange={(e) => onFieldChange(e.target.value, param)} + variant={variant} + /> + ); }; return ( diff --git a/src/components/topBar/TopBar.tsx b/src/components/topBar/TopBar.tsx index dad58c7d..63554eb6 100644 --- a/src/components/topBar/TopBar.tsx +++ b/src/components/topBar/TopBar.tsx @@ -37,8 +37,8 @@ import { Computer as ComputerIcon, ExitToApp as ExitToAppIcon, HelpOutline as HelpOutlineIcon, - Person as PersonIcon, ManageAccounts, + Person as PersonIcon, Settings as SettingsIcon, WbSunny as WbSunnyIcon, } from '@mui/icons-material'; @@ -155,13 +155,18 @@ const CustomListItemIcon = styled(ListItemIcon)({ borderRadius: '25px', }); -const EN = 'EN'; -const FR = 'FR'; - export type GsLangUser = typeof LANG_ENGLISH | typeof LANG_FRENCH; export type GsLang = GsLangUser | typeof LANG_SYSTEM; export type GsTheme = typeof LIGHT_THEME | typeof DARK_THEME; +function abbreviationFromUserName(name: string) { + const tab = name.split(' ').map((x) => x.charAt(0)); + if (tab.length === 1) { + return tab[0]; + } + return tab[0] + tab[tab.length - 1]; +} + export type TopBarProps = Omit & Omit & Omit & { @@ -233,14 +238,6 @@ export function TopBar({ } }; - const abbreviationFromUserName = (name: string) => { - const tab = name.split(' ').map((x) => x.charAt(0)); - if (tab.length === 1) { - return tab[0]; - } - return tab[0] + tab[tab.length - 1]; - }; - const changeTheme = (_: MouseEvent, value: GsTheme) => { if (onThemeClick && value !== null) { onThemeClick(value); @@ -261,8 +258,7 @@ export function TopBar({ const [isAboutDialogOpen, setAboutDialogOpen] = useState(false); const onAboutClicked = () => { - // @ts-expect-error should be an Element, or maybe we should use null ? - setAnchorElSettingsMenu(false); + setAnchorElSettingsMenu(null); if (onAboutClick) { onAboutClick(); } else { @@ -361,12 +357,7 @@ export function TopBar({ style={anchorElSettingsMenu ? { cursor: 'initial' } : { cursor: 'pointer' }} > - {user !== null - ? abbreviationFromUserName( - // @ts-expect-error name could be undefined, how to handle this case ? - user.profile.name - ) - : ''} + {user.profile.name !== undefined ? abbreviationFromUserName(user.profile.name) : ''} {anchorElSettingsMenu ? ( @@ -512,14 +503,14 @@ export function TopBar({ aria-label={LANG_ENGLISH} sx={styles.languageToggleButton} > - {EN} + EN - {FR} + FR