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 63 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
30 changes: 20 additions & 10 deletions src/controls/tab-bar/focus.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import { KeyCodes } from '../../utils'
const Keys = {
HOME: 'Home',
END: 'End',
LEFT: 'ArrowLeft',
RIGHT: 'ArrowRight',
UP: 'ArrowUp',
DOWN: 'ArrowDown',
}

// Funnction that can be passed to event handlers (e.g., onKeyPress) that manages which element should be focused
// Function that can be passed to event handlers (e.g., onKeyPress) that manages which element should be focused
// Note: Expected keyboard interaction with arrow keys changes depending on the orientation of the tab list
function manageFocus(e, { vertical = false }) {
function manageFocus(e, { vertical }) {
// If not activated while on a tab, then ignore
if (!isTabControl(e.target)) return

const key = (e.which || e.keyCode || '').toString()
// Key will be set to Unidentified if it cannot be mapped
// Source: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#value
const key = e.key === 'Unidentified' ? e.code : e.key
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
switch (key) {
case KeyCodes.DOWN: {
case Keys.DOWN: {
if (!vertical) return
return focusNextControl(e)
}
case KeyCodes.UP: {
case Keys.UP: {
if (!vertical) return
return focusPreviousControl(e)
}
case KeyCodes.LEFT: {
case Keys.LEFT: {
if (vertical) return
return focusPreviousControl(e)
}
case KeyCodes.RIGHT: {
case Keys.RIGHT: {
if (vertical) return
return focusNextControl(e)
}
case KeyCodes.HOME: {
case Keys.HOME: {
return focusFirstControl(e)
}
case KeyCodes.END: {
case Keys.END: {
return focusLastControl(e)
}
default:
Expand Down Expand Up @@ -81,6 +90,7 @@ function getAdjacentControl(control, { previous = false } = {}) {

// Recursively searches for the closest parent tab list
function getClosestTabList(el) {
/* istanbul ignore next */
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't possible to test without changing the markup of the component. If this situation does occur, we'll get an error on undefined.querySelectorAll('[role="tab"]')).

However, it still feels good to me to have this guard in a recursive function? Let me know if you think we should remove it outright

if (!el) return
return el.matches('[role="tablist"]')
? el
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const DEFAULT_REQUEST_OPTIONS = {
const FILE_NAME_PATTERN = /[\s?&#\\%<>]/gi

// Throws an error when a required param is not found
function requireParam(paramName, context = 'Error') {
function requireParam(paramName, context) {
chawes13 marked this conversation as resolved.
Show resolved Hide resolved
throw new Error(`${context}: required param ${paramName} not provided`)
}

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
3 changes: 2 additions & 1 deletion src/tables/sortable-table.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,12 +150,13 @@ function SortableTable({
setSortFunc(() => newSortFunc)
setValueGetter(() => newValueGetter)

if (onChange)
if (onChange) {
onChange({
ascending: newAscending,
sortPath: newSortPath,
sortFunc: newSortFunc,
})
}
}

return (
Expand Down
3 changes: 3 additions & 0 deletions src/utils/local/key-codes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// e.which and e.keyCode are deprecated
// This export will be removed in next major release
// Source: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
const KEY_CODES = {
DOWN: '40',
UP: '38',
Expand Down
8 changes: 5 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,9 @@ import { castArray, compact } from 'lodash'
function triggerOnKeys(fn, keyCodes) {
const codes = compact(castArray(keyCodes))
return function (e) {
const key = e.which || e.keyCode
// Key will be set to Unidentified if it cannot be mapped
// Source: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key#value
const key = e.key === 'Unidentified' ? e.code : e.key
if (!codes.some((keyCode) => keyCode == key)) return

return fn(e)
Expand Down
54 changes: 42 additions & 12 deletions test/controls/color-picker.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
import React from 'react'
import { mount } from 'enzyme'
import { render, screen, act } 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 act(() => user.click(swatchControl))
expect(screen.queryByRole('dialog')).toBeInTheDocument()
await act(() => 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()
})

test('ColorPicker closes when a click is registered outside', async () => {
const user = userEvent.setup()
const { container } = render(<ColorPicker />)
const swatchControl = screen.getByRole('button')

await act(() => user.click(swatchControl))
expect(screen.queryByRole('dialog')).toBeInTheDocument()
await act(() => user.click(container))
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
})

test('ColorPicker calls on change with a hex value', async () => {
const user = userEvent.setup()
const mock = jest.fn()
render(<ColorPicker onChange={mock} />)
const swatchControl = screen.getByRole('button')

await act(() => user.click(swatchControl))
const input = screen.getByRole('textbox')
await user.clear(input)
await user.type(input, '639')

expect(mock).toHaveBeenCalledWith('#663399')
})
Loading