diff --git a/.changeset/warm-items-sell.md b/.changeset/warm-items-sell.md new file mode 100644 index 0000000000..8f18c89687 --- /dev/null +++ b/.changeset/warm-items-sell.md @@ -0,0 +1,11 @@ +--- +'@rocket.chat/fuselage-toastbar': minor +'@rocket.chat/fuselage-hooks': minor +'@rocket.chat/onboarding-ui': minor +'@rocket.chat/fuselage': minor +'@rocket.chat/layout': minor +'@rocket.chat/styled': minor +'@rocket.chat/logo': minor +--- + +feat: Raise minimum supported React version to 18 diff --git a/packages/fuselage-hooks/jest.config.ts b/packages/fuselage-hooks/jest.config.ts index e524d57a9c..0afa743f61 100644 --- a/packages/fuselage-hooks/jest.config.ts +++ b/packages/fuselage-hooks/jest.config.ts @@ -3,36 +3,7 @@ import type { Config } from 'jest'; export default { projects: [ { - displayName: 'React 17', - preset: 'ts-jest', - errorOnDeprecated: true, - testMatch: [ - '/src/**/*.spec.{ts,tsx}', - '!**/*.server.spec.{ts,tsx}', - ], - testEnvironment: 'jsdom', - setupFilesAfterEnv: ['testing-utils/setup/noErrorsLogged'], - moduleNameMapper: { - '^react($|/.+)': 'react$1', - '^react-dom/client$': 'react-dom$1', - '^react-dom($|/.+)': 'react-dom$1', - }, - }, - { - displayName: 'React 17 SSR', - preset: 'ts-jest', - errorOnDeprecated: true, - testMatch: ['/src/**/*.server.spec.{ts,tsx}'], - testEnvironment: 'node', - setupFilesAfterEnv: ['testing-utils/setup/noErrorsLogged'], - moduleNameMapper: { - '^react($|/.+)': 'react$1', - '^react-dom/client$': 'react-dom$1', - '^react-dom($|/.+)': 'react-dom$1', - }, - }, - { - displayName: 'React 18', + displayName: 'csr', preset: 'ts-jest', errorOnDeprecated: true, testMatch: [ @@ -44,13 +15,9 @@ export default { 'testing-utils/setup/noErrorsLogged', '/src/jest-setup.ts', ], - moduleNameMapper: { - '^react($|/.+)': 'react18$1', - '^react-dom($|/.+)': 'react-dom18$1', - }, }, { - displayName: 'React 18 SSR', + displayName: 'ssr', preset: 'ts-jest', errorOnDeprecated: true, testMatch: ['/src/**/*.server.spec.{ts,tsx}'], @@ -59,10 +26,6 @@ export default { 'testing-utils/setup/noErrorsLogged', '/src/jest-setup.ts', ], - moduleNameMapper: { - '^react($|/.+)': 'react18$1', - '^react-dom($|/.+)': 'react-dom18$1', - }, }, ], } satisfies Config; diff --git a/packages/fuselage-hooks/package.json b/packages/fuselage-hooks/package.json index e554770253..396a6c597a 100644 --- a/packages/fuselage-hooks/package.json +++ b/packages/fuselage-hooks/package.json @@ -50,20 +50,17 @@ "@testing-library/react": "~16.1.0", "@testing-library/user-event": "~14.5.2", "@types/jest": "~29.5.14", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "@types/resize-observer-browser": "~0.1.11", - "@types/use-sync-external-store": "~0.0.3", "eslint": "~9.18.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", "lint-all": "workspace:~", "npm-run-all": "^4.1.5", "prettier": "~3.4.2", - "react": "^17.0.2", - "react-dom": "~17.0.2", - "react-dom18": "npm:react-dom@18", - "react18": "npm:react@18", + "react": "~18.3.1", + "react-dom": "~18.3.1", "rimraf": "~5.0.0", "rollup": "~2.79.2", "rollup-plugin-terser": "~7.0.2", @@ -74,9 +71,6 @@ }, "peerDependencies": { "@rocket.chat/fuselage-tokens": "*", - "react": "^17.0.2" - }, - "dependencies": { - "use-sync-external-store": "~1.4.0" + "react": "*" } } diff --git a/packages/fuselage-hooks/src/jest-setup.ts b/packages/fuselage-hooks/src/jest-setup.ts index 838c2fefbe..1e62ddadcd 100644 --- a/packages/fuselage-hooks/src/jest-setup.ts +++ b/packages/fuselage-hooks/src/jest-setup.ts @@ -1,3 +1,7 @@ +import { TextEncoder } from 'node:util'; + import { configure } from '@testing-library/react'; configure({ reactStrictMode: true }); + +global.TextEncoder = TextEncoder; diff --git a/packages/fuselage-hooks/src/testing.ts b/packages/fuselage-hooks/src/testing.ts index ab0008b1ea..db4c96d3e8 100644 --- a/packages/fuselage-hooks/src/testing.ts +++ b/packages/fuselage-hooks/src/testing.ts @@ -6,7 +6,6 @@ import { renderHook as _renderHook, } from '@testing-library/react'; import { createElement } from 'react'; -import * as ReactDOMClient from 'react-dom'; import { renderToString } from 'react-dom/server'; type RendererableContainer = Element | Document | DocumentFragment; @@ -37,9 +36,7 @@ export function renderHook< }; } - if ('createRoot' in ReactDOMClient) return _renderHook(render, options); - - return _renderHook(render, { ...options, legacyRoot: true }); + return _renderHook(render, options); } export { act } from '@testing-library/react'; diff --git a/packages/fuselage-hooks/src/useMediaQueries.ts b/packages/fuselage-hooks/src/useMediaQueries.ts index c6c9dd91e8..65f1f75259 100644 --- a/packages/fuselage-hooks/src/useMediaQueries.ts +++ b/packages/fuselage-hooks/src/useMediaQueries.ts @@ -1,5 +1,4 @@ -import { useCallback, useRef } from 'react'; -import { useSyncExternalStore } from 'use-sync-external-store/shim'; +import { useCallback, useRef, useSyncExternalStore } from 'react'; import { useStableArray } from './useStableArray'; diff --git a/packages/fuselage-hooks/src/useOutsideClick.ts b/packages/fuselage-hooks/src/useOutsideClick.ts index 2b4e6f17d1..2e49a41991 100644 --- a/packages/fuselage-hooks/src/useOutsideClick.ts +++ b/packages/fuselage-hooks/src/useOutsideClick.ts @@ -11,7 +11,7 @@ import { useEffectEvent } from './useEffectEvent'; */ export function useOutsideClick( - elements: RefObject[], + elements: RefObject[], cb: (e: MouseEvent) => void, ): void { const handleClickOutside = useEffectEvent(function handleClickOutside( diff --git a/packages/fuselage-hooks/src/useUniqueId.ts b/packages/fuselage-hooks/src/useUniqueId.ts index 6e83b5370d..53c9001bb6 100644 --- a/packages/fuselage-hooks/src/useUniqueId.ts +++ b/packages/fuselage-hooks/src/useUniqueId.ts @@ -5,6 +5,7 @@ import { useDebugValue, useRef, useMemo } from 'react'; * * @returns the unique ID string * @public + * @deprecated use `useId` from `react` instead */ export const useUniqueId = (): string => { const ref = useRef(); diff --git a/packages/fuselage-toastbar/jest.config.ts b/packages/fuselage-toastbar/jest.config.ts index a668c05d08..d940544fa6 100644 --- a/packages/fuselage-toastbar/jest.config.ts +++ b/packages/fuselage-toastbar/jest.config.ts @@ -9,7 +9,4 @@ export default { '/jest-setup.ts', 'testing-utils/setup/noErrorsLogged', ], - moduleNameMapper: { - '^react-dom/client$': 'react-dom', - }, } satisfies Config; diff --git a/packages/fuselage-toastbar/package.json b/packages/fuselage-toastbar/package.json index 3c041cb7f1..efdc118160 100644 --- a/packages/fuselage-toastbar/package.json +++ b/packages/fuselage-toastbar/package.json @@ -60,15 +60,15 @@ "@testing-library/react": "~16.1.0", "@testing-library/user-event": "~14.5.2", "@types/jest": "~29.5.14", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "eslint": "~9.18.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", "lint-all": "workspace:~", "npm-run-all": "^4.1.5", "prettier": "~3.4.2", - "react-dom": "~17.0.2", + "react-dom": "~18.3.1", "rimraf": "~3.0.2", "storybook": "~8.4.7", "storybook-dark-mode": "~4.0.2", @@ -81,8 +81,8 @@ "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/fuselage-polyfills": "*", "@rocket.chat/styled": "*", - "react": "^17.0.2", - "react-dom": "^17.0.2" + "react": "*", + "react-dom": "*" }, "volta": { "extends": "../../package.json" diff --git a/packages/fuselage-toastbar/src/ToastBarTimed.tsx b/packages/fuselage-toastbar/src/ToastBarTimed.tsx index 7d701244ee..c8f9879301 100644 --- a/packages/fuselage-toastbar/src/ToastBarTimed.tsx +++ b/packages/fuselage-toastbar/src/ToastBarTimed.tsx @@ -23,7 +23,7 @@ const ToastBarTimed = ({ variant={type} onPointerEnter={() => pause()} onPointerLeave={() => resume()} - children={message} + children={String(message)} onClose={dismissToastMessage} id={id} time={time} diff --git a/packages/fuselage-toastbar/src/testing.ts b/packages/fuselage-toastbar/src/testing.ts index 17ebf4f4c8..3c1b4c7342 100644 --- a/packages/fuselage-toastbar/src/testing.ts +++ b/packages/fuselage-toastbar/src/testing.ts @@ -1,34 +1 @@ -import type { - queries, - Queries, - RenderOptions, - RenderResult, -} from '@testing-library/react'; -import { render as renderOriginal } from '@testing-library/react'; -import type React from 'react'; -import type ReactDOMClient from 'react-dom'; - -type RendererableContainer = ReactDOMClient.Container; -type HydrateableContainer = Parameters<(typeof ReactDOMClient)['hydrate']>[0]; - -/** - * Light wrapper around `react-testing-library` to provide a custom render function for transitioning to React 18 - */ -export function render< - Q extends Queries = typeof queries, - Container extends RendererableContainer | HydrateableContainer = HTMLElement, - BaseElement extends RendererableContainer | HydrateableContainer = Container, ->( - ui: React.ReactNode, - options: RenderOptions, -): RenderResult; -export function render( - ui: React.ReactNode, - options?: Omit, -): RenderResult; -export function render(ui: React.ReactNode, options?: any): any { - return renderOriginal(ui, { - legacyRoot: true, - ...options, - }); -} +export { render } from '@testing-library/react'; diff --git a/packages/fuselage/jest-setup.ts b/packages/fuselage/jest-setup.ts index eb28fb96a5..cf23e03cf2 100644 --- a/packages/fuselage/jest-setup.ts +++ b/packages/fuselage/jest-setup.ts @@ -48,9 +48,3 @@ window.ResizeObserver = jest.fn().mockImplementation(() => ({ unobserve: jest.fn(), disconnect: jest.fn(), })); - -let uniqueIdCounter = 0; -jest.mock('@rocket.chat/fuselage-hooks', () => ({ - ...jest.requireActual('@rocket.chat/fuselage-hooks'), - useUniqueId: () => `unique-id-${uniqueIdCounter++}`, -})); diff --git a/packages/fuselage/jest.config.ts b/packages/fuselage/jest.config.ts index 7fef654ea4..073aef3c6a 100644 --- a/packages/fuselage/jest.config.ts +++ b/packages/fuselage/jest.config.ts @@ -11,6 +11,5 @@ export default { ], moduleNameMapper: { '\\.scss$': 'testing-utils/lazySingletonStyleTagModule', - '^react-dom/client$': 'react-dom', }, } satisfies Config; diff --git a/packages/fuselage/package.json b/packages/fuselage/package.json index c27354d762..2375ad0325 100644 --- a/packages/fuselage/package.json +++ b/packages/fuselage/package.json @@ -47,8 +47,8 @@ "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/fuselage-polyfills": "*", "@rocket.chat/icons": "*", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "*", + "react-dom": "*", "react-virtuoso": "1.2.4" }, "dependencies": { @@ -89,8 +89,8 @@ "@types/invariant": "^2.2.37", "@types/jest": "~29.5.14", "@types/jest-axe": "~3.5.9", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "autoprefixer": "~10.4.20", "babel-loader": "~9.2.1", "caniuse-lite": "~1.0.30001689", @@ -115,8 +115,8 @@ "postcss-scss": "~4.0.9", "postcss-svg": "~3.0.0", "prettier": "~3.4.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "~18.3.1", + "react-dom": "~18.3.1", "react-virtuoso": "~3.1.5", "resolve-url-loader": "~5.0.0", "rimraf": "^3.0.2", diff --git a/packages/fuselage/src/components/Accordion/AccordionItem.tsx b/packages/fuselage/src/components/Accordion/AccordionItem.tsx index 5cc8bf5bd0..0723df1a79 100644 --- a/packages/fuselage/src/components/Accordion/AccordionItem.tsx +++ b/packages/fuselage/src/components/Accordion/AccordionItem.tsx @@ -1,5 +1,10 @@ -import { useToggle, useUniqueId } from '@rocket.chat/fuselage-hooks'; -import type { KeyboardEvent, MouseEvent, ReactNode } from 'react'; +import { useToggle } from '@rocket.chat/fuselage-hooks'; +import { + useId, + type KeyboardEvent, + type MouseEvent, + type ReactNode, +} from 'react'; import { cx, cxx } from '../../helpers/composeClassNames'; import { StylingBox } from '../Box'; @@ -31,8 +36,8 @@ const AccordionItem = ({ const panelExpanded = noncollapsible || expanded; - const titleId = useUniqueId(); - const panelId = useUniqueId(); + const titleId = useId(); + const panelId = useId(); const handleClick = (e: MouseEvent) => { if (disabled) { diff --git a/packages/fuselage/src/components/Accordion/__snapshots__/Accordion.spec.tsx.snap b/packages/fuselage/src/components/Accordion/__snapshots__/Accordion.spec.tsx.snap index 47f51c1482..b31a49bc69 100644 --- a/packages/fuselage/src/components/Accordion/__snapshots__/Accordion.spec.tsx.snap +++ b/packages/fuselage/src/components/Accordion/__snapshots__/Accordion.spec.tsx.snap @@ -10,7 +10,7 @@ exports[`renders Default without crashing 1`] = ` class="rcx-box rcx-box--full rcx-accordion-item" >

Item #2

@@ -322,7 +322,7 @@ exports[`renders ExpandedItemByDefault without crashing 1`] = `
{ ); const transformFn = useCallback( - (props) => { + (props: any) => { if (props.onAnimationEnd === undefined) { props.onAnimationEnd = handleAnimationEnd; } diff --git a/packages/fuselage/src/components/Banner/Banner.tsx b/packages/fuselage/src/components/Banner/Banner.tsx index b64889a7ce..2b87244e17 100644 --- a/packages/fuselage/src/components/Banner/Banner.tsx +++ b/packages/fuselage/src/components/Banner/Banner.tsx @@ -3,6 +3,7 @@ import type { ReactNode, AllHTMLAttributes, HTMLAttributeAnchorTarget, + MouseEvent, } from 'react'; import { useRef, useCallback, useMemo } from 'react'; @@ -65,7 +66,7 @@ const Banner = ({ }, [onAction]); const handleCloseButtonClick = useCallback( - (event) => { + (event: MouseEvent) => { event.stopPropagation(); if (onClose) { diff --git a/packages/fuselage/src/components/Box/Box.tsx b/packages/fuselage/src/components/Box/Box.tsx index d54d24fcbf..02b77dffa1 100644 --- a/packages/fuselage/src/components/Box/Box.tsx +++ b/packages/fuselage/src/components/Box/Box.tsx @@ -16,7 +16,16 @@ import { useBoxTransform, BoxTransforms } from './BoxTransforms'; import type { StylingProps } from './stylingProps'; import { useStylingProps } from './useStylingProps'; -type BoxProps = { +export interface BoxProps + extends Partial, + Omit< + AllHTMLAttributes, + 'ref' | 'is' | 'className' | 'size' | 'elevation' | keyof StylingProps + >, + Omit< + SVGAttributes, + keyof AllHTMLAttributes | 'elevation' | keyof StylingProps + > { /** * The `is` prop is used to render the Box as a different HTML tag. It can also be used to render a different fuselage component. */ @@ -25,15 +34,7 @@ type BoxProps = { animated?: boolean; withRichContent?: boolean | 'inlineWithoutBreaks'; htmlSize?: AllHTMLAttributes['size']; -} & Partial & - Omit< - AllHTMLAttributes, - 'ref' | 'is' | 'className' | 'size' | 'elevation' - > & - Omit< - SVGAttributes, - keyof AllHTMLAttributes | 'elevation' - >; +} export const Box = forwardRef(function Box( { is = 'div', children, ...props }: BoxProps, diff --git a/packages/fuselage/src/components/Box/index.ts b/packages/fuselage/src/components/Box/index.ts index b2f4651178..69fc0d99a4 100644 --- a/packages/fuselage/src/components/Box/index.ts +++ b/packages/fuselage/src/components/Box/index.ts @@ -1,2 +1,2 @@ -export { default } from './Box'; +export { default, default as Box, BoxProps } from './Box'; export { default as StylingBox, type StylingBoxProps } from './StylingBox'; diff --git a/packages/fuselage/src/components/Box/useStylingProps.ts b/packages/fuselage/src/components/Box/useStylingProps.ts index add620a199..e93aa33820 100644 --- a/packages/fuselage/src/components/Box/useStylingProps.ts +++ b/packages/fuselage/src/components/Box/useStylingProps.ts @@ -5,8 +5,8 @@ import type { StylingProps } from './stylingProps'; import { extractStylingProps } from './stylingProps'; export const useStylingProps = ( - originalProps: TProps & Partial, -): TProps => { + originalProps: TProps, +): Omit => { const [props, styles] = extractStylingProps(originalProps); const newClassName = useStyle(styles, undefined); diff --git a/packages/fuselage/src/components/Card/Card.stories.tsx b/packages/fuselage/src/components/Card/Card.stories.tsx index b4cdfca9e9..1158615843 100644 --- a/packages/fuselage/src/components/Card/Card.stories.tsx +++ b/packages/fuselage/src/components/Card/Card.stories.tsx @@ -25,7 +25,7 @@ export default { CardCol: CardCol as ComponentType, CardRow: CardRow as ComponentType, CardBody: CardBody as ComponentType, - CardControls, + CardControls: CardControls as ComponentType, }, parameters: { backgrounds: { default: 'dark' }, diff --git a/packages/fuselage/src/components/Flex/Flex.stories.tsx b/packages/fuselage/src/components/Flex/Flex.stories.tsx index adcf6659ed..9227f1ede6 100644 --- a/packages/fuselage/src/components/Flex/Flex.stories.tsx +++ b/packages/fuselage/src/components/Flex/Flex.stories.tsx @@ -1,12 +1,20 @@ import type { Meta } from '@storybook/react'; +import type { ComponentType } from 'react'; import Tile from '../Tile'; import Flex from '.'; +import FlexContainer from './FlexContainer'; +import FlexItem from './FlexItem'; export default { title: 'Layout/Flex', - subcomponents: { 'Flex.Container': Flex.Container, 'Flex.Item': Flex.Item }, + subcomponents: { + 'FlexContainer': FlexContainer as ComponentType, + 'FlexItem': FlexItem as ComponentType, + 'Flex.Container': Flex.Container as ComponentType, + 'Flex.Item': Flex.Item as ComponentType, + }, } satisfies Meta; export const example = () => ( diff --git a/packages/fuselage/src/components/Flex/FlexContainer.tsx b/packages/fuselage/src/components/Flex/FlexContainer.tsx index c5fc054944..c9211bfcbb 100644 --- a/packages/fuselage/src/components/Flex/FlexContainer.tsx +++ b/packages/fuselage/src/components/Flex/FlexContainer.tsx @@ -28,7 +28,7 @@ function FlexContainer({ justifyContent, }: FlexContainerProps) { const transformFn = useCallback( - (props) => { + (props: any) => { if (inline !== undefined && props.display === undefined) { props.display = inline ? 'inline-flex' : 'flex'; } diff --git a/packages/fuselage/src/components/Flex/FlexItem.tsx b/packages/fuselage/src/components/Flex/FlexItem.tsx index 4afc255362..d6b40ed167 100644 --- a/packages/fuselage/src/components/Flex/FlexItem.tsx +++ b/packages/fuselage/src/components/Flex/FlexItem.tsx @@ -21,7 +21,7 @@ function FlexItem({ align, }: FlexItemProps) { const transformFn = useCallback( - (props) => { + (props: any) => { if (order !== undefined && props.order === undefined) { props.order = order; } diff --git a/packages/fuselage/src/components/InputBox/InputBox.tsx b/packages/fuselage/src/components/InputBox/InputBox.tsx index 99529a6dac..17d3cd675e 100644 --- a/packages/fuselage/src/components/InputBox/InputBox.tsx +++ b/packages/fuselage/src/components/InputBox/InputBox.tsx @@ -1,6 +1,7 @@ import { useMergedRefs } from '@rocket.chat/fuselage-hooks'; import type { ComponentProps, + FormEvent, ForwardRefExoticComponent, ReactNode, Ref, @@ -97,7 +98,7 @@ export const InputBox = forwardRef(function InputBox( }, []); const handleChange = useCallback( - (event) => { + (event: FormEvent) => { if (addon && innerRef.current && innerRef.current.parentElement) { innerRef.current.parentElement.classList.toggle( 'invalid', diff --git a/packages/fuselage/src/components/Margins/Margins.tsx b/packages/fuselage/src/components/Margins/Margins.tsx index 6a6bdf8ad4..8def1f02a6 100644 --- a/packages/fuselage/src/components/Margins/Margins.tsx +++ b/packages/fuselage/src/components/Margins/Margins.tsx @@ -37,7 +37,7 @@ export const Margins = (props: MarginsProps) => { } = props; const transformFn = useCallback( - (props) => { + (props: any) => { if (all !== undefined && props.margin === undefined) { props.margin = all; } diff --git a/packages/fuselage/src/components/Menu/Menu.spec.tsx b/packages/fuselage/src/components/Menu/Menu.spec.tsx index 3c1e130fe8..cfeb24349c 100644 --- a/packages/fuselage/src/components/Menu/Menu.spec.tsx +++ b/packages/fuselage/src/components/Menu/Menu.spec.tsx @@ -1,5 +1,9 @@ import { composeStories } from '@storybook/react'; -import { act, screen } from '@testing-library/react'; +import { + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { withResizeObserverMock } from 'testing-utils/mocks/withResizeObserverMock'; @@ -12,38 +16,44 @@ withResizeObserverMock(); const { Simple } = composeStories(stories); describe('[Menu Component]', () => { - const menuOption = screen.queryByText('Make Admin'); - it('should renders without crashing', () => { render(); }); it('should open options when click', async () => { render(); + expect(screen.queryByText('Make Admin')).not.toBeInTheDocument(); const button = screen.getByTestId('menu'); - await act(async () => { - await userEvent.click(button); - expect(await screen.findByText('Make Admin')).toBeInTheDocument(); - }); + await userEvent.click(button); + await waitFor(() => + expect(screen.getByText('Make Admin')).toBeInTheDocument(), + ); }); - it('should have no options when click twice', async () => { + // FIXME: options are not being removed because `AnimatedVisibility` relies on + // `onAnimationEnd` event which is not being triggered by jsdom + + it.skip('should have no options when click twice', async () => { render(); + const button = screen.getByTestId('menu'); await userEvent.click(button); - await act(async () => { - await userEvent.click(button); - expect(menuOption).toBeNull(); - }); + await waitFor(() => + expect(screen.getByText('Make Admin')).toBeInTheDocument(), + ); + await userEvent.click(button); + await waitForElementToBeRemoved(() => screen.queryByText('Make Admin')); }); - it('should have no options when click on menu and then elsewhere', async () => { + it.skip('should have no options when click on menu and then elsewhere', async () => { render(); + const button = screen.getByTestId('menu'); - await act(async () => { - await userEvent.click(button); - await userEvent.click(document.body); - expect(menuOption).toBeNull(); - }); + await userEvent.click(button); + await waitFor(() => + expect(screen.getByText('Make Admin')).toBeInTheDocument(), + ); + await userEvent.click(document.body); + await waitForElementToBeRemoved(() => screen.getByText('Make Admin')); }); }); diff --git a/packages/fuselage/src/components/Menu/Menu.tsx b/packages/fuselage/src/components/Menu/Menu.tsx index 872ca095d9..060c2e0574 100644 --- a/packages/fuselage/src/components/Menu/Menu.tsx +++ b/packages/fuselage/src/components/Menu/Menu.tsx @@ -65,7 +65,8 @@ export const Menu = ({ const onClick = useCallback(() => { if (ref.current?.classList.contains('focus-visible')) { ref.current.classList.remove('focus-visible'); - return hide(); + hide(); + return; } if (ref.current) { ref.current.focus(); @@ -75,7 +76,7 @@ export const Menu = ({ }, [hide, show]); const handleSelection = useCallback( - (args) => { + (args: OptionType) => { menuAction(args, options); reset(); hide(); diff --git a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.stories.tsx b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.stories.tsx index 1bf3a33b03..09939927ca 100644 --- a/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.stories.tsx +++ b/packages/fuselage/src/components/Message/MessageMetrics/MessageMetrics.stories.tsx @@ -23,13 +23,15 @@ export default { title: 'Message/MessageMetrics', component: MessageMetrics, subcomponents: { - MessageMetricsReply, - MessageMetricsItem, + MessageMetricsReply: MessageMetricsReply as ComponentType, + MessageMetricsItem: MessageMetricsItem as ComponentType, MessageMetricsFollowing: MessageMetricsFollowing as ComponentType, MessageMetricsItemIcon: MessageMetricsItemIcon as ComponentType, - MessageMetricsItemLabel, - MessageMetricsItemAvatarRowContent, - MessageMetricsItemAvatarRow, + MessageMetricsItemLabel: MessageMetricsItemLabel as ComponentType, + MessageMetricsItemAvatarRowContent: + MessageMetricsItemAvatarRowContent as ComponentType, + MessageMetricsItemAvatarRow: + MessageMetricsItemAvatarRow as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.stories.tsx b/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.stories.tsx index 5b2ca78630..1912b4aa6e 100644 --- a/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.stories.tsx +++ b/packages/fuselage/src/components/Message/MessageReactions/MessageReactions.stories.tsx @@ -1,4 +1,5 @@ import type { Meta } from '@storybook/react'; +import type { ComponentType } from 'react'; import { BasicMessageTemplate } from '../helpers'; @@ -10,8 +11,8 @@ export default { title: 'Message/MessageReactions', component: MessageReactions, subcomponents: { - MessageReaction, - MessageReactionAction, + MessageReaction: MessageReaction as ComponentType, + MessageReactionAction: MessageReactionAction as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/NavBar/__snapshots__/NavBar.spec.tsx.snap b/packages/fuselage/src/components/NavBar/__snapshots__/NavBar.spec.tsx.snap index a042d12b6e..e4c9d784cf 100644 --- a/packages/fuselage/src/components/NavBar/__snapshots__/NavBar.spec.tsx.snap +++ b/packages/fuselage/src/components/NavBar/__snapshots__/NavBar.spec.tsx.snap @@ -88,7 +88,7 @@ exports[`[NavBar Component] renders Default without crashing 1`] = ` aria-expanded="false" aria-haspopup="true" class="rcx-box rcx-box--full rcx-button--small-square rcx-button--square rcx-button--icon rcx-button rcx-button-group__item" - id="react-aria-1" + id="react-aria-:r0:" title="sort" type="button" > @@ -115,7 +115,7 @@ exports[`[NavBar Component] renders Default without crashing 1`] = ` aria-expanded="false" aria-haspopup="true" class="rcx-box rcx-box--full rcx-button--small-square rcx-button--square rcx-button--icon rcx-button rcx-button-group__item" - id="react-aria-3" + id="react-aria-:r2:" title="create new" type="button" > @@ -207,7 +207,7 @@ exports[`[NavBar Component] renders Default without crashing 1`] = ` aria-expanded="false" aria-haspopup="true" class="rcx-box rcx-box--full rcx-button--small-square rcx-button--square rcx-button--icon rcx-button rcx-button-group__item" - id="react-aria-5" + id="react-aria-:r4:" title="profile" type="button" > diff --git a/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx b/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx index 7fd0cf4d91..1ee8a893e2 100644 --- a/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx +++ b/packages/fuselage/src/components/PaginatedSelect/PaginatedMultiSelect.tsx @@ -1,10 +1,5 @@ import { useEffectEvent, useResizeObserver } from '@rocket.chat/fuselage-hooks'; -import { - type ComponentProps, - type ReactElement, - useState, - useRef, -} from 'react'; +import { type ComponentProps, useState, useRef, ElementType } from 'react'; import { prevent } from '../../helpers/prevent'; import AnimatedVisibility from '../AnimatedVisibility'; @@ -36,10 +31,8 @@ type PaginatedMultiSelectProps = Omit< endReached?: (start?: number, end?: number) => void; value?: PaginatedMultiSelectOption[]; onChange: (values: PaginatedMultiSelectOption[]) => void; - renderOptions?: ( - props: ComponentProps, - ) => ReactElement | null; - renderItem?: (props: ComponentProps) => ReactElement | null; + renderOptions?: ElementType>; + renderItem?: ElementType>; anchor?: any; }; diff --git a/packages/fuselage/src/components/Scrollable/Scrollable.tsx b/packages/fuselage/src/components/Scrollable/Scrollable.tsx index 4db42a07d0..93e4fbd5e3 100644 --- a/packages/fuselage/src/components/Scrollable/Scrollable.tsx +++ b/packages/fuselage/src/components/Scrollable/Scrollable.tsx @@ -119,7 +119,7 @@ export const Scrollable = ({ ); const transformFn = useCallback( - (props) => { + (props: any) => { props.className = className && appendClassName(props.className, className); diff --git a/packages/fuselage/src/components/Select/Select.stories.tsx b/packages/fuselage/src/components/Select/Select.stories.tsx index 3424b96f11..15028bc91e 100644 --- a/packages/fuselage/src/components/Select/Select.stories.tsx +++ b/packages/fuselage/src/components/Select/Select.stories.tsx @@ -24,12 +24,7 @@ const TemplateControlled: StoryFn = (args) => { const [value, setValue] = useState('3'); return ( - ); }; diff --git a/packages/fuselage/src/components/Select/Select.tsx b/packages/fuselage/src/components/Select/Select.tsx index f567b9e131..72cc261e8f 100644 --- a/packages/fuselage/src/components/Select/Select.tsx +++ b/packages/fuselage/src/components/Select/Select.tsx @@ -17,7 +17,7 @@ type SelectProps = Omit< onChange?: ((key: V) => any) | undefined; options: SelectOption[]; small?: boolean; -} & Omit, 'onChange'>; +} & Omit, 'value' | 'onChange'>; /** * An input for selection of options. diff --git a/packages/fuselage/src/components/Select/SelectAria.tsx b/packages/fuselage/src/components/Select/SelectAria.tsx index b4231e15d6..9bc018d79b 100644 --- a/packages/fuselage/src/components/Select/SelectAria.tsx +++ b/packages/fuselage/src/components/Select/SelectAria.tsx @@ -41,7 +41,7 @@ export const SelectAria = forwardRef(function SelectAria( const state = useSelectState({ isDisabled, - selectedKey: value, + selectedKey: typeof value !== 'bigint' ? value : null, onSelectionChange: onChange, ...props, }); diff --git a/packages/fuselage/src/components/SelectInput/SelectInput.stories.tsx b/packages/fuselage/src/components/SelectInput/SelectInput.stories.tsx index 5cddc43c0e..9e4371e946 100644 --- a/packages/fuselage/src/components/SelectInput/SelectInput.stories.tsx +++ b/packages/fuselage/src/components/SelectInput/SelectInput.stories.tsx @@ -1,4 +1,5 @@ import type { StoryFn, Meta } from '@storybook/react'; +import { ComponentType } from 'react'; import { PropsVariationSection } from '../../../.storybook/helpers'; import { Icon } from '../Icon'; @@ -9,7 +10,9 @@ import { SelectInput } from './SelectInput'; export default { title: 'Inputs/SelectInput', component: SelectInput, - subcomponents: { SelectInputOption }, + subcomponents: { + SelectInputOption: SelectInputOption as ComponentType, + }, } satisfies Meta; const Template: StoryFn = (args) => ( diff --git a/packages/fuselage/src/components/SelectInput/SelectInput.tsx b/packages/fuselage/src/components/SelectInput/SelectInput.tsx index b63dc30874..a0cfe8c36a 100644 --- a/packages/fuselage/src/components/SelectInput/SelectInput.tsx +++ b/packages/fuselage/src/components/SelectInput/SelectInput.tsx @@ -1,4 +1,4 @@ -import type { ComponentProps, ReactNode, Ref } from 'react'; +import type { ComponentProps, FormEvent, ReactNode, Ref } from 'react'; import { forwardRef, useState, useCallback } from 'react'; import { Icon } from '../Icon'; @@ -24,7 +24,7 @@ export const SelectInput = forwardRef(function SelectInput( !props.value && !props.defaultValue, ); const handleChange = useCallback( - (event) => { + (event: FormEvent) => { setPlaceholderVisible(!event.currentTarget.value); onChange?.call(event.currentTarget, event); }, diff --git a/packages/fuselage/src/components/Sidebar/Item.stories.tsx b/packages/fuselage/src/components/Sidebar/Item.stories.tsx index e6e8a657f3..e8f609a4a2 100644 --- a/packages/fuselage/src/components/Sidebar/Item.stories.tsx +++ b/packages/fuselage/src/components/Sidebar/Item.stories.tsx @@ -20,13 +20,13 @@ export default { title: 'Sidebar/Item', component: SidebarItem, subcomponents: { - SidebarItemAvatar, - SidebarItemContent, - SidebarItemContainer, + SidebarItemAvatar: SidebarItemAvatar as ComponentType, + SidebarItemContent: SidebarItemContent as ComponentType, + SidebarItemContainer: SidebarItemContainer as ComponentType, SidebarItemIcon: SidebarItemIcon as ComponentType, - SidebarItemSubtitle, - SidebarItemTitle, - SidebarItemWrapper, + SidebarItemSubtitle: SidebarItemSubtitle as ComponentType, + SidebarItemTitle: SidebarItemTitle as ComponentType, + SidebarItemWrapper: SidebarItemWrapper as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx b/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx index dead2598f1..056adf1a9d 100644 --- a/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx +++ b/packages/fuselage/src/components/Sidebar/Sidebar.stories.tsx @@ -26,20 +26,20 @@ export default { title: 'Sidebar/Sidebar', component: Sidebar, subcomponents: { - SidebarItemAvatar, - SidebarItemContent, - SidebarItemContainer, + SidebarItemAvatar: SidebarItemAvatar as ComponentType, + SidebarItemContent: SidebarItemContent as ComponentType, + SidebarItemContainer: SidebarItemContainer as ComponentType, SidebarItemIcon: SidebarItemIcon as ComponentType, - SidebarItemSubtitle, - SidebarItemTitle, - SidebarItemWrapper, - SidebarFooterHighlight, - SidebarItemActions, + SidebarItemSubtitle: SidebarItemSubtitle as ComponentType, + SidebarItemTitle: SidebarItemTitle as ComponentType, + SidebarItemWrapper: SidebarItemWrapper as ComponentType, + SidebarFooterHighlight: SidebarFooterHighlight as ComponentType, + SidebarItemActions: SidebarItemActions as ComponentType, SidebarItemAction: SidebarItemAction as ComponentType, - SidebarFooter, - SidebarSectionTitle, - SidebarItem, - SidebarSection, + SidebarFooter: SidebarFooter as ComponentType, + SidebarSectionTitle: SidebarSectionTitle as ComponentType, + SidebarItem: SidebarItem as ComponentType, + SidebarSection: SidebarSection as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/Sidebar/TopBar/TopBar.stories.tsx b/packages/fuselage/src/components/Sidebar/TopBar/TopBar.stories.tsx index 493cf1b351..c52349d992 100644 --- a/packages/fuselage/src/components/Sidebar/TopBar/TopBar.stories.tsx +++ b/packages/fuselage/src/components/Sidebar/TopBar/TopBar.stories.tsx @@ -18,9 +18,9 @@ export default { component: TopBar, subcomponents: { TopBarAction: TopBarAction as ComponentType, - TopBarActions, - TopBarTitle, - TopBarToolBox, + TopBarActions: TopBarActions as ComponentType, + TopBarTitle: TopBarTitle as ComponentType, + TopBarToolBox: TopBarToolBox as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/SidebarV2/hooks/useCollapse.tsx b/packages/fuselage/src/components/SidebarV2/hooks/useCollapse.tsx index 5e97b73be4..2be4b5c9f9 100644 --- a/packages/fuselage/src/components/SidebarV2/hooks/useCollapse.tsx +++ b/packages/fuselage/src/components/SidebarV2/hooks/useCollapse.tsx @@ -1,5 +1,5 @@ -import { useToggle, useUniqueId } from '@rocket.chat/fuselage-hooks'; -import type { KeyboardEventHandler, MouseEvent } from 'react'; +import { useToggle } from '@rocket.chat/fuselage-hooks'; +import { useId, type KeyboardEventHandler, type MouseEvent } from 'react'; const hasPropExpanded = (expanded: boolean | undefined) => expanded !== undefined; @@ -22,8 +22,8 @@ export const useCollapse = ({ const panelExpanded = noncollapsible || expanded; - const titleId = useUniqueId(); - const panelId = useUniqueId(); + const titleId = useId(); + const panelId = useId(); const handleClick = (e: MouseEvent) => { if (disabled) { diff --git a/packages/fuselage/src/components/Slider/Slider.spec.tsx b/packages/fuselage/src/components/Slider/Slider.spec.tsx index b06bb74812..911ce445f1 100644 --- a/packages/fuselage/src/components/Slider/Slider.spec.tsx +++ b/packages/fuselage/src/components/Slider/Slider.spec.tsx @@ -1,5 +1,6 @@ import { composeStories } from '@storybook/react'; -import { fireEvent, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { render } from '../../testing'; @@ -32,16 +33,16 @@ describe('[Slider Component]', () => { expect(thumbs.length).toBe(2); }); - it("should update Thumb's position when Thumb is clicked and dragged", () => { + it("should update Thumb's position when Thumb is clicked and dragged", async () => { render(); const slider = screen.getByRole('slider'); - slider.focus(); - fireEvent.keyDown(slider, { key: 'ArrowRight' }); - fireEvent.keyDown(slider, { key: 'ArrowRight' }); - fireEvent.keyDown(slider, { key: 'ArrowRight' }); - fireEvent.keyDown(slider, { key: 'ArrowRight' }); + await userEvent.tab(); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{ArrowRight}'); + await userEvent.keyboard('{ArrowRight}'); expect(slider.value).toBe('4'); }); diff --git a/packages/fuselage/src/components/States/States.stories.tsx b/packages/fuselage/src/components/States/States.stories.tsx index 99970d47a0..e345c5f242 100644 --- a/packages/fuselage/src/components/States/States.stories.tsx +++ b/packages/fuselage/src/components/States/States.stories.tsx @@ -22,16 +22,16 @@ export default { title: 'Feedback/States', component: States, subcomponents: { - StatesSubtitle, + StatesSubtitle: StatesSubtitle as ComponentType, StatesIcon: StatesIcon as ComponentType, - StatesTitle, - StatesSuggestionList, - StatesSuggestionListItem, - StatesSuggestion, - StatesSuggestionText, - StatesActions, - StatesAction, - StatesLink, + StatesTitle: StatesTitle as ComponentType, + StatesSuggestionList: StatesSuggestionList as ComponentType, + StatesSuggestionListItem: StatesSuggestionListItem as ComponentType, + StatesSuggestion: StatesSuggestion as ComponentType, + StatesSuggestionText: StatesSuggestionText as ComponentType, + StatesActions: StatesActions as ComponentType, + StatesAction: StatesAction as ComponentType, + StatesLink: StatesLink as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/Table/Table.stories.tsx b/packages/fuselage/src/components/Table/Table.stories.tsx index 9e57ab14d6..b5c629d847 100644 --- a/packages/fuselage/src/components/Table/Table.stories.tsx +++ b/packages/fuselage/src/components/Table/Table.stories.tsx @@ -1,4 +1,5 @@ import type { Meta, StoryFn } from '@storybook/react'; +import type { ComponentType } from 'react'; import { CheckBox } from '../CheckBox'; @@ -17,13 +18,13 @@ export default { title: 'Data Display/Table', component: Table, subcomponents: { - TableHead, - TableRow, - TableCell, - TableBody, - TableSelection, - TableSelectionButton, - TableSelectionButtonGroup, + TableHead: TableHead as ComponentType, + TableRow: TableRow as ComponentType, + TableCell: TableCell as ComponentType, + TableBody: TableBody as ComponentType, + TableSelection: TableSelection as ComponentType, + TableSelectionButton: TableSelectionButton as ComponentType, + TableSelectionButtonGroup: TableSelectionButtonGroup as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/Tabs/Tabs.stories.tsx b/packages/fuselage/src/components/Tabs/Tabs.stories.tsx index c893d4b332..9281f397e4 100644 --- a/packages/fuselage/src/components/Tabs/Tabs.stories.tsx +++ b/packages/fuselage/src/components/Tabs/Tabs.stories.tsx @@ -1,4 +1,5 @@ import type { StoryFn, Meta } from '@storybook/react'; +import type { ComponentType } from 'react'; import { Tabs } from './Tabs'; import { TabsItem } from './TabsItem'; @@ -7,7 +8,7 @@ export default { title: 'Navigation/Tabs', component: Tabs, subcomponents: { - TabsItem, + TabsItem: TabsItem as ComponentType, }, } satisfies Meta; diff --git a/packages/fuselage/src/components/ToastBar/ToastBar.tsx b/packages/fuselage/src/components/ToastBar/ToastBar.tsx index bc25bc997d..c295983ab8 100644 --- a/packages/fuselage/src/components/ToastBar/ToastBar.tsx +++ b/packages/fuselage/src/components/ToastBar/ToastBar.tsx @@ -1,6 +1,5 @@ import { css, keyframes } from '@rocket.chat/css-in-js'; -import { useUniqueId } from '@rocket.chat/fuselage-hooks'; -import type { ReactNode, AllHTMLAttributes } from 'react'; +import { type ReactNode, type AllHTMLAttributes, useId } from 'react'; import Box from '../Box'; import { IconButton } from '../Button'; @@ -68,7 +67,7 @@ export function ToastBar({ } `; - const uniqueId = useUniqueId(); + const uniqueId = useId(); const toastId = id || uniqueId; return ( diff --git a/packages/fuselage/src/hooks/useBoxOnlyProps.ts b/packages/fuselage/src/hooks/useBoxOnlyProps.ts index b016c60dee..1511993f4a 100644 --- a/packages/fuselage/src/hooks/useBoxOnlyProps.ts +++ b/packages/fuselage/src/hooks/useBoxOnlyProps.ts @@ -5,13 +5,11 @@ import { prependClassName } from '../helpers/prependClassName'; export const useBoxOnlyProps = < T extends { className: string; - size?: AllHTMLAttributes['size']; }, >( props: T & { animated?: boolean; withRichContent?: boolean | 'inlineWithoutBreaks'; - elevation?: '0' | '1' | '2'; htmlSize?: AllHTMLAttributes['size']; size?: AllHTMLAttributes['size']; }, diff --git a/packages/fuselage/src/testing.ts b/packages/fuselage/src/testing.ts index 17ebf4f4c8..3c1b4c7342 100644 --- a/packages/fuselage/src/testing.ts +++ b/packages/fuselage/src/testing.ts @@ -1,34 +1 @@ -import type { - queries, - Queries, - RenderOptions, - RenderResult, -} from '@testing-library/react'; -import { render as renderOriginal } from '@testing-library/react'; -import type React from 'react'; -import type ReactDOMClient from 'react-dom'; - -type RendererableContainer = ReactDOMClient.Container; -type HydrateableContainer = Parameters<(typeof ReactDOMClient)['hydrate']>[0]; - -/** - * Light wrapper around `react-testing-library` to provide a custom render function for transitioning to React 18 - */ -export function render< - Q extends Queries = typeof queries, - Container extends RendererableContainer | HydrateableContainer = HTMLElement, - BaseElement extends RendererableContainer | HydrateableContainer = Container, ->( - ui: React.ReactNode, - options: RenderOptions, -): RenderResult; -export function render( - ui: React.ReactNode, - options?: Omit, -): RenderResult; -export function render(ui: React.ReactNode, options?: any): any { - return renderOriginal(ui, { - legacyRoot: true, - ...options, - }); -} +export { render } from '@testing-library/react'; diff --git a/packages/layout/jest.config.js b/packages/layout/jest.config.js index 2857b4d157..393372326f 100644 --- a/packages/layout/jest.config.js +++ b/packages/layout/jest.config.js @@ -3,4 +3,5 @@ module.exports = { errorOnDeprecated: true, testMatch: ['/src/**/*.spec.ts?(x)'], testEnvironment: 'jsdom', + setupFilesAfterEnv: ['/src/jest-setup.ts'], }; diff --git a/packages/layout/package.json b/packages/layout/package.json index 4edf74a004..9351a75ef0 100644 --- a/packages/layout/package.json +++ b/packages/layout/package.json @@ -46,18 +46,19 @@ "@storybook/react": "~8.4.7", "@storybook/react-webpack5": "~8.4.7", "@storybook/theming": "~8.4.7", + "@testing-library/react": "~16.1.0", "@types/jest": "~29.5.14", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "eslint": "~9.18.0", "jest": "~29.7.0", "jest-environment-jsdom": "~29.7.0", "lint-all": "workspace:~", "npm-run-all": "^4.1.5", "prettier": "~3.4.2", - "react": "~17.0.2", - "react-dom": "~17.0.2", - "react-i18next": "~11.15.4", + "react": "~18.3.1", + "react-dom": "~18.3.1", + "react-i18next": "~13.2.2", "rimraf": "~3.0.2", "storybook": "~8.4.7", "storybook-dark-mode": "~4.0.2", @@ -67,9 +68,9 @@ }, "peerDependencies": { "@rocket.chat/fuselage": "*", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-i18next": "~11.15.4" + "react": "*", + "react-dom": "*", + "react-i18next": "*" }, "volta": { "extends": "../../package.json" diff --git a/packages/layout/src/BackgroundLayer/BackgroundImage.tsx b/packages/layout/src/BackgroundLayer/BackgroundImage.tsx index efc97012c5..afc45eee0b 100644 --- a/packages/layout/src/BackgroundLayer/BackgroundImage.tsx +++ b/packages/layout/src/BackgroundLayer/BackgroundImage.tsx @@ -1,10 +1,4 @@ -import type { ReactElement } from 'react'; - -const BackgroundImage = ({ - backgroundColor, -}: { - backgroundColor: string; -}): ReactElement => ( +const BackgroundImage = ({ backgroundColor }: { backgroundColor: string }) => ( { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + render(); }); diff --git a/packages/layout/src/List/List.styles.tsx b/packages/layout/src/List/List.styles.tsx index b9a27f8635..4edd87d7de 100644 --- a/packages/layout/src/List/List.styles.tsx +++ b/packages/layout/src/List/List.styles.tsx @@ -2,7 +2,7 @@ import styled from '@rocket.chat/styled'; import type { CSSProperties } from 'react'; type ListComponentProps = { - color: string; + color: NonNullable; icon?: string; listStyleType: CSSProperties['listStyleType']; }; diff --git a/packages/layout/src/List/List.tsx b/packages/layout/src/List/List.tsx index 24d8a6a31f..e48fcd0332 100644 --- a/packages/layout/src/List/List.tsx +++ b/packages/layout/src/List/List.tsx @@ -1,12 +1,6 @@ -import type { Box } from '@rocket.chat/fuselage'; import { Margins } from '@rocket.chat/fuselage'; import colors from '@rocket.chat/fuselage-tokens/colors.json'; -import type { - ReactElement, - ReactNode, - ComponentProps, - CSSProperties, -} from 'react'; +import type { ReactNode, ComponentProps, CSSProperties } from 'react'; import { ListComponent } from './List.styles'; import ListItem from './ListItem'; @@ -21,9 +15,9 @@ const List = ({ children: ReactNode; spacing?: ComponentProps['block']; listStyleType?: CSSProperties['listStyleType']; - color?: ComponentProps['color']; + color?: CSSProperties['color']; icon?: string; -}): ReactElement => ( +}) => ( {children} diff --git a/packages/layout/src/ManageWorkspaceFallback.spec.tsx b/packages/layout/src/ManageWorkspaceFallback.spec.tsx index 2bda55b369..dcc48f2efc 100644 --- a/packages/layout/src/ManageWorkspaceFallback.spec.tsx +++ b/packages/layout/src/ManageWorkspaceFallback.spec.tsx @@ -1,12 +1,7 @@ -import ReactDOM from 'react-dom'; +import { render } from '@testing-library/react'; import ManageWorkspaceFallback from './ManageWorkspaceFallback'; it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render( - undefined} />, - div, - ); - ReactDOM.unmountComponentAtNode(div); + render( undefined} />); }); diff --git a/packages/layout/src/TooltipWrapper.spec.tsx b/packages/layout/src/TooltipWrapper.spec.tsx index 45a6455315..565a3c2b38 100644 --- a/packages/layout/src/TooltipWrapper.spec.tsx +++ b/packages/layout/src/TooltipWrapper.spec.tsx @@ -1,15 +1,11 @@ -import ReactDOM from 'react-dom'; +import { render } from '@testing-library/react'; import TooltipWrapper from './TooltipWrapper'; it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render( null} text='' />, div); - ReactDOM.unmountComponentAtNode(div); + render( null} text='' />); }); it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(} text='' />, div); - ReactDOM.unmountComponentAtNode(div); + render(} text='' />); }); diff --git a/packages/layout/src/TooltipWrapper.tsx b/packages/layout/src/TooltipWrapper.tsx index 24979bfd22..012f7b44df 100644 --- a/packages/layout/src/TooltipWrapper.tsx +++ b/packages/layout/src/TooltipWrapper.tsx @@ -3,7 +3,7 @@ import { PositionAnimated, Tooltip, } from '@rocket.chat/fuselage'; -import { useDebouncedState, useUniqueId } from '@rocket.chat/fuselage-hooks'; +import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; import type { ComponentProps, Dispatch, @@ -13,7 +13,14 @@ import type { Ref, SetStateAction, } from 'react'; -import { cloneElement, forwardRef, useCallback, useMemo, useRef } from 'react'; +import { + cloneElement, + forwardRef, + useCallback, + useId, + useMemo, + useRef, +} from 'react'; type AnchorParams = { ref: MutableRefObject; @@ -63,7 +70,7 @@ const TooltipWrapper = ({ const anchorRef = useRef(null); const [open, setOpen] = useDebouncedState(false, 460); const toggle = useCallback( - (open) => { + (open: SetStateAction) => { setOpen(open); if (open) { @@ -73,7 +80,7 @@ const TooltipWrapper = ({ [setOpen], ); - const id = useUniqueId(); + const id = useId(); const anchorParams = useMemo( () => ({ ref: anchorRef, toggle, id }), diff --git a/packages/layout/src/jest-setup.ts b/packages/layout/src/jest-setup.ts new file mode 100644 index 0000000000..347dd46681 --- /dev/null +++ b/packages/layout/src/jest-setup.ts @@ -0,0 +1,3 @@ +import { TextEncoder } from 'node:util'; + +globalThis.TextEncoder = TextEncoder; diff --git a/packages/logo/package.json b/packages/logo/package.json index 4d3a8c6be9..af3f4c117b 100644 --- a/packages/logo/package.json +++ b/packages/logo/package.json @@ -42,9 +42,10 @@ }, "devDependencies": { "@rocket.chat/fuselage-tokens": "workspace:~", + "@testing-library/react": "~16.1.0", "@types/jest": "~29.5.14", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "build-logo": "workspace:~", "eslint": "~9.18.0", "jest": "~29.7.0", @@ -52,8 +53,8 @@ "lint-all": "workspace:~", "npm-run-all": "^4.1.5", "prettier": "~3.4.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", + "react": "~18.3.1", + "react-dom": "~18.3.1", "rimraf": "^3.0.2", "ts-jest": "~29.2.5", "typedoc": "~0.27.5", @@ -64,7 +65,7 @@ "@rocket.chat/styled": "workspace:~" }, "peerDependencies": { - "react": "17.0.2", + "react": "*", "react-dom": "17.0.2" } } diff --git a/packages/logo/src/RocketChatLogo/RocketChatLogo.spec.tsx b/packages/logo/src/RocketChatLogo/RocketChatLogo.spec.tsx index 92662a0dc1..5b49392762 100644 --- a/packages/logo/src/RocketChatLogo/RocketChatLogo.spec.tsx +++ b/packages/logo/src/RocketChatLogo/RocketChatLogo.spec.tsx @@ -1,9 +1,7 @@ -import ReactDOM from 'react-dom'; +import { render } from '@testing-library/react'; import RocketChatLogo from './RocketChatLogo'; it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + render(); }); diff --git a/packages/logo/src/RocketChatLogo/RocketChatLogo.tsx b/packages/logo/src/RocketChatLogo/RocketChatLogo.tsx index d442984490..79b7d9a0de 100644 --- a/packages/logo/src/RocketChatLogo/RocketChatLogo.tsx +++ b/packages/logo/src/RocketChatLogo/RocketChatLogo.tsx @@ -1,6 +1,5 @@ -import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import colors from '@rocket.chat/fuselage-tokens/colors.json'; -import type { ReactElement, SVGAttributes } from 'react'; +import { useId, type ReactElement, type SVGAttributes } from 'react'; type RocketChatLogoProps = { color?: SVGAttributes['fill']; @@ -9,7 +8,7 @@ type RocketChatLogoProps = { const RocketChatLogo = ({ color = colors.r400, }: RocketChatLogoProps): ReactElement => { - const titleId = useUniqueId(); + const titleId = useId(); return ( { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + render(); }); diff --git a/packages/onboarding-ui/.jest/setup.ts b/packages/onboarding-ui/.jest/setup.ts index 0c3a7ecc1a..b3a0797e21 100644 --- a/packages/onboarding-ui/.jest/setup.ts +++ b/packages/onboarding-ui/.jest/setup.ts @@ -1,3 +1,5 @@ +import { TextEncoder } from 'node:util'; + import i18next from 'i18next'; import { initReactI18next } from 'react-i18next'; @@ -18,3 +20,5 @@ beforeAll(async () => { beforeAll(() => { window.ResizeObserver = ResizeObserverMock; }); + +global.TextEncoder = TextEncoder; diff --git a/packages/onboarding-ui/package.json b/packages/onboarding-ui/package.json index 9e5f097585..ff4924e6ce 100644 --- a/packages/onboarding-ui/package.json +++ b/packages/onboarding-ui/package.json @@ -55,9 +55,10 @@ "@storybook/react": "~8.4.7", "@storybook/react-webpack5": "~8.4.7", "@storybook/theming": "~8.4.7", + "@testing-library/react": "~16.1.0", "@types/jest": "~29.5.14", - "@types/react": "~17.0.83", - "@types/react-dom": "~17.0.25", + "@types/react": "~18.3.18", + "@types/react-dom": "~18.3.5", "countries-list": "^2.6.1", "eslint": "~9.18.0", "jest": "~29.7.0", @@ -65,9 +66,9 @@ "lint-all": "workspace:~", "npm-run-all": "^4.1.5", "prettier": "~3.4.2", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-i18next": "~11.15.7", + "react": "~18.3.1", + "react-dom": "~18.3.1", + "react-i18next": "~13.2.2", "rimraf": "^3.0.2", "storybook": "~8.4.7", "storybook-dark-mode": "~4.0.2", @@ -87,9 +88,9 @@ "@rocket.chat/layout": "*", "@rocket.chat/logo": "*", "@rocket.chat/styled": "*", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-i18next": "~11.15.4" + "react": "*", + "react-dom": "*", + "react-i18next": "*" }, "volta": { "extends": "../../package.json" diff --git a/packages/onboarding-ui/src/common/AgreeTermsField.tsx b/packages/onboarding-ui/src/common/AgreeTermsField.tsx index 455a0ee929..389d1af185 100644 --- a/packages/onboarding-ui/src/common/AgreeTermsField.tsx +++ b/packages/onboarding-ui/src/common/AgreeTermsField.tsx @@ -33,7 +33,7 @@ const AgreeTermsField = ({ name='agreement' control={control} rules={{ - required: String(t('component.form.requiredField')), + required: t('component.form.requiredField'), }} render={({ field: { ref, name, onBlur, onChange, value } }) => ( { - const div = document.createElement('div'); - ReactDOM.render(, div); - ReactDOM.unmountComponentAtNode(div); + render(); }); diff --git a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.spec.tsx b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.spec.tsx index 2dceb12dc9..8f5f865666 100644 --- a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.spec.tsx +++ b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.spec.tsx @@ -1,10 +1,9 @@ -import ReactDOM from 'react-dom'; +import { render } from '@testing-library/react'; import AdminInfoForm from './AdminInfoForm'; it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render( + render( { validateUsername={() => true} onSubmit={() => undefined} />, - div, ); - ReactDOM.unmountComponentAtNode(div); }); diff --git a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx index e99e7e632b..05fb012106 100644 --- a/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx +++ b/packages/onboarding-ui/src/forms/AdminInfoForm/AdminInfoForm.tsx @@ -13,11 +13,10 @@ import { FieldError, FieldHint, } from '@rocket.chat/fuselage'; -import { useUniqueId } from '@rocket.chat/fuselage-hooks'; import { Form } from '@rocket.chat/layout'; import type { ReactElement } from 'react'; -import { useRef, useEffect } from 'react'; -import type { SubmitHandler, Validate } from 'react-hook-form'; +import { useRef, useEffect, useId } from 'react'; +import type { FieldPathValue, SubmitHandler, Validate } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -35,9 +34,18 @@ type AdminInfoFormProps = { passwordRulesHint: string; keepPosted?: boolean; initialValues?: Omit; - validateUsername: Validate; - validateEmail: Validate; - validatePassword: Validate; + validateUsername: Validate< + FieldPathValue, + AdminInfoPayload + >; + validateEmail: Validate< + FieldPathValue, + AdminInfoPayload + >; + validatePassword: Validate< + FieldPathValue, + AdminInfoPayload + >; onSubmit: SubmitHandler; }; @@ -54,11 +62,11 @@ const AdminInfoForm = ({ }: AdminInfoFormProps): ReactElement => { const { t } = useTranslation(); - const formId = useUniqueId(); - const fullnameField = useUniqueId(); - const usernameField = useUniqueId(); // lgtm [js/insecure-randomness] - const emailField = useUniqueId(); - const passwordField = useUniqueId(); // lgtm [js/insecure-randomness] + const formId = useId(); + const fullnameField = useId(); + const usernameField = useId(); // lgtm [js/insecure-randomness] + const emailField = useId(); + const passwordField = useId(); // lgtm [js/insecure-randomness] const adminInfoFormRef = useRef(null); @@ -108,7 +116,7 @@ const AdminInfoForm = ({ ( ( @@ -170,7 +178,7 @@ const AdminInfoForm = ({ name='email' control={control} rules={{ - required: String(t('component.form.requiredField')), + required: t('component.form.requiredField'), validate: validateEmail, }} render={({ field }) => ( @@ -202,7 +210,7 @@ const AdminInfoForm = ({ name='password' control={control} rules={{ - required: String(t('component.form.requiredField')), + required: t('component.form.requiredField'), validate: validatePassword, }} render={({ field }) => ( diff --git a/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.spec.tsx b/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.spec.tsx index 9a2bcb7197..65d615ad5a 100644 --- a/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.spec.tsx +++ b/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.spec.tsx @@ -1,10 +1,9 @@ -import ReactDOM from 'react-dom'; +import { render } from '@testing-library/react'; import AwaitConfirmationForm from './AwaitConfirmationForm'; it('renders without crashing', () => { - const div = document.createElement('div'); - ReactDOM.render( + render( { onResendEmailRequest={() => true} onChangeEmailRequest={() => true} />, - div, ); - ReactDOM.unmountComponentAtNode(div); }); diff --git a/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.tsx b/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.tsx index cecd8fa294..ec6644fe53 100644 --- a/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.tsx +++ b/packages/onboarding-ui/src/forms/AwaitConfirmationForm/AwaitConfirmationForm.tsx @@ -32,14 +32,17 @@ const AwaitingConfirmationForm = ({ - - Email sent to {{ emailAddress }} with a - confirmation link.Please verify that the security code below matches - the one in the email. + + Email sent to {emailAddress} with a confirmation + link.Please verify that the security code below matches the one in + the email.