Skip to content

Commit

Permalink
fix(emotion,ui-i18n,ui-react-utils): make decorated components testab…
Browse files Browse the repository at this point in the history
…le with ReactTestUtils
  • Loading branch information
balzss committed Oct 26, 2023
1 parent c90c3a8 commit 3568250
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 35 deletions.
6 changes: 4 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion packages/emotion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 24 additions & 0 deletions packages/emotion/src/__tests__/withStyle.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -136,6 +138,28 @@ describe('@withStyle', async () => {
}
}

class WrapperComponent extends React.Component {
render() {
return (
<div>
<ThemeableComponent />
</div>
)
}
}

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(<WrapperComponent />, 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(
Expand Down
5 changes: 5 additions & 0 deletions packages/emotion/src/withStyle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ const withStyle = decorator(
> & {
generateComponentTheme?: GenerateComponentTheme
allowedProps?: string[]
originalType?: WithStyleComponent
} = forwardRef((props, ref) => {
const theme = useTheme()

Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-i18n/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 24 additions & 0 deletions packages/ui-i18n/src/__tests__/bidirectional.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -39,12 +41,34 @@ class BidirectionalComponent extends React.Component<BidirectionalProps> {
}
}

class WrapperComponent extends React.Component {
render() {
return (
<div>
<BidirectionalComponent />
</div>
)
}
}

describe('@bidirectional', async () => {
it('should take on the direction of the document by default', async () => {
const subject = await mount(<BidirectionalComponent />)
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(<WrapperComponent />, rootNode)
ReactTestUtils.findRenderedComponentWithType(
rendered as any,
(BidirectionalComponent as any).originalType
)
})

it('should set the text direction via props', async () => {
const subject = await mount(<BidirectionalComponent dir="rtl" />)
expect(subject.getDOMNode().getAttribute('data-dir')).to.equal('rtl')
Expand Down
20 changes: 17 additions & 3 deletions packages/ui-i18n/src/bidirectional.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -101,9 +106,13 @@ const bidirectional: BidirectionalType = decorator((ComposedComponent) => {
}
}

const BidirectionalForwardingRef = forwardRef<any, BidirectionalProps>(
(props, ref) => <BidirectionalComponent {...props} forwardedRef={ref} />
)
const BidirectionalForwardingRef: ForwardRefExoticComponent<
PropsWithoutRef<Record<string, unknown>> & RefAttributes<any>
> & {
originalType?: React.ComponentClass
} = forwardRef<any, BidirectionalProps>((props, ref) => (
<BidirectionalComponent {...props} forwardedRef={ref} />
))
if (process.env.NODE_ENV !== 'production') {
const displayName = ComposedComponent.displayName || ComposedComponent.name
BidirectionalForwardingRef.displayName = `BidirectionalForwardingRef(${displayName})`
Expand All @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -49,33 +49,33 @@ type WithDeterministicIdProps = {
*/
const withDeterministicId = decorator((ComposedComponent: InstUIComponent) => {
type Props = PropsWithoutRef<Record<string, unknown>> & RefAttributes<any>
const WithDeterministicId = forwardRef(
(props: Props, ref: React.ForwardedRef<any>) => {
const componentName =
ComposedComponent.componentId ||
ComposedComponent.displayName ||
ComposedComponent.name
const instanceCounterMap = useContext(DeterministicIdContext)
const deterministicId = (instanceName = componentName) =>
generateId(instanceName, instanceCounterMap)
const WithDeterministicId: ForwardRefExoticComponent<Props> & {
originalType?: React.ComponentClass
} = forwardRef((props: Props, ref: React.ForwardedRef<any>) => {
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 (
<ComposedComponent
ref={ref}
deterministicId={deterministicId}
{...props}
/>
if (props.deterministicId) {
warn(
false,
`Manually passing the "deterministicId" property is not allowed on the ${componentName} component.\n`,
props.deterministicId
)
}
)

return (
<ComposedComponent
ref={ref}
deterministicId={deterministicId}
{...props}
/>
)
})

hoistNonReactStatics(WithDeterministicId, ComposedComponent)

Expand All @@ -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})`
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -41,13 +43,35 @@ class TestComponent extends React.Component<
}
}

class WrapperComponent extends React.Component {
render() {
return (
<div>
<TestComponent />
</div>
)
}
}

const uniqueIds = (el: { getDOMNode: () => Element }) => {
const idList = Array.from(el.getDOMNode().children).map((el) => el.id)

return new Set(idList).size === idList.length
}

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(<WrapperComponent />, rootNode)
ReactTestUtils.findRenderedComponentWithType(
rendered as any,
(TestComponent as any).originalType
)
})

it('should generate unique ids without Provider wrapper', async () => {
const el = await mount(
<div>
Expand Down

0 comments on commit 3568250

Please sign in to comment.