Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate enzyme to testing library #586

Merged
merged 64 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 50 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
08b1a65
Set up React Testing Library
josiasds Mar 13, 2023
9b16899
Migrate Spinner
josiasds Mar 13, 2023
943f105
convert button tests from enzyme to rtl
aojin Mar 22, 2023
f71a8cb
remove unused imports
aojin Mar 22, 2023
305230b
remove unused import
aojin Mar 22, 2023
df47bb0
Update jest and set test environment to jsdom
josiasds Apr 27, 2023
a9643cf
Bump version
josiasds Apr 27, 2023
46fe18e
update checkbox.test.js tests
aojin May 29, 2023
7a1a23e
checkbox.test.js and checkbox-group.test.js complete
aojin May 29, 2023
37dffab
update button.test.js to use userevent
aojin May 29, 2023
0657a87
update color-input.test.js to rtl
aojin May 29, 2023
605072d
remove unused imports
aojin May 29, 2023
6e7e647
update date input
aojin May 30, 2023
f3ffe10
complete color-input and date-input
aojin May 31, 2023
4db55ea
complete dropdown-checkbox-group
aojin May 31, 2023
ca49de6
complete hidden input
aojin May 31, 2023
c81e249
complete icon-input
aojin May 31, 2023
c69b700
Fix date input issues
chawes13 Aug 4, 2023
fef7199
RTL migration: controls (#602)
chawes13 Aug 7, 2023
4359649
RTL migration: modal (#601)
chawes13 Aug 7, 2023
a99338f
RTL migration: indicators (#600)
chawes13 Aug 7, 2023
0c4d1e6
Partial cleanup; Address code review
josiasds Aug 14, 2023
54228c2
Remove unecessary test
josiasds Aug 14, 2023
e846098
Remove moment dependency from DateInput test
josiasds Aug 15, 2023
db3c8d7
Update ColorPicker component
josiasds Aug 15, 2023
56e6d66
Update DropdownCheckboxGroup
josiasds Aug 15, 2023
70f33f6
Update HiddenInput
josiasds Aug 15, 2023
9839116
Update IconInput
josiasds Aug 15, 2023
ac42846
Update Spinner component
josiasds Aug 15, 2023
63738dd
Bump minor version
josiasds Aug 15, 2023
01e41e9
Merge branch 'main' into migrate-enzyme-to-testing-library
chawes13 Aug 15, 2023
f006f4e
Address comments
josiasds Aug 16, 2023
b6306df
Merge branch 'migrate-enzyme-to-testing-library' of github.com:Launch…
josiasds Aug 16, 2023
dac1b9d
Merge branch 'main' into migrate-enzyme-to-testing-library
chawes13 Aug 22, 2023
143aba0
Fix trigger on keys util
chawes13 Aug 22, 2023
455998c
Migrate color-picker
chawes13 Aug 22, 2023
d9406c2
RTL migration: tables (#603)
chawes13 Aug 25, 2023
7eec13e
RTL migration: labels (#607)
chawes13 Aug 25, 2023
8ecd5fb
RTL migration: inputs (#604)
chawes13 Aug 25, 2023
cf5587f
RTL migration: file inputs (#590)
josiasds Aug 25, 2023
ce8827b
Remove enzyme
chawes13 Aug 25, 2023
1177fde
Add act back to file input
chawes13 Aug 25, 2023
4c0a212
Avoid race conditions with act
chawes13 Aug 25, 2023
a55ca33
Update lock
chawes13 Sep 5, 2023
d0a2b15
Add test for read helper
chawes13 Sep 5, 2023
23af73d
Mock server...better
chawes13 Sep 5, 2023
840fe5f
Address uncovered line in wrap-display-name
chawes13 Sep 5, 2023
1e7684f
Remove unused default
chawes13 Sep 5, 2023
00fcbe7
Add coverage for modal
chawes13 Sep 5, 2023
4d9272e
Increase dropdown select coverage
chawes13 Sep 5, 2023
86ba4fe
Increase color picker coverage
chawes13 Sep 5, 2023
065073d
Increase color-input coverage
chawes13 Sep 5, 2023
cc6c923
Increase to-hex coverage
chawes13 Sep 5, 2023
c1b9ff9
Increase paginator coverage
chawes13 Sep 5, 2023
cc26270
Increase masked input coverage
chawes13 Sep 5, 2023
20b5e67
Increase date input coverage
chawes13 Sep 5, 2023
0e39746
Increase radio group coverage
chawes13 Sep 5, 2023
e8cff67
Improve cloudinary-uploader coverage
chawes13 Sep 5, 2023
cd2541a
Improve coverage for getEnvVar
chawes13 Sep 5, 2023
2fdbfca
Improve sortable table coverage
chawes13 Sep 5, 2023
e02d1cc
Improve tab-bar coverage
chawes13 Sep 6, 2023
b5ebff7
Update trigger on keys
chawes13 Sep 6, 2023
65fb434
Add comment
chawes13 Sep 6, 2023
ec062bb
Replace act with waitFor
chawes13 Sep 11, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ module.exports = {
node: false,
'shared-node-browser': true,
},
globals: {
File: true,
},
}
4 changes: 2 additions & 2 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1700,12 +1700,12 @@ Returns **[String][142]** String with namespace removed
### Parameters

* `fn` **[Function][143]** The function to trigger
* `keyCodes` **([Number][145] | [String][142] | [Array][146]<([Number][145] | [String][142])>)** Number, String, or Array of key codes
* `keys` **([String][142] | [Array][146]<[String][142]>)** String or Array of keys

### Examples

```javascript
const triggerOnEnter = triggerOnKeys(() => console.log('Hi'), [13])
const triggerOnEnter = triggerOnKeys(() => console.log('Hi'), ['Enter'])
function MyExample () { return <Example onKeyPress={triggerOnEnter} /> }
```

Expand Down
10 changes: 3 additions & 7 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
module.exports = {
testEnvironment: 'jsdom',
'setupFiles': [
'./test/setup.js',
],
"watchPathIgnorePatterns": [
"<rootDir>/node_modules",
]
}
setupFilesAfterEnv: ['./test/setup.js'],
watchPathIgnorePatterns: ['<rootDir>/node_modules'],
}
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "9.0.0",
"version": "9.1.0",
"engines": {
"node": "^18.12"
},
Expand Down Expand Up @@ -70,14 +70,16 @@
"@storybook/builder-webpack5": "^6.5.14",
"@storybook/manager-webpack5": "^6.5.14",
"@storybook/react": "^6.4.22",
"@wojtekmaj/enzyme-adapter-react-17": "^0.8.0",
"@testing-library/dom": "^9.3.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.4.3",
"babel-loader": "^9.1.0",
"bourbon": "^7.2.0",
"bourbon-neat": "^4.0.0",
"core-js": "^3.21.1",
"css-loader": "^6.7.2",
"documentation": "^14.0.2",
"enzyme": "^3.2.0",
"eslint": "^8.46.0",
"husky": "^8.0.3",
"jest": "^29.6.2",
Expand Down
2 changes: 1 addition & 1 deletion src/controls/color-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function ColorPicker({
}}
/>
{isExpanded && (
<div className="popover">
<div className="popover" role="dialog">
<ChromePicker
color={value}
onChange={({ hex }) => onChange(hex)}
Expand Down
4 changes: 2 additions & 2 deletions src/controls/paginator/page-link.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { noop, triggerOnKeys } from '../../utils'
import classnames from 'classnames'

const ENTER_KEY_CODE = 13
const ENTER_KEY = 'Enter'

const propTypes = {
className: PropTypes.string,
Expand All @@ -24,7 +24,7 @@ function PageLink({ className, active, onClick, children, ...rest }) {
<a
role="link"
onClick={onClick}
onKeyDown={triggerOnKeys(onClick, ENTER_KEY_CODE)} // keyboard interaction requirement
onKeyDown={triggerOnKeys(onClick, ENTER_KEY)} // keyboard interaction requirement
aria-current={active ? 'page' : false}
tabIndex="0" // add back to natural tab order (automatically removed without an href)
{...rest}
Expand Down
2 changes: 1 addition & 1 deletion src/forms/inputs/icon-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function IconInput(props) {
className: classnames('icon-label', className),
}}
>
<i className={`${icon}-icon`} />
<i className={`${icon}-icon`} data-testid="icon" />
</Input>
)
}
Expand Down
1 change: 0 additions & 1 deletion src/indicators/flash-message-container.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ function FlashMessageContainer({ messages, limit, onDismiss, ...rest }) {
return (
<FlashMessage
key={message.id}
message={message}
isError={message.isError}
{...rest}
{...message.props}
Expand Down
3 changes: 2 additions & 1 deletion src/indicators/spinner.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { filterInvalidDOMProps } from '../utils'
import classnames from 'classnames'
import { filterInvalidDOMProps } from '../utils'

/**
*
Expand Down Expand Up @@ -38,6 +38,7 @@ const defaultProps = {
function Spinner({ className, ...rest }) {
return (
<div
role="progressbar"
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
className={classnames('spinner', className)}
{...filterInvalidDOMProps(rest)}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/tables/helpers/get-column-data.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { castArray, has } from '../../utils'

// Get column info from children via props
function getColumnData(children = [], doDisable) {
function getColumnData(children, doDisable) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was only called in sortable-table, whose prop types require children. We were getting a flag for missing a branch here, but in reality this isn't a valid use of this util.

const childrenArray = castArray(children)
return childrenArray
.filter((child) => has(child, 'props'))
Expand Down
6 changes: 3 additions & 3 deletions src/utils/local/trigger-on-keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
* @name triggerOnKeys
* @type Function
* @param {Function} fn - The function to trigger
* @param {Number|String|Array<Number|String>} keyCodes - Number, String, or Array of key codes
* @param {String|Array<String>} keys - String or Array of keys
* @returns {Function} - Returns a function that takes an event and watches for keys
*
* @example
*
* const triggerOnEnter = triggerOnKeys(() => console.log('Hi'), [13])
* const triggerOnEnter = triggerOnKeys(() => console.log('Hi'), ['Enter'])
* function MyExample () { return <Example onKeyPress={triggerOnEnter} /> }
*/

Expand All @@ -16,7 +16,7 @@ import { castArray, compact } from 'lodash'
function triggerOnKeys(fn, keyCodes) {
const codes = compact(castArray(keyCodes))
return function (e) {
const key = e.which || e.keyCode
const key = e.key || e.code
josiasds marked this conversation as resolved.
Show resolved Hide resolved
if (!codes.some((keyCode) => keyCode == key)) return

return fn(e)
Expand Down
29 changes: 17 additions & 12 deletions test/controls/color-picker.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import React from 'react'
import { mount } from 'enzyme'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { ColorPicker } from '../../src/'

test('ColorPicker toggles expanded when swatch is clicked', () => {
const wrapper = mount(<ColorPicker />)
expect(wrapper.find('.popover').exists()).toBe(false)
wrapper.find('.swatch').simulate('click')
expect(wrapper.find('.popover').exists()).toBe(true)
wrapper.find('.swatch').simulate('click')
expect(wrapper.find('.popover').exists()).toBe(false)
test('ColorPicker toggles expanded when swatch is clicked', async () => {
const user = userEvent.setup()
render(<ColorPicker />)
const swatchControl = screen.getByRole('button')

expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
await user.click(swatchControl)
expect(screen.queryByRole('dialog')).toBeInTheDocument()
await user.click(swatchControl)
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})

test('ColorPicker can be externally controlled', () => {
const wrapper = mount(<ColorPicker active={true} />)
expect(wrapper.find('.popover').exists()).toBe(true)
wrapper.setProps({ active: false })
expect(wrapper.find('.popover').exists()).toBe(false)
const { rerender } = render(<ColorPicker active={true} />)

expect(screen.queryByRole('dialog')).toBeInTheDocument()
rerender(<ColorPicker active={false} />)
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})
104 changes: 56 additions & 48 deletions test/controls/paginator/paginator.test.js
Original file line number Diff line number Diff line change
@@ -1,99 +1,107 @@
import React from 'react'
import { mount } from 'enzyme'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Paginator } from '../../../src/'

test('Previous button renders unless value is min', () => {
const wrapper = mount(<Paginator value={5} min={1} max={10} />)
expect(wrapper.find('.prev').exists()).toBe(true)
wrapper.setProps({ value: 1 })
expect(wrapper.find('.prev').exists()).toBe(false)
test('Previous control renders unless value is min', () => {
const { rerender } = render(<Paginator value={5} min={1} max={10} />)
expect(screen.getByRole('link', { name: /previous page/i })).toBeInTheDocument()
rerender(<Paginator value={1} min={1} max={10} />)
expect(screen.queryByRole('link', { name: /previous page/i })).not.toBeInTheDocument()
})

test('Next button renders unless value is max', () => {
const wrapper = mount(<Paginator value={5} min={1} max={10} />)
expect(wrapper.find('.next').exists()).toBe(true)
wrapper.setProps({ value: 10 })
expect(wrapper.find('.next').exists()).toBe(false)
test('Next control renders unless value is max', () => {
const { rerender } = render(<Paginator value={5} min={1} max={10} />)
expect(screen.getByRole('link', { name: /next page/i })).toBeInTheDocument()
rerender(<Paginator value={10} min={1} max={10} />)
expect(screen.queryByRole('link', { name: /next page/i })).not.toBeInTheDocument()
})

test('Button with current value is marked as active', () => {
const wrapper = mount(<Paginator value={5} min={1} max={10} />)
expect(wrapper.find('.active').text()).toBe('5')
test('Control with current value is marked as active', () => {
render(<Paginator value={5} min={1} max={10} />)
expect(screen.getByText(5).parentElement).toHaveClass('active')
})

test('Button click sets value', () => {
test('Control click sets value', async () => {
const onChange = jest.fn()
const wrapper = mount(
const user = userEvent.setup()

render(
<Paginator value={5} min={1} max={10} onChange={onChange} />
)
wrapper.find('li > a').at(2).simulate('click')
await user.click(screen.getAllByRole('link').at(2))
expect(onChange).toHaveBeenCalledWith(4)
})

test('Previous button decrements value', () => {
test('Previous control decrements value', async () => {
const onChange = jest.fn()
const wrapper = mount(
<Paginator value={5} min={1} max={10} onChange={onChange} />
const user = userEvent.setup()
const currentValue = 5
render(
<Paginator value={currentValue} min={1} max={10} onChange={onChange} />
)
wrapper.find('li > a').first().simulate('click')
expect(onChange).toHaveBeenCalledWith(4)
await user.click(screen.getByRole('link', { name: /previous page/i }))
expect(onChange).toHaveBeenCalledWith(currentValue - 1)
})

test('Next button increments value', () => {
test('Next control increments value', async () => {
const onChange = jest.fn()
const wrapper = mount(
<Paginator value={5} min={1} max={10} onChange={onChange} />
const user = userEvent.setup()
const currentValue = 5
render(
<Paginator value={currentValue} min={1} max={10} onChange={onChange} />
)
wrapper.find('li > a').last().simulate('click')
expect(onChange).toHaveBeenCalledWith(6)
await user.click(screen.getByRole('link', { name: /next page/i }))
expect(onChange).toHaveBeenCalledWith(currentValue + 1)
})

test('Min button sets value to min', () => {
test('Min control sets value to min', async () => {
const onChange = jest.fn()
const wrapper = mount(
const user = userEvent.setup()
render(
<Paginator value={5} min={1} max={10} onChange={onChange} />
)
wrapper.find('li > a').at(1).simulate('click')
await user.click(screen.getByRole('link', { name: /page 1$/ }))
expect(onChange).toHaveBeenCalledWith(1)
})

test('Max button sets value to max', () => {
test('Max control sets value to max', async () => {
const onChange = jest.fn()
const wrapper = mount(
const user = userEvent.setup()
render(
<Paginator value={5} min={1} max={10} onChange={onChange} />
)
wrapper.find('li > a').at(5).simulate('click')
await user.click(screen.getByRole('link', { name: /page 10$/ }))
expect(onChange).toHaveBeenCalledWith(10)
})

test('Current page is indicated via aria-current', () => {
const wrapper = mount(<Paginator value={5} min={1} max={10} />)
expect(wrapper.find('.active > a').prop('aria-current')).toBe('page')
expect(wrapper.find('a').not('.active').first().prop('aria-current')).toBe(
false
)
render(<Paginator value={5} min={1} max={10} />)
expect(screen.getByText(5)).toHaveAttribute('aria-current', 'page')
expect(screen.getByText(1)).toHaveAttribute('aria-current', 'false')
})

test('Destination is indicated via aria-label', () => {
const wrapper = mount(<Paginator value={5} min={1} max={10} />)
expect(wrapper.find('.active > a').prop('aria-label')).toBe('Go to page 5')
render(<Paginator value={5} min={1} max={10} />)
expect(screen.getByLabelText('Go to page 5')).toBeInTheDocument()
})

test('Page button is triggered via click or enter', () => {
test('Page control is triggered via click or enter', async () => {
const onChange = jest.fn()
const wrapper = mount(
const user = userEvent.setup()
render(
<Paginator value={5} min={1} max={10} onChange={onChange} />
)
const link = wrapper.find('li > a').at(2)
link.simulate('click')
link.simulate('keydown', { keyCode: 13 })

expect(onChange).toHaveBeenCalledTimes(2)
await user.click(screen.getByLabelText('Go to page 1'))
await user.keyboard('{Enter}')
expect(onChange).toHaveBeenNthCalledWith(1, 1)
expect(onChange).toHaveBeenNthCalledWith(2, 1)
})

test('Can accept custom delimiter', () => {
const wrapper = mount(
render(
<Paginator value={1} min={1} max={10} delimiter="foo" />
)
expect(wrapper.find('.delimiter').contains('foo')).toBe(true)
expect(screen.getByText('foo')).toBeInTheDocument()
})
Loading