Skip to content

Commit

Permalink
Migrate enzyme to testing library (#586)
Browse files Browse the repository at this point in the history
* Set up React Testing Library

* Migrate Spinner

* convert button tests from enzyme to rtl

* remove unused imports

* remove unused import

* Update jest and set test environment to jsdom

* Bump version

* update checkbox.test.js tests

* checkbox.test.js and checkbox-group.test.js complete

* update button.test.js to use userevent

* update color-input.test.js to rtl

* remove unused imports

* update date input

* complete color-input and date-input

* complete dropdown-checkbox-group

* complete hidden input

* complete icon-input

* Fix date input issues

* RTL migration: controls (#602)

* Migrate paginator

* Add comment

* Remove unused import

* Migrate tab-bar

* RTL migration: modal (#601)

* Initial commit

* Clean up logs

* Add tests more default close interactions

* RTL migration: indicators (#600)

* Migrate loading container

* Migrate flash message

* Migrate flash message container

* Upgdate assertion

* Partial cleanup; Address code review

* Remove unecessary test

* Remove moment dependency from DateInput test

* Update ColorPicker component

* Update DropdownCheckboxGroup

* Update HiddenInput

* Update IconInput

* Update Spinner component

* Bump minor version

* Address comments

* Fix trigger on keys util

* Migrate color-picker

* RTL migration: tables (#603)

* RTL migration: labels (#607)

* partial label folder tests

* Clean up error label

* Fix input error tests

* Remove unused attribute

* Fix input label tests

* Migrate labeled-field tests

* Fixes

---------

Co-authored-by: Alex Jin <[email protected]>

* RTL migration: inputs (#604)

* Migrate input

* Migrate textarea

* Remove unused import

* Migrate switch

* Remove unused import

* Migrate range-input

* Migrate select

* Migrate masked input

* Migrate replace empty string value hoc

* Migrate blur dirty

* Migrate radio group input

* Migrate cloudinary uploader

* Be more explicit

* Use toHaveAttribute

* Do not reference id directly

* RTL migration: file inputs (#590)

* Migrate FileInput to RTL

* Migrate CloudinaryFileInput to RTL

---------

Co-authored-by: Conor <[email protected]>

* Remove enzyme

* Add act back to file input

* Avoid race conditions with act

* Update lock

* Add test for read helper

* Mock server...better

* Address uncovered line in wrap-display-name

* Remove unused default

* Add coverage for modal

* Increase dropdown select coverage

* Increase color picker coverage

* Increase color-input coverage

* Increase to-hex coverage

* Increase paginator coverage

* Increase masked input coverage

* Increase date input coverage

* Increase radio group coverage

* Improve cloudinary-uploader coverage

* Improve coverage for getEnvVar

* Improve sortable table coverage

* Improve tab-bar coverage

* Update trigger on keys

* Add comment

* Replace act with waitFor

---------

Co-authored-by: Alex Jin <[email protected]>
Co-authored-by: Conor Hawes <[email protected]>
  • Loading branch information
3 people authored Sep 11, 2023
1 parent f5146b3 commit c3bf628
Show file tree
Hide file tree
Showing 57 changed files with 2,889 additions and 1,881 deletions.
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
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 */
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) {
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"
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) {
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
55 changes: 43 additions & 12 deletions test/controls/color-picker.test.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,50 @@
import React from 'react'
import { mount } from 'enzyme'
import { render, screen, waitForElementToBeRemoved } 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()
})

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

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

user.click(container)
await waitForElementToBeRemoved(screen.queryByRole('dialog'))
})

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 user.click(swatchControl)
const input = screen.getByRole('textbox')
await user.clear(input)
await user.type(input, '639')

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

0 comments on commit c3bf628

Please sign in to comment.