diff --git a/.flowconfig b/.flowconfig index ae58e932fc..daae6590c8 100644 --- a/.flowconfig +++ b/.flowconfig @@ -20,6 +20,7 @@ /node_modules/react-styleguidist* /node_modules/immer* /.*/__tests__/.* +/.*/*.stories.* # ignoring the flow check for this component specifically because it increased the scope too much when we are planning to switch to typescript anyways /src/components/pill-selector-dropdown/PillSelectorDropdown.stories.js diff --git a/.storybook/main.ts b/.storybook/main.ts index d7ead195db..0cfbb743c0 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,9 +1,20 @@ +/* eslint-disable */ +// @ts-nocheck + import path from 'path'; const language = process.env.LANGUAGE; -const config: { webpackFinal: (config: any) => Promise; staticDirs: string[]; stories: string[]; framework: { name: string }; addons: (string | { name: string; options: { sass: { implementation: any } } } | { name: string; options: { mdxPluginOptions: { mdxCompileOptions: { remarkPlugins: any[] } } } })[] } = { - stories: ['../src/**/*.mdx','../src/**/*.stories.@(js|jsx|ts|tsx)'], +const config: { + stories: string[]; + addons: (string | { name: string; options: { sass: { implementation: any } } })[], + framework: { name: string }; + staticDirs: string[]; + webpackFinal: (config: any) => Promise; + typescript: any +} = { + stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'], + addons: [ '@storybook/addon-links', '@storybook/addon-essentials', @@ -17,11 +28,18 @@ const config: { webpackFinal: (config: any) => Promise; staticDirs: string[ }, }, '@storybook/addon-styling-webpack', + '@storybook/addon-docs', + '@storybook/addon-webpack5-compiler-babel', + '@chromatic-com/storybook', + 'storybook-react-intl' ], + framework: { name: '@storybook/react-webpack5', }, + staticDirs: ['public'], + webpackFinal: async (config: any) => { // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION' // You can change the configuration based on that. @@ -39,6 +57,9 @@ const config: { webpackFinal: (config: any) => Promise; staticDirs: string[ return config; }, + typescript: { + reactDocgen: 'react-docgen-typescript' + } }; export default config; diff --git a/.storybook/manager.ts b/.storybook/manager.ts index d4383b1530..185c9bc845 100644 --- a/.storybook/manager.ts +++ b/.storybook/manager.ts @@ -1,4 +1,4 @@ -import { addons } from '@storybook/addons'; +import { addons } from '@storybook/manager-api'; import customTheme from './customTheme'; addons.setConfig({ diff --git a/.storybook/typings.d.ts b/.storybook/typings.d.ts index 8c0abaa133..5c07c07cbf 100644 --- a/.storybook/typings.d.ts +++ b/.storybook/typings.d.ts @@ -1,3 +1,6 @@ +/* eslint-disable */ +// @ts-nocheck + declare module '*.md' { const content: string; export = content; diff --git a/DEVELOPING.md b/DEVELOPING.md index f237bcb204..5c16e5244a 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -138,7 +138,7 @@ Most hooks can be tested with `shallow` rendering except for lifecycle hooks suc To test a `useEffect` hook, you must use `act()` from `react-dom/test-utils` and `mount()` from `enzyme`. ```jsx -import { act } from 'react-dom/test-utils'; +import { act } from 'react'; test('something happens', () => { let wrapper; @@ -164,7 +164,7 @@ test('something happens', () => { // Assert expect(wrapper...); -} +}) ``` See [React Testing Recipes](https://reactjs.org/docs/testing-recipes.html) for more examples. diff --git a/eslint.config.mjs b/eslint.config.mjs index fd3d15df9e..b180e5d9f5 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,3 +1,4 @@ +/* eslint-disable */ import path from "node:path"; import { fileURLToPath } from "node:url"; import js from "@eslint/js"; diff --git a/examples/src/SelectorDropdownExamples.js b/examples/src/SelectorDropdownExamples.js index 73017b6b88..6dc3fe7465 100644 --- a/examples/src/SelectorDropdownExamples.js +++ b/examples/src/SelectorDropdownExamples.js @@ -68,6 +68,7 @@ class SelectorDropdownContainer extends Component { return (
+ {/* eslint-disable-next-line react/no-unknown-property */}
-
-
+ `; @@ -78,28 +59,9 @@ exports[`components/collapsible/Collapsible should apply correct border class 1` /> -
-
+ `; @@ -133,28 +95,9 @@ exports[`components/collapsible/Collapsible should apply correct isOpen state 1` /> -
-
+ `; @@ -188,28 +131,9 @@ exports[`components/collapsible/Collapsible should not render a PlainButton if a /> -
-
+ `; @@ -243,28 +167,9 @@ exports[`components/collapsible/Collapsible should render a PlainButton if a but /> -
-
+ `; @@ -298,28 +203,9 @@ exports[`components/collapsible/Collapsible should render component correctly 1` /> -
-
+ `; @@ -367,28 +253,9 @@ exports[`components/collapsible/Collapsible should render with headerActionItems -
-
+ `; diff --git a/src/components/context-menu/__tests__/ContextMenu.test.tsx b/src/components/context-menu/__tests__/ContextMenu.test.tsx index ebf4cab769..086834e8ba 100644 --- a/src/components/context-menu/__tests__/ContextMenu.test.tsx +++ b/src/components/context-menu/__tests__/ContextMenu.test.tsx @@ -1,4 +1,6 @@ -import * as React from 'react'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import React, { act } from 'react'; import { mount, shallow, ReactWrapper } from 'enzyme'; import sinon from 'sinon'; @@ -202,7 +204,9 @@ describe('components/context-menu/ContextMenu', () => { , ); document.addEventListener = jest.fn(); - wrapper.setState({ isOpen: true }); + act(() => { + wrapper.setState({ isOpen: true }); + }); expect(document.addEventListener).toHaveBeenCalledWith('click', expect.anything(), expect.anything()); expect(document.addEventListener).toHaveBeenCalledWith('contextmenu', expect.anything(), expect.anything()); }); @@ -214,10 +218,14 @@ describe('components/context-menu/ContextMenu', () => { , ); - wrapper.setState({ isOpen: true }); + act(() => { + wrapper.setState({ isOpen: true }); + }); const instance = wrapper.instance(); document.removeEventListener = jest.fn(); - instance.closeMenu(); + act(() => { + instance.closeMenu(); + }); expect(document.removeEventListener).toHaveBeenCalledWith( 'contextmenu', expect.anything(), @@ -233,7 +241,9 @@ describe('components/context-menu/ContextMenu', () => { , ); - wrapper.setState({ isOpen: true }); + act(() => { + wrapper.setState({ isOpen: true }); + }); const instance = wrapper.instance(); document.addEventListener = jest.fn(); document.removeEventListener = jest.fn(); @@ -299,7 +309,9 @@ describe('components/context-menu/ContextMenu', () => { , ); - wrapper.setState({ isOpen: true }); + act(() => { + wrapper.setState({ isOpen: true }); + }); const documentMock = sandbox.mock(document); documentMock.expects('removeEventListener').withArgs('contextmenu'); @@ -404,7 +416,9 @@ describe('components/context-menu/ContextMenu', () => { clientY: 15, preventDefault: preventDefaultSpy, } as unknown) as MouseEvent; - instance.handleContextMenu(handleContextMenuEvent); + act(() => { + instance.handleContextMenu(handleContextMenuEvent); + }); const documentClickEvent = ({ target: document.createElement('div'), @@ -429,7 +443,9 @@ describe('components/context-menu/ContextMenu', () => { clientY: 15, preventDefault: preventDefaultSpy, } as unknown) as MouseEvent; - instance.handleContextMenu(handleContextMenuEvent); + act(() => { + instance.handleContextMenu(handleContextMenuEvent); + }); const documentClickEvent = ({ target: document.getElementById(instance.menuID), diff --git a/src/components/draggable-list/PortaledDraggableListItem.tsx b/src/components/draggable-list/PortaledDraggableListItem.tsx index 473fb3ad93..3093d90721 100644 --- a/src/components/draggable-list/PortaledDraggableListItem.tsx +++ b/src/components/draggable-list/PortaledDraggableListItem.tsx @@ -29,6 +29,8 @@ const PortaledDraggableListItem = ({ ); if (draggableSnapshot.isDragging) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return {listItem}; } return listItem; diff --git a/src/components/dropdown-menu/DropdownMenu.stories.js b/src/components/dropdown-menu/DropdownMenu.stories.js index d897d3d005..2385e0ec01 100644 --- a/src/components/dropdown-menu/DropdownMenu.stories.js +++ b/src/components/dropdown-menu/DropdownMenu.stories.js @@ -1,6 +1,5 @@ // @flow import * as React from 'react'; -import { boolean } from '@storybook/addon-knobs'; import Avatar from '../avatar/Avatar'; import Button from '../button/Button'; @@ -45,10 +44,7 @@ export const basic = () => ( View Profile Help - + Disabled Option @@ -88,7 +84,7 @@ export const responsiveWithHeader = () => ( View Profile Help - Disabled Option + Disabled Option Submenu diff --git a/src/components/dropdown-menu/__tests__/DropdownMenu.test.js b/src/components/dropdown-menu/__tests__/DropdownMenu.test.js index b5d685632a..44d9159579 100644 --- a/src/components/dropdown-menu/__tests__/DropdownMenu.test.js +++ b/src/components/dropdown-menu/__tests__/DropdownMenu.test.js @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { act } from 'react'; import { mount, shallow } from 'enzyme'; import sinon from 'sinon'; @@ -526,7 +526,9 @@ describe('components/dropdown-menu/DropdownMenu', () => { documentMock.expects('addEventListener').withArgs('click', sinon.match.any, !useBubble); documentMock.expects('addEventListener').withArgs('contextmenu', sinon.match.any, !useBubble); documentMock.expects('removeEventListener').never(); - instance.openMenuAndSetFocusIndex(0); + act(() => { + instance.openMenuAndSetFocusIndex(0); + }); }); test('should remove click and contextmenu listeners when closing menu', () => { const wrapper = mount( @@ -536,12 +538,16 @@ describe('components/dropdown-menu/DropdownMenu', () => { , ); const instance = wrapper.instance(); - instance.openMenuAndSetFocusIndex(0); + act(() => { + instance.openMenuAndSetFocusIndex(0); + }); const documentMock = sandbox.mock(document); documentMock.expects('removeEventListener').withArgs('contextmenu', sinon.match.any, !useBubble); documentMock.expects('removeEventListener').withArgs('click', sinon.match.any, !useBubble); documentMock.expects('addEventListener').never(); - instance.closeMenu(); + act(() => { + instance.closeMenu(); + }); }); test('should not do anything opening a menu when menu is already open', () => { const wrapper = mount( @@ -582,7 +588,9 @@ describe('components/dropdown-menu/DropdownMenu', () => { ); const documentMock = sandbox.mock(document); const instance = wrapper.instance(); - instance.openMenuAndSetFocusIndex(0); + act(() => { + instance.openMenuAndSetFocusIndex(0); + }); documentMock.expects('removeEventListener').withArgs('contextmenu', sinon.match.any, !useBubble); documentMock.expects('removeEventListener').withArgs('click', sinon.match.any, !useBubble); wrapper.unmount(); @@ -631,7 +639,9 @@ describe('components/dropdown-menu/DropdownMenu', () => { ); const instance = wrapper.instance(); - instance.openMenuAndSetFocusIndex(0); + act(() => { + instance.openMenuAndSetFocusIndex(0); + }); instance.closeMenu = closeMenuSpy; const handleDocumentClickEvent = { target: document.createElement('div'), @@ -651,11 +661,16 @@ describe('components/dropdown-menu/DropdownMenu', () => { ); const instance = wrapper.instance(); - instance.openMenuAndSetFocusIndex(0); + act(() => { + instance.openMenuAndSetFocusIndex(0); + }); + const handleDocumentClickEvent = { target: document.createElement('div'), }; - instance.handleDocumentClick(handleDocumentClickEvent); + act(() => { + instance.handleDocumentClick(handleDocumentClickEvent); + }); expect(onMenuCloseSpy).toHaveBeenCalledWith(handleDocumentClickEvent); }); diff --git a/src/components/flyout/Flyout.stories.tsx b/src/components/flyout/Flyout.stories.tsx index 0062898a99..e6dc8eed7f 100644 --- a/src/components/flyout/Flyout.stories.tsx +++ b/src/components/flyout/Flyout.stories.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import { select } from '@storybook/addon-knobs'; import Button from '../button'; import IconHelp from '../../icons/general/IconHelp'; @@ -15,18 +14,7 @@ import { Flyout, Overlay } from '.'; import notes from './Flyout.stories.md'; export const Basic = () => { - const positions = { - 'bottom-center': 'bottom-center', - 'bottom-left': 'bottom-left', - 'bottom-right': 'bottom-right', - 'middle-left': 'middle-left', - 'middle-right': 'middle-right', - 'top-center': 'top-left', - 'top-left': 'top-left', - 'top-right': 'top-right', - }; - - const position = select('Position', positions, 'bottom-center'); + const position = 'bottom-center'; return (
diff --git a/src/components/flyout/__tests__/Flyout.test.js b/src/components/flyout/__tests__/Flyout.test.js index 78af5e0544..4f9d70f90b 100644 --- a/src/components/flyout/__tests__/Flyout.test.js +++ b/src/components/flyout/__tests__/Flyout.test.js @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { act } from 'react'; import { mount, shallow } from 'enzyme'; import sinon from 'sinon'; @@ -289,7 +289,7 @@ describe('components/flyout/Flyout', () => { }); describe('handleOverlayClick()', () => { - [ + test.each([ { closeOnClick: true, hasClickableAncestor: true, @@ -310,8 +310,9 @@ describe('components/flyout/Flyout', () => { hasClickableAncestor: false, shouldCloseOverlay: false, }, - ].forEach(({ closeOnClick, hasClickableAncestor, shouldCloseOverlay }) => { - test('should handle clicks within overlay properly', () => { + ])( + 'should handle clicks within overlay properly %s', + ({ closeOnClick, hasClickableAncestor, shouldCloseOverlay }) => { const wrapper = mount( @@ -319,8 +320,10 @@ describe('components/flyout/Flyout', () => { , ); const instance = wrapper.instance(); - instance.setState({ - isVisible: true, + act(() => { + instance.setState({ + isVisible: true, + }); }); const event = {}; @@ -338,10 +341,11 @@ describe('components/flyout/Flyout', () => { .expects('handleOverlayClose') .never(); } - - instance.handleOverlayClick(event); - }); - }); + act(() => { + instance.handleOverlayClick(event); + }); + }, + ); }); describe('handleButtonClick()', () => { @@ -379,10 +383,14 @@ describe('components/flyout/Flyout', () => { const event = { preventDefault: sandbox.stub(), }; - instance.setState({ - isVisible: currentIsVisible, + act(() => { + instance.setState({ + isVisible: currentIsVisible, + }); + }); + act(() => { + instance.handleButtonClick(event); }); - instance.handleButtonClick(event); expect(instance.state.isVisible).toEqual(isVisibleAfterToggle); }); }); @@ -545,10 +553,14 @@ describe('components/flyout/Flyout', () => { const event = { preventDefault: sandbox.stub(), }; - instance.setState({ - isVisible: currentIsVisible, + act(() => { + instance.setState({ + isVisible: currentIsVisible, + }); + }); + act(() => { + instance.openOverlay(event); }); - instance.openOverlay(event); expect(instance.state.isVisible).toEqual(isVisibleAfterOverlayOpened); }); }); @@ -782,8 +794,10 @@ describe('components/flyout/Flyout', () => { const instance = wrapper.instance(); const event = {}; - instance.setState({ - isVisible, + act(() => { + instance.setState({ + isVisible, + }); }); if (shouldCallCloseOverlay) { @@ -894,8 +908,10 @@ describe('components/flyout/Flyout', () => { documentMock.expects('addEventListener').never(); } - instance.setState({ - isVisible: currIsVisible, + act(() => { + instance.setState({ + isVisible: currIsVisible, + }); }); }); }); @@ -922,8 +938,10 @@ describe('components/flyout/Flyout', () => { const instance = wrapper.instance(); const documentMock = sandbox.mock(document); - instance.setState({ - isVisible, + act(() => { + instance.setState({ + isVisible, + }); }); if (shouldRemoveEventListener) { diff --git a/src/components/footer-indicator/__tests__/FooterIndicator.stories.test.tsx b/src/components/footer-indicator/__tests__/FooterIndicator.stories.test.tsx index 7515a4b17a..ac376ccc60 100644 --- a/src/components/footer-indicator/__tests__/FooterIndicator.stories.test.tsx +++ b/src/components/footer-indicator/__tests__/FooterIndicator.stories.test.tsx @@ -1,4 +1,6 @@ +// tslint:disable jest.retryTimes(3); +// tslint:enable describe('components/footer-indicator/FooterIndicator', () => { const STORIES = [['components-footerindicator--regular'], ['components-footerindicator--with-truncated-text']]; test.each(STORIES)('looks visually correct when using story %s', async id => { diff --git a/src/components/form-elements/draft-js-mention-selector/__tests__/DraftJSMentionSelectorCore.test.js b/src/components/form-elements/draft-js-mention-selector/__tests__/DraftJSMentionSelectorCore.test.js index 28c8d9e066..bd741683b9 100644 --- a/src/components/form-elements/draft-js-mention-selector/__tests__/DraftJSMentionSelectorCore.test.js +++ b/src/components/form-elements/draft-js-mention-selector/__tests__/DraftJSMentionSelectorCore.test.js @@ -1,4 +1,4 @@ -import * as React from 'react'; +import React, { act } from 'react'; import { mount, shallow } from 'enzyme'; import { EditorState } from 'draft-js'; import sinon from 'sinon'; @@ -67,7 +67,9 @@ describe('components/form-elements/draft-js-mention-selector/DraftJSMentionSelec ].forEach(({ onMention, isFocused, activeMention, shouldShowMentionStartState }) => { const wrapper = mount(); - wrapper.setState({ activeMention, isFocused }); + act(() => { + wrapper.setState({ activeMention, isFocused }); + }); if (shouldShowMentionStartState) { test('should show MentionStartState', () => { diff --git a/src/components/form-elements/formik/Formik.stories.js b/src/components/form-elements/formik/Formik.stories.js index 6f3d9938cf..30b88db86b 100644 --- a/src/components/form-elements/formik/Formik.stories.js +++ b/src/components/form-elements/formik/Formik.stories.js @@ -1,7 +1,6 @@ // @flow import * as React from 'react'; import { Field, Form, Formik } from 'formik'; -import { boolean } from '@storybook/addon-knobs'; import TextInput from '../../text-input/TextInput'; import TextArea from '../../text-area/TextAreaField'; @@ -61,7 +60,7 @@ export const basic = () => { { component={TextInput} /> { const component = mount(