diff --git a/package-lock.json b/package-lock.json index b99728aaf6..89bb26c1ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49791,7 +49791,8 @@ }, "devDependencies": { "@instructure/ui-babel-preset": "8.46.1", - "@instructure/ui-test-utils": "8.46.1" + "@instructure/ui-test-utils": "8.46.1", + "react-dom": "^18.2.0" }, "peerDependencies": { "react": ">=16.8 <=18" @@ -51011,7 +51012,8 @@ "devDependencies": { "@instructure/ui-babel-preset": "8.46.1", "@instructure/ui-test-utils": "8.46.1", - "@types/hoist-non-react-statics": "^3.3.3" + "@types/hoist-non-react-statics": "^3.3.3", + "react-dom": "^18.2.0" }, "peerDependencies": { "react": ">=16.8 <=18" diff --git a/packages/emotion/package.json b/packages/emotion/package.json index 75e1eaad01..2b70e5a2bf 100644 --- a/packages/emotion/package.json +++ b/packages/emotion/package.json @@ -38,7 +38,8 @@ }, "devDependencies": { "@instructure/ui-babel-preset": "8.46.1", - "@instructure/ui-test-utils": "8.46.1" + "@instructure/ui-test-utils": "8.46.1", + "react-dom": "^18.2.0" }, "peerDependencies": { "react": ">=16.8 <=18" diff --git a/packages/emotion/src/__tests__/withStyle.test.tsx b/packages/emotion/src/__tests__/withStyle.test.tsx index f687bd2699..8c601bed99 100644 --- a/packages/emotion/src/__tests__/withStyle.test.tsx +++ b/packages/emotion/src/__tests__/withStyle.test.tsx @@ -24,6 +24,8 @@ /** @jsx jsx */ import React from 'react' +import ReactDOM from 'react-dom' +import ReactTestUtils from 'react-dom/test-utils' import PropTypes from 'prop-types' import { expect, match, mount, stub, within } from '@instructure/ui-test-utils' @@ -136,6 +138,28 @@ describe('@withStyle', async () => { } } + class WrapperComponent extends React.Component { + render() { + return ( +
+ +
+ ) + } + } + + it('can be found and tested with ReactTestUtils', async () => { + const rootNode = document.createElement('div') + document.body.appendChild(rootNode) + + // eslint-disable-next-line react/no-render-return-value + const rendered = ReactDOM.render(, rootNode) + ReactTestUtils.findRenderedComponentWithType( + rendered as any, + (ThemeableComponent as any).originalType + ) + }) + describe('with theme provided by InstUISettingsProvider', async () => { it('should add css class suffixed with label', async () => { const subject = await mount( diff --git a/packages/emotion/src/withStyle.tsx b/packages/emotion/src/withStyle.tsx index 71d626781a..4636dea963 100644 --- a/packages/emotion/src/withStyle.tsx +++ b/packages/emotion/src/withStyle.tsx @@ -173,6 +173,7 @@ const withStyle = decorator( > & { generateComponentTheme?: GenerateComponentTheme allowedProps?: string[] + originalType?: WithStyleComponent } = forwardRef((props, ref) => { const theme = useTheme() @@ -243,6 +244,10 @@ const withStyle = decorator( hoistNonReactStatics(WithStyle, ComposedComponent) + // added so it can be tested with ReactTestUtils + // more info: https://github.com/facebook/react/issues/13455 + WithStyle.originalType = ComposedComponent + // we have to pass these on, because sometimes users // access propTypes of the component in other components // eslint-disable-next-line react/forbid-foreign-prop-types diff --git a/packages/ui-i18n/package.json b/packages/ui-i18n/package.json index 4132778aa4..9d09dd16a0 100644 --- a/packages/ui-i18n/package.json +++ b/packages/ui-i18n/package.json @@ -25,7 +25,8 @@ "devDependencies": { "@instructure/ui-babel-preset": "8.46.1", "@instructure/ui-test-utils": "8.46.1", - "@types/hoist-non-react-statics": "^3.3.3" + "@types/hoist-non-react-statics": "^3.3.3", + "react-dom": "^18.2.0" }, "dependencies": { "@babel/runtime": "^7.23.2", diff --git a/packages/ui-i18n/src/__tests__/bidirectional.test.tsx b/packages/ui-i18n/src/__tests__/bidirectional.test.tsx index 0c98e77e04..46a10c8bd7 100644 --- a/packages/ui-i18n/src/__tests__/bidirectional.test.tsx +++ b/packages/ui-i18n/src/__tests__/bidirectional.test.tsx @@ -23,6 +23,8 @@ */ import React from 'react' +import ReactDOM from 'react-dom' +import ReactTestUtils from 'react-dom/test-utils' import { expect, mount } from '@instructure/ui-test-utils' import { bidirectional, BidirectionalProps } from '../bidirectional' @@ -39,12 +41,34 @@ class BidirectionalComponent extends React.Component { } } +class WrapperComponent extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + describe('@bidirectional', async () => { it('should take on the direction of the document by default', async () => { const subject = await mount() expect(subject.getDOMNode().getAttribute('data-dir')).to.equal('ltr') }) + it('can be found and tested with ReactTestUtils', async () => { + const rootNode = document.createElement('div') + document.body.appendChild(rootNode) + + // eslint-disable-next-line react/no-render-return-value + const rendered = ReactDOM.render(, rootNode) + ReactTestUtils.findRenderedComponentWithType( + rendered as any, + (BidirectionalComponent as any).originalType + ) + }) + it('should set the text direction via props', async () => { const subject = await mount() expect(subject.getDOMNode().getAttribute('data-dir')).to.equal('rtl') diff --git a/packages/ui-i18n/src/bidirectional.tsx b/packages/ui-i18n/src/bidirectional.tsx index dabb55a30f..8a80766474 100644 --- a/packages/ui-i18n/src/bidirectional.tsx +++ b/packages/ui-i18n/src/bidirectional.tsx @@ -22,6 +22,11 @@ * SOFTWARE. */ import React, { ForwardedRef, forwardRef, PropsWithChildren } from 'react' +import type { + ForwardRefExoticComponent, + PropsWithoutRef, + RefAttributes +} from 'react' import { decorator } from '@instructure/ui-decorator' import { DIRECTION, TextDirectionContext } from './TextDirectionContext' import hoistNonReactStatics from 'hoist-non-react-statics' @@ -101,9 +106,13 @@ const bidirectional: BidirectionalType = decorator((ComposedComponent) => { } } - const BidirectionalForwardingRef = forwardRef( - (props, ref) => - ) + const BidirectionalForwardingRef: ForwardRefExoticComponent< + PropsWithoutRef> & RefAttributes + > & { + originalType?: React.ComponentClass + } = forwardRef((props, ref) => ( + + )) if (process.env.NODE_ENV !== 'production') { const displayName = ComposedComponent.displayName || ComposedComponent.name BidirectionalForwardingRef.displayName = `BidirectionalForwardingRef(${displayName})` @@ -114,6 +123,11 @@ const bidirectional: BidirectionalType = decorator((ComposedComponent) => { BidirectionalForwardingRef.propTypes = ComposedComponent.propTypes // @ts-expect-error These static fields exist on InstUI components BidirectionalForwardingRef.allowedProps = ComposedComponent.allowedProps + + // added so it can be tested with ReactTestUtils + // more info: https://github.com/facebook/react/issues/13455 + BidirectionalForwardingRef.originalType = ComposedComponent + return BidirectionalForwardingRef }) as BidirectionalType diff --git a/packages/ui-react-utils/src/DeterministicIdContext/withDeterministicId.tsx b/packages/ui-react-utils/src/DeterministicIdContext/withDeterministicId.tsx index 07e45c01f9..fa7005032b 100644 --- a/packages/ui-react-utils/src/DeterministicIdContext/withDeterministicId.tsx +++ b/packages/ui-react-utils/src/DeterministicIdContext/withDeterministicId.tsx @@ -21,11 +21,11 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -import React, { - forwardRef, +import React, { forwardRef, useContext } from 'react' +import type { + ForwardRefExoticComponent, PropsWithoutRef, - RefAttributes, - useContext + RefAttributes } from 'react' import hoistNonReactStatics from 'hoist-non-react-statics' @@ -49,33 +49,33 @@ type WithDeterministicIdProps = { */ const withDeterministicId = decorator((ComposedComponent: InstUIComponent) => { type Props = PropsWithoutRef> & RefAttributes - const WithDeterministicId = forwardRef( - (props: Props, ref: React.ForwardedRef) => { - const componentName = - ComposedComponent.componentId || - ComposedComponent.displayName || - ComposedComponent.name - const instanceCounterMap = useContext(DeterministicIdContext) - const deterministicId = (instanceName = componentName) => - generateId(instanceName, instanceCounterMap) + const WithDeterministicId: ForwardRefExoticComponent & { + originalType?: React.ComponentClass + } = forwardRef((props: Props, ref: React.ForwardedRef) => { + const componentName = + ComposedComponent.componentId || + ComposedComponent.displayName || + ComposedComponent.name + const instanceCounterMap = useContext(DeterministicIdContext) + const deterministicId = (instanceName = componentName) => + generateId(instanceName, instanceCounterMap) - if (props.deterministicId) { - warn( - false, - `Manually passing the "deterministicId" property is not allowed on the ${componentName} component.\n`, - props.deterministicId - ) - } - - return ( - + if (props.deterministicId) { + warn( + false, + `Manually passing the "deterministicId" property is not allowed on the ${componentName} component.\n`, + props.deterministicId ) } - ) + + return ( + + ) + }) hoistNonReactStatics(WithDeterministicId, ComposedComponent) @@ -89,6 +89,10 @@ const withDeterministicId = decorator((ComposedComponent: InstUIComponent) => { //@ts-expect-error fix this WithDeterministicId.allowedProps = ComposedComponent.allowedProps + // added so it can be tested with ReactTestUtils + // more info: https://github.com/facebook/react/issues/13455 + WithDeterministicId.originalType = ComposedComponent + if (process.env.NODE_ENV !== 'production') { WithDeterministicId.displayName = `WithDeterministicId(${ComposedComponent.displayName})` } diff --git a/packages/ui-react-utils/src/__tests__/DeterministicIdContext.test.tsx b/packages/ui-react-utils/src/__tests__/DeterministicIdContext.test.tsx index cfae13ba74..7195859946 100644 --- a/packages/ui-react-utils/src/__tests__/DeterministicIdContext.test.tsx +++ b/packages/ui-react-utils/src/__tests__/DeterministicIdContext.test.tsx @@ -23,6 +23,8 @@ */ import React from 'react' +import ReactDOM from 'react-dom' +import ReactTestUtils from 'react-dom/test-utils' import { expect, mount } from '@instructure/ui-test-utils' import { @@ -41,6 +43,16 @@ class TestComponent extends React.Component< } } +class WrapperComponent extends React.Component { + render() { + return ( +
+ +
+ ) + } +} + const uniqueIds = (el: { getDOMNode: () => Element }) => { const idList = Array.from(el.getDOMNode().children).map((el) => el.id) @@ -48,6 +60,18 @@ const uniqueIds = (el: { getDOMNode: () => Element }) => { } describe('DeterministicIdContext', () => { + it('can be found and tested with ReactTestUtils', async () => { + const rootNode = document.createElement('div') + document.body.appendChild(rootNode) + + // eslint-disable-next-line react/no-render-return-value + const rendered = ReactDOM.render(, rootNode) + ReactTestUtils.findRenderedComponentWithType( + rendered as any, + (TestComponent as any).originalType + ) + }) + it('should generate unique ids without Provider wrapper', async () => { const el = await mount(