diff --git a/.babelrc b/.babelrc
index 59fac07ef..fbf43a8ea 100644
--- a/.babelrc
+++ b/.babelrc
@@ -4,6 +4,10 @@
"presets": [
["@babel/preset-env", { "modules": "cjs" }],
"@babel/preset-react"
+ ],
+ "plugins": [
+ "@babel/plugin-transform-regenerator",
+ "@babel/plugin-transform-runtime"
]
},
"build": {
diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml
index 7d646af50..629c76267 100644
--- a/.github/workflows/pull-request.yml
+++ b/.github/workflows/pull-request.yml
@@ -43,6 +43,15 @@ jobs:
- name: unit test
run: |
yarn test
+ - name: Upload coverage to Codecov
+ uses: codecov/codecov-action@v1.0.7
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ file: ./coverage/clover.xml
+ flags: unittests
+ name: codecov-umbrella
+ yml: ./codecov.yml
+ fail_ci_if_error: true
visual-tests:
name: visual regression tests
runs-on: ubuntu-latest
diff --git a/.gitignore b/.gitignore
index bada37a8a..7e48dcc95 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,6 +9,7 @@
/coverage
# production
+/coverage
/dist
/build
/storybook-static
diff --git a/.storybook/main.js b/.storybook/main.js
index 647e95537..ca07930e9 100644
--- a/.storybook/main.js
+++ b/.storybook/main.js
@@ -21,15 +21,13 @@ const scssRules = {
const fileLoaderRules = {
test: /\.(png|jpe?g|gif)$/i,
- use: [
- {
- loader: 'file-loader',
- },
- ],
+ use: [{
+ loader: 'file-loader',
+ }, ],
};
module.exports = {
- stories: ['../src/**/*.stories.[tj]sx'],
+ stories: ['../src/**/*.stories.([tj]sx|mdx)'],
addons: [
'@storybook/addon-actions/register',
'@storybook/addon-a11y/register',
@@ -40,7 +38,9 @@ module.exports = {
webpackFinal: (config) => {
config.module.rules.push(scssRules);
config.module.rules.push(fileLoaderRules);
- const assetRule = config.module.rules.find(({ test }) => test.test(".svg"));
+ const assetRule = config.module.rules.find(({
+ test
+ }) => test.test(".svg"));
const assetLoader = {
loader: assetRule.loader,
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 000000000..e9d858d19
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,20 @@
+codecov:
+ require_ci_to_pass: yes
+
+coverage:
+ precision: 2
+ round: down
+ range: "80...100"
+
+parsers:
+ gcov:
+ branch_detection:
+ conditional: yes
+ loop: yes
+ method: no
+ macro: no
+
+comment:
+ layout: "reach,diff,flags,tree"
+ behavior: default
+ require_changes: no
\ No newline at end of file
diff --git a/jest.config.js b/jest.config.js
index 9a8db2f64..d54afb866 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -11,4 +11,6 @@ module.exports = {
'identity-obj-proxy',
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
},
+ coverageDirectory: './coverage/',
+ collectCoverage: true,
};
diff --git a/package.json b/package.json
index be1b1f580..fb6f86ec0 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@palmetto/palmetto-components",
"author": "@palmetto",
- "version": "0.1.6",
+ "version": "0.1.7",
"private": false,
"main": "dist/index.js",
"module": "dist/index.esm.js",
@@ -45,6 +45,8 @@
},
"devDependencies": {
"@babel/core": "^7.10.3",
+ "@babel/plugin-transform-regenerator": "^7.10.4",
+ "@babel/plugin-transform-runtime": "^7.10.4",
"@babel/polyfill": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@rollup/plugin-babel": "^5.0.4",
@@ -68,6 +70,7 @@
"chromatic": "^4.0.3",
"classnames": "^2.2.6",
"cleave.js": "^1.6.0",
+ "codecov": "^3.7.0",
"css-loader": "^3.6.0",
"eslint": "^7.3.1",
"eslint-config-airbnb": "^18.2.0",
@@ -81,12 +84,14 @@
"identity-obj-proxy": "^3.0.0",
"jest": "^24.9.0",
"mini-css-extract-plugin": "^0.9.0",
+ "mutationobserver-shim": "^0.3.7",
"node-sass": "^4.14.1",
"postcss-loader": "^3.0.0",
"prop-types": "^15.7.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-is": "^16.13.1",
+ "react-select-event": "^4.1.4",
"rollup": "^1.27.4",
"rollup-plugin-cleaner": "^1.0.0",
"rollup-plugin-copy": "^3.3.0",
diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.jsx
index fbb1c3f40..acb9a1fe2 100644
--- a/src/components/Button/Button.jsx
+++ b/src/components/Button/Button.jsx
@@ -94,10 +94,7 @@ Button.propTypes = {
/**
* Contents of the button
*/
- children: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.node,
- ]).isRequired,
+ children: PropTypes.node.isRequired,
/**
* Disables the button, making it inoperable
*/
@@ -107,7 +104,7 @@ Button.propTypes = {
*/
isLoading: PropTypes.bool,
/**
- * Callback when button receives focus
+ * Allow the button to grow to its container's full width
*/
fullWidth: PropTypes.bool,
/**
diff --git a/src/components/Button/Button.stories.jsx b/src/components/Button/Button.stories.jsx
deleted file mode 100644
index b222f0f9c..000000000
--- a/src/components/Button/Button.stories.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import React from 'react';
-import { action } from '@storybook/addon-actions';
-import { withA11y } from '@storybook/addon-a11y';
-import Button from './Button';
-
-export default {
- title: 'Button',
- component: Button,
- decorators: [withA11y],
-};
-
-export const All = () => {
- const handleClick = event => {
- action('clicked')(event);
- };
-
- const handleBlur = event => {
- action('blurred')(event);
- };
-
- const handleFocus = event => {
- action('focused')(event);
- };
-
- return (
- <>
-
-
- Button
-
-
-
-
- Full Width Button
-
-
-
-
- Disabled Button
-
-
-
-
- Button with a really long name. Width will not decrease when loading indicator is rendered.
-
-
- >
- );
-};
diff --git a/src/components/Button/Button.stories.mdx b/src/components/Button/Button.stories.mdx
new file mode 100644
index 000000000..01758494c
--- /dev/null
+++ b/src/components/Button/Button.stories.mdx
@@ -0,0 +1,113 @@
+import Button from './Button';
+import { action } from '@storybook/addon-actions';
+import { withA11y } from '@storybook/addon-a11y';
+import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
+
+
+
+
+# Button
+
+Buttons are used as triggers for actions.
+They are used in forms, toolbars, modal dialogs and as stand-alone action triggers.
+Actions almost always occur on the same page.
+
+
+ alert('clicked')}>
+ Button
+
+
+
+## Props
+
+
+## Appearance
+
+
+
+
+ Button
+
+
+
+
+## Full Width
+
+Use the `fullWidth` prop to allow the button to grow to its container's full width.
+
+
+
+
+ Full Width
+
+
+
+
+## Loading
+
+Setting the `isLoading` prop to true will replace the button text with loading indicator and disable the button to prevent unintended submissions.
+The width of the button will remain consistent to prevent content from shifting.
+
+
+
+ <>
+
+
+ Log In
+
+
+
+
+ Button with a really long name. Width will not decrease when loading indicator is rendered.
+
+
+
+
+ Full Width Loading Button
+
+
+ >
+
+
+
+## Disabled
+
+Use for actions that aren’t currently available, such as immediately after a form submission.
+The interface should make it clear why the button is disabled and what needs to be done to enable it.
+
+
+
+ <>
+
+
+ Disabled Button
+
+
+
+
+ Disabled Full Width Button
+
+
+ >
+
+
+
+## Event Callbacks
+
+Callback functions can be passed to `onClick`, `onBlur`, and `onFocus` events.
+
+
+
+
+ click, focus, blur events
+
+
+
diff --git a/src/components/CheckboxInput/CheckboxInput.jsx b/src/components/CheckboxInput/CheckboxInput.jsx
index 45510067d..06ea43780 100644
--- a/src/components/CheckboxInput/CheckboxInput.jsx
+++ b/src/components/CheckboxInput/CheckboxInput.jsx
@@ -16,17 +16,22 @@ const CheckboxInput = ({
isChecked,
isDisabled,
isRequired,
+ onBlur,
onChange,
+ onFocus,
label,
}) => {
+ const handleBlur = e => {
+ if (onBlur) onBlur(e);
+ };
+
const handleChange = e => {
- onChange(e.target.checked);
+ onChange(e);
};
- // const labelClasses = classNames(
- // 'checkboxInputInstructions',
- // { error },
- // );
+ const handleFocus = e => {
+ if (onFocus) onFocus(e);
+ };
const labelProps = {
isFieldRequired: isRequired,
@@ -40,10 +45,14 @@ const CheckboxInput = ({
@@ -60,6 +69,8 @@ CheckboxInput.defaultProps = {
isChecked: false,
isDisabled: false,
isRequired: false,
+ onBlur: undefined,
+ onFocus: undefined,
};
CheckboxInput.propTypes = {
@@ -92,10 +103,18 @@ CheckboxInput.propTypes = {
* Determines if input is required or not. (Label will have an asterisk if required)
*/
isRequired: PropTypes.bool,
+ /**
+ * Callback function when input is blurred.
+ */
+ onBlur: PropTypes.func,
/**
* Callback function when input is changed
*/
onChange: PropTypes.func.isRequired,
+ /**
+ * Callback function when input is focused
+ */
+ onFocus: PropTypes.func,
/**
* Custom content to be displayed to right of checkbox. Can be any valid node/tree, anchors, etc.
*/
diff --git a/src/components/CheckboxInput/CheckboxInput.stories.jsx b/src/components/CheckboxInput/CheckboxInput.stories.jsx
index 3cf4befbb..3fb225c64 100644
--- a/src/components/CheckboxInput/CheckboxInput.stories.jsx
+++ b/src/components/CheckboxInput/CheckboxInput.stories.jsx
@@ -5,7 +5,7 @@ import { withA11y } from '@storybook/addon-a11y';
import CheckboxInput from './CheckboxInput';
export default {
- title: 'Forms/Inputs/CheckboxInput',
+ title: 'Components/Form Inputs/CheckboxInput',
component: CheckboxInput,
decorators: [withA11y],
};
@@ -22,9 +22,9 @@ export const All = () => {
withErrorRequired: false,
});
- const handleChange = (value, key) => {
- action('change')(value);
- store.set({ [key]: value });
+ const handleChange = (event, key) => {
+ action('change')(event);
+ store.set({ [key]: event.target.checked });
};
return (
diff --git a/src/components/CheckboxInput/CheckboxInput.test.jsx b/src/components/CheckboxInput/CheckboxInput.test.jsx
index b14e1ae1e..9959003fb 100644
--- a/src/components/CheckboxInput/CheckboxInput.test.jsx
+++ b/src/components/CheckboxInput/CheckboxInput.test.jsx
@@ -12,7 +12,7 @@ describe('CheckboxInput', () => {
null}
/>,
);
@@ -28,8 +28,8 @@ describe('CheckboxInput', () => {
null}
+ isChecked={false}
/>,
);
expect(getByLabelText('test checkbox')).toBeDefined();
@@ -40,7 +40,6 @@ describe('CheckboxInput', () => {
null}
isChecked
/>,
@@ -54,15 +53,27 @@ describe('CheckboxInput', () => {
null}
isChecked={false}
+ onChange={() => null}
/>,
);
const checkbox = getByLabelText('test checkbox');
expect(checkbox.checked).toEqual(false);
});
+ test('assigns the "aria-labelledby" attribute and renders a label with correct id, when a label is provided', () => {
+ const { getByLabelText } = render(
+ null}
+ />,
+ );
+ expect(getByLabelText('test label')).toHaveAttribute('aria-labelledby', 'testInputLabel');
+ expect(document.getElementById('testInputLabel')).toBeInTheDocument();
+ });
+
describe('onChange', () => {
test('onChange event fires callback function', () => {
const mockedHandleChange = jest.fn(() => null);
@@ -71,7 +82,7 @@ describe('CheckboxInput', () => {
,
);
@@ -80,37 +91,22 @@ describe('CheckboxInput', () => {
expect(mockedHandleChange).toHaveBeenCalledTimes(1);
});
- test('calls onChange with true when isCheck is false', () => {
- const mockedHandleChange = jest.fn(() => null);
-
- const { getByLabelText } = render(
- ,
- );
- const checkbox = getByLabelText('test checkbox');
- fireEvent.click(checkbox);
- expect(mockedHandleChange).toHaveBeenCalledWith(true);
- });
-
- test('calls onChange with false when isChecked when true', () => {
- const mockedHandleChange = jest.fn(() => null);
+ test('calls onChange and passes checked value in event', () => {
+ let value = true;
+ const mockedHandleChange = jest.fn(event => { value = event.target.checked; });
const { getByLabelText } = render(
,
);
const checkbox = getByLabelText('test checkbox');
fireEvent.click(checkbox);
- expect(mockedHandleChange).toHaveBeenCalledWith(false);
+ expect(mockedHandleChange).toBeCalledTimes(1);
+ expect(value).toBe(false);
});
});
});
diff --git a/src/components/FormLabel/FormLabel.jsx b/src/components/FormLabel/FormLabel.jsx
index 09e84f1e3..48ab3a97f 100644
--- a/src/components/FormLabel/FormLabel.jsx
+++ b/src/components/FormLabel/FormLabel.jsx
@@ -17,7 +17,11 @@ const FormLabel = ({
);
return (
-
+
{labelText}
{isFieldRequired && * }
diff --git a/src/components/FormLabel/FormLabel.stories.mdx b/src/components/FormLabel/FormLabel.stories.mdx
new file mode 100644
index 000000000..ef42e2217
--- /dev/null
+++ b/src/components/FormLabel/FormLabel.stories.mdx
@@ -0,0 +1,39 @@
+import FormLabel from './FormLabel';
+import { action } from '@storybook/addon-actions';
+import { withA11y } from '@storybook/addon-a11y';
+import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
+
+
+
+# FormLabel
+
+FormLabel is a subcomponent of form inputs and are not intended to be used directly.
+
+## Props
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <>
+
+
+ >
+
+
diff --git a/src/components/FormLabel/FormLabel.test.jsx b/src/components/FormLabel/FormLabel.test.jsx
index cba253ff3..cea500421 100644
--- a/src/components/FormLabel/FormLabel.test.jsx
+++ b/src/components/FormLabel/FormLabel.test.jsx
@@ -44,4 +44,10 @@ describe('FormLabel', () => {
const labelElement = screen.getByText('my label');
expect(labelElement.getAttribute('class')).toContain('error');
});
+
+ test('correctly assigns an id when given an inputId', () => {
+ render( );
+ const labelElement = screen.getByText('my label');
+ expect(labelElement).toHaveAttribute('id', 'myIdLabel');
+ });
});
diff --git a/src/components/Formik/Formik.stories.jsx b/src/components/Formik/Formik.stories.jsx
index d12006022..222381aa3 100644
--- a/src/components/Formik/Formik.stories.jsx
+++ b/src/components/Formik/Formik.stories.jsx
@@ -1,20 +1,39 @@
import React from 'react';
import { withA11y } from '@storybook/addon-a11y';
import { Field, Form, Formik } from 'formik';
+import { action } from '@storybook/addon-actions';
+import Button from '../Button/Button';
import FormikTextInput from './FormikTextInput/FormikTextInput';
import FormikCheckboxInput from './FormikCheckboxInput/FormikCheckboxInput';
-import Button from '../Button/Button';
+import FormikSelectInput from './FormikSelectInput/FormikSelectInput';
export default {
- title: 'Forms/Formik',
+ title: 'Patterns/Formik Form',
decorators: [withA11y],
subcomponents: {
- FormikTextInput,
FormikCheckboxInput,
+ FormikSelectInput,
+ FormikTextInput,
},
};
export const FormikForm = () => {
+ const flavorOptions = [
+ { value: 'chocolate', label: 'Chocolate' },
+ { value: 'strawberry', label: 'Strawberry' },
+ { value: 'vanilla', label: 'Vanilla' },
+ ];
+
+ const colorOptions = [
+ { value: 'red', label: 'red' },
+ { value: 'orange', label: 'orange' },
+ { value: 'yellow', label: 'yellow' },
+ { value: 'green', label: 'green' },
+ { value: 'blue', label: 'blue' },
+ { value: 'indigo', label: 'indigo' },
+ { value: 'violet', label: 'violet' },
+ ];
+
const handleValidation = values => {
const errors = {};
if (!values.email) {
@@ -24,6 +43,9 @@ export const FormikForm = () => {
) {
errors.email = 'Invalid email address';
}
+ if (!values.flavor) {
+ errors.flavor = 'Flavor is required';
+ }
return errors;
};
@@ -45,21 +67,31 @@ export const FormikForm = () => {
lastName: '',
email: '',
areTermsChecked: false,
+ flavor: null,
+ flavor2: null,
+ colors: [],
}}
validate={handleValidation}
onSubmit={handleSubmit}
- render={({ isSubmitting, values }) => (
+ handleChange={event => action('change')(event)}
+ render={({ isSubmitting, values, setFieldValue }) => (
@@ -134,7 +136,7 @@ export const All = () => {
isDisabled
isRequired
value={state.disabledRequired}
- onChange={value => handleChange(value, 'disabledRequired')}
+ onChange={event => handleChange(event.target.value, 'disabledRequired')}
options={selectOptions}
/>
@@ -144,7 +146,7 @@ export const All = () => {
label="Disabled Single Select Pre-Selected"
isDisabled
value={state.disabledSingleSelectPreselected}
- onChange={value => handleChange(value, 'disabledSingleSelectPreselected')}
+ onChange={event => handleChange(event.target.value, 'disabledSingleSelectPreselected')}
options={selectOptions}
/>
@@ -155,7 +157,7 @@ export const All = () => {
isMulti
isDisabled
value={state.disabledMultiValuePreselected}
- onChange={value => handleChange(value, 'disabledMultiValuePreselected')}
+ onChange={event => handleChange(event.target.value, 'disabledMultiValuePreselected')}
options={selectOptions}
/>
@@ -163,9 +165,9 @@ export const All = () => {
handleChange(value, 'invalid')}
+ onChange={event => handleChange(event.target.value, 'invalid')}
options={selectOptions}
/>
@@ -173,9 +175,9 @@ export const All = () => {
handleChange(value, 'invalidNotRequired')}
+ onChange={event => handleChange(event.target.value, 'invalidNotRequired')}
options={selectOptions}
/>
@@ -183,10 +185,10 @@ export const All = () => {
handleChange(value, 'invalidRequired')}
+ onChange={event => handleChange(event.target.value, 'invalidRequired')}
options={selectOptions}
/>
diff --git a/src/components/SelectInput/SelectInput.test.jsx b/src/components/SelectInput/SelectInput.test.jsx
new file mode 100644
index 000000000..9fc657ba6
--- /dev/null
+++ b/src/components/SelectInput/SelectInput.test.jsx
@@ -0,0 +1,304 @@
+import React from 'react';
+import Select from 'react-select';
+import '@babel/polyfill';
+import 'mutationobserver-shim';
+import {
+ render,
+ fireEvent,
+ screen,
+} from '@testing-library/react';
+import '@testing-library/jest-dom/extend-expect';
+import selectEvent from 'react-select-event';
+import SelectInput from './SelectInput';
+
+const selectOptions = [
+ { value: 'chocolate', label: 'Chocolate' },
+ { value: 'strawberry', label: 'Strawberry' },
+ { value: 'vanilla', label: 'Vanilla' },
+];
+
+describe('SelectInput', () => {
+ describe('Props Validation', () => {
+ test('Throws error if required prop "id" is not supplied to component', () => {
+ const mockedHandleChange = jest.fn();
+
+ console.error = jest.fn(); // eslint-disable-line no-console
+ render(
+ ,
+ );
+ expect(console.error).toHaveBeenCalledTimes(2); // eslint-disable-line no-console
+ expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
+ .toContain('Failed prop type: The prop `id`');
+ expect(console.error.mock.calls[1][0]) // eslint-disable-line no-console
+ .toContain('Failed prop type: The prop `inputId` is marked as required in `FormLabel`');
+ });
+
+ test('Throws error if required prop "onChange" is not supplied to component', () => {
+ console.error = jest.fn(); // eslint-disable-line no-console
+ render(
+ ,
+ );
+ expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
+ expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
+ .toContain('Failed prop type: The prop `onChange`');
+ });
+
+ test('Throws error if required prop "options" is not supplied to component', () => {
+ const mockedHandleChange = jest.fn();
+
+ console.error = jest.fn(); // eslint-disable-line no-console
+ render(
+ ,
+ );
+ expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
+ expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
+ .toContain('Failed prop type: The prop `options`');
+ });
+ });
+
+ describe('Callback Handling', () => {
+ test('it fires onChange callback on change', async () => {
+ const mockedHandleChange = jest.fn();
+
+ const { getByLabelText } = render(
+ ,
+ );
+
+ await selectEvent.select(getByLabelText('onchange test'), 'Vanilla');
+
+ expect(mockedHandleChange).toBeCalledTimes(1);
+ });
+
+ test('it fires onFocus callback on focus', () => {
+ const mockedHandleChange = jest.fn();
+ const mockedHandleFocus = jest.fn();
+
+ render(
+ ,
+ );
+
+ fireEvent.focus(screen.getByRole('textbox'));
+
+ expect(mockedHandleFocus).toBeCalledTimes(1);
+ });
+
+ test('it fires onBlur callback on blur', () => {
+ const mockedHandleChange = jest.fn();
+ const mockedHandleBlur = jest.fn();
+
+ render(
+ ,
+ );
+
+ fireEvent.blur(screen.getByRole('textbox'));
+
+ expect(mockedHandleBlur).toBeCalledTimes(1);
+ });
+ });
+
+ describe('States', () => {
+ describe('Hidden label, with a placeholder', () => {
+ test('it renders input without a visual label, and with a placeholder', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+ expect(screen.queryByText('hidden label')).toBeNull();
+ expect(screen.getByText('Test Placeholder')).toBeInTheDocument();
+ });
+ });
+
+ test('does not assign "aria-labelledby" attribute when a label is hidden', () => {
+ const mockedHandleChange = jest.fn();
+
+ render( );
+ const inputElement = screen.getByLabelText('hidden label');
+ expect(inputElement).not.toHaveAttribute('aria-labelledby');
+ });
+
+ describe('With a label, and no custom placeholder', () => {
+ test('it renders input with a label, and with a default placeholder', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByLabelText('Select Label')).toBeInTheDocument();
+ expect(screen.getByText('Select...')).toBeInTheDocument();
+ });
+
+ test('assigns the "aria-labelledby" attribute and renders a label with correct id, when a label is provided', () => {
+ render( );
+ const inputElement = screen.getByLabelText('test label');
+ expect(inputElement).toHaveAttribute('aria-labelledby', 'testInputLabel');
+ expect(document.getElementById('testInputLabel')).toBeInTheDocument();
+ });
+ });
+
+ describe('Single select, pre-selected', () => {
+ test('it renders with value pre-selected', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
+ });
+ });
+
+ describe('Multi select, no selection', () => {
+ test('it renders input with a label, and with a default placeholder', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByLabelText('Select Label')).toBeInTheDocument();
+ expect(screen.getByText('Select...')).toBeInTheDocument();
+ });
+ });
+
+ describe('Multi select, with multiple items selected', () => {
+ test('it renders input with a label, and with two items selected', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByLabelText('Select Label')).toBeInTheDocument();
+ expect(screen.queryByText('Select...')).toBeNull();
+ expect(screen.getByText('Chocolate')).toBeInTheDocument();
+ expect(screen.getByText('Vanilla')).toBeInTheDocument();
+ });
+ });
+
+ describe('Is Required', () => {
+ test('it renders an asterisk in the label', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText('Select Label')).toBeInTheDocument();
+ expect(screen.getByText('*')).toBeInTheDocument();
+ });
+ });
+
+ describe('Is Disabled', () => {
+ test('it disables the input', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByRole('textbox')).toBeDisabled();
+ });
+ });
+
+ describe('Is Invalid, with a helpful message', () => {
+ test('it renders the helpful message', () => {
+ const mockedHandleChange = jest.fn();
+
+ render(
+ ,
+ );
+
+ expect(screen.getByText('Helpful message')).toBeInTheDocument();
+ });
+ });
+ });
+});
diff --git a/src/components/TextInput/TextInput.jsx b/src/components/TextInput/TextInput.jsx
index 3ea508685..ff22f6f39 100644
--- a/src/components/TextInput/TextInput.jsx
+++ b/src/components/TextInput/TextInput.jsx
@@ -21,12 +21,14 @@ const TextInput = ({
autoComplete,
autoFocus,
className,
+ hideLabel,
id,
inputMask,
isDisabled,
error,
isRequired,
label,
+ maxLength,
name,
onBlur,
onChange,
@@ -70,13 +72,15 @@ const TextInput = ({
const inputProps = {
'aria-required': isRequired,
- 'aria-label': label || name,
'aria-invalid': !!error,
+ 'aria-label': label,
+ 'aria-labelledby': label && !hideLabel ? `${id}Label` : null,
autoComplete: getAutoCompleteValue(),
autoFocus,
className: inputClasses,
disabled: isDisabled,
id,
+ maxLength,
name,
onBlur: handleBlur,
onChange: handleChange,
@@ -95,7 +99,7 @@ const TextInput = ({
return (
- {label &&
}
+ {label && !hideLabel &&
}
{!inputMask ? (
) : (
@@ -131,6 +135,10 @@ TextInput.propTypes = {
PropTypes.string,
PropTypes.node,
]),
+ /**
+ * Visually hide the label
+ */
+ hideLabel: PropTypes.bool,
/**
* The input's id attribute. Used to programmatically tie the input with its label.
*/
@@ -153,9 +161,14 @@ TextInput.propTypes = {
*/
isRequired: PropTypes.bool,
/**
- * Value for HTML
tag
+ * Custom content to be displayed above the input. If the label is hidden, will be used to set aria-label attribute.
+ */
+ label: PropTypes.string.isRequired,
+ /**
+ * The input's 'maxlength' attribute.
+ * NOTE: initializing the input with a value longer than the desired maxlength will not trim this value.
*/
- label: PropTypes.string,
+ maxLength: PropTypes.string,
/**
* The input's 'name' attribute
*/
@@ -190,11 +203,12 @@ TextInput.defaultProps = {
autoComplete: false,
autoFocus: false,
className: '',
+ hideLabel: false,
inputMask: undefined,
isDisabled: false,
error: false,
isRequired: false,
- label: undefined,
+ maxLength: undefined,
name: '',
onBlur: undefined,
onFocus: undefined,
diff --git a/src/components/TextInput/TextInput.stories.jsx b/src/components/TextInput/TextInput.stories.jsx
index ed2836ad5..6ac10787e 100644
--- a/src/components/TextInput/TextInput.stories.jsx
+++ b/src/components/TextInput/TextInput.stories.jsx
@@ -5,14 +5,14 @@ import { withA11y } from '@storybook/addon-a11y';
import TextInput from './TextInput';
export default {
- title: 'Forms/Inputs/TextInput',
+ title: 'Components/Form Inputs/TextInput',
component: TextInput,
decorators: [withA11y],
};
export const All = () => {
const store = new Store({
- basicInputValue: 'Hello World!',
+ hiddenLabel: '',
placeholderInputValue: '',
withLabelInputValue: 'With a label',
requiredInputValue: '',
@@ -22,6 +22,7 @@ export const All = () => {
withLabelErrorInputValue: 'invalid value',
withValidationMessage: '',
invalidWithLabel: '',
+ withMaxLength: 'asdhasdhasdh',
});
const handleChange = (event, key) => {
@@ -33,46 +34,11 @@ export const All = () => {
{state => (
-
- handleChange(event, 'basicInputValue')}
- />
-
-
- null}
- />
-
-
- null}
- />
-
-
- null}
- />
-
handleChange(event, 'withLabelInputValue')}
/>
@@ -104,6 +70,44 @@ export const All = () => {
onChange={event => handleChange(event, 'autoFocusedInputValue')}
/>
+
+ handleChange(event, 'hiddenLabel')}
+ placeholder="My label is visually hidden"
+ />
+
+
+ null}
+ />
+
+
+ null}
+ />
+
+
+ null}
+ />
+
{
{
onChange={event => handleChange(event, 'invalidWithLabel')}
/>
+
+ handleChange(event, 'withMaxLength')}
+ />
+
)}
diff --git a/src/components/TextInput/TextInput.test.jsx b/src/components/TextInput/TextInput.test.jsx
index 22852848b..75febe5a2 100644
--- a/src/components/TextInput/TextInput.test.jsx
+++ b/src/components/TextInput/TextInput.test.jsx
@@ -13,7 +13,7 @@ describe('TextInput', () => {
*/
test('Throws error if required prop "value" is not supplied to component', () => {
console.error = jest.fn(); // eslint-disable-line no-console
- render( null} id="myId" />);
+ render( null} id="myId" />);
expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
.toContain('Failed prop type: The prop `value`');
@@ -21,7 +21,7 @@ describe('TextInput', () => {
test('Throws error if required prop "onChange" is not supplied to component', () => {
console.error = jest.fn(); // eslint-disable-line no-console
- render( );
+ render( );
expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
.toContain('Failed prop type: The prop `onChange`');
@@ -29,15 +29,15 @@ describe('TextInput', () => {
test('Throws error if required prop "id" is not supplied to component', () => {
console.error = jest.fn(); // eslint-disable-line no-console
- render( null} />);
- expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
+ render( null} />);
+ expect(console.error).toHaveBeenCalledTimes(2); // eslint-disable-line no-console
expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
.toContain('Failed prop type: The prop `id`');
});
test('Throws an error if "type" prop is anything other than allowed values', () => {
console.error = jest.fn(); // eslint-disable-line no-console
- render( null} value="hello" type="notOnTheList" />);
+ render( null} value="hello" type="notOnTheList" />);
expect(console.error).toHaveBeenCalledTimes(1); // eslint-disable-line no-console
expect(console.error.mock.calls[0][0]) // eslint-disable-line no-console
.toContain('Failed prop type: Invalid prop `type`');
@@ -97,7 +97,7 @@ describe('TextInput', () => {
test('Input fires onFocus callback', () => {
const mockedHandleFocus = jest.fn();
- render( null} onFocus={mockedHandleFocus} />);
+ render( null} onFocus={mockedHandleFocus} />);
const inputElement = screen.getByDisplayValue('hello');
fireEvent.focus(inputElement);
expect(mockedHandleFocus).toBeCalledTimes(1);
@@ -105,7 +105,7 @@ describe('TextInput', () => {
test('Input fires onBlur callback', () => {
const mockedHandleBlur = jest.fn();
- render( null} onBlur={mockedHandleBlur} />);
+ render( null} onBlur={mockedHandleBlur} />);
const inputElement = screen.getByDisplayValue('hello');
fireEvent.focus(inputElement);
fireEvent.blur(inputElement);
@@ -113,31 +113,31 @@ describe('TextInput', () => {
});
test('Input autofocuses if "autoFocus" prop is set to true', () => {
- render( null} autoFocus />);
+ render( null} autoFocus />);
const inputElement = screen.getByDisplayValue('hello');
expect(document.activeElement).toEqual(inputElement);
});
test('Input correctly assigns autocomplete value of "on" when bool true is provided', () => {
- render( null} autoComplete />);
+ render( null} autoComplete />);
const inputElement = screen.getByDisplayValue('hello');
expect(inputElement).toHaveAttribute('autocomplete', 'on');
});
test('Input correctly assigns autocomplete value of "off" when bool false is provided', () => {
- render( null} autoComplete={false} />);
+ render( null} autoComplete={false} />);
const inputElement = screen.getByDisplayValue('hello');
expect(inputElement).toHaveAttribute('autocomplete', 'off');
});
test('Input correctly assigns autocomplete value of "off" when incorrect type is provided', () => {
- render( null} autoComplete={['a', 'random', 'array']} />);
+ render( null} autoComplete={['a', 'random', 'array']} />);
const inputElement = screen.getByDisplayValue('hello');
expect(inputElement).toHaveAttribute('autocomplete', 'off');
});
test('Input correctly assigns the "aria-required" attribute when "isRequired" prop is true', () => {
- render( null} isRequired />);
+ render( null} isRequired />);
const inputElement = screen.getByDisplayValue('hello');
expect(inputElement).toHaveAttribute('aria-required', 'true');
});
@@ -152,9 +152,47 @@ describe('TextInput', () => {
});
test('Input correctly displays error message if provided', () => {
- render( null} id="myId" error="You silly goose" />);
+ render( null} id="myId" error="You silly goose" />);
const validationMessageElement = screen.getByText('You silly goose');
expect(validationMessageElement).toBeInTheDocument();
expect(validationMessageElement).toHaveTextContent('You silly goose');
});
+
+ test('Input correctly passes maxlength property if prop is passed', async () => {
+ render(
+ null}
+ />,
+ );
+
+ const inputElement = screen.getByLabelText('first name');
+ expect(inputElement).toBeInTheDocument();
+ expect(inputElement).toHaveAttribute('maxlength');
+ expect(inputElement.getAttribute('maxlength')).toBe('3');
+ expect(inputElement.value).toBe('');
+ });
+
+ test('assigns the "aria-labelledby" attribute and renders a label with correct id, when a label is provided', () => {
+ render( null} />);
+ const inputElement = screen.getByDisplayValue('hello');
+ expect(inputElement).toHaveAttribute('aria-labelledby', 'testInputLabel');
+ expect(document.getElementById('testInputLabel')).toBeInTheDocument();
+ });
+
+ test('does not assign "aria-labelledby" attribute when a label is hidden', () => {
+ render( null}
+ />);
+ const inputElement = screen.getByLabelText('hidden label');
+ expect(inputElement).not.toHaveAttribute('aria-labelledby');
+ });
});
diff --git a/src/components/colors/Colors.stories.jsx b/src/components/colors/Colors.stories.jsx
index f372b3170..ded948710 100644
--- a/src/components/colors/Colors.stories.jsx
+++ b/src/components/colors/Colors.stories.jsx
@@ -6,7 +6,7 @@ import './colors.scss';
const [baseColors, brandColors] = PALMETTO_COLOR_VALUES;
export default {
- title: 'Colors',
+ title: 'Tokens/Colors',
decorators: [withA11y],
};
diff --git a/yarn.lock b/yarn.lock
index 63981bf83..97e3d3270 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1547,6 +1547,16 @@
resolve "^1.8.1"
semver "^5.5.1"
+"@babel/plugin-transform-runtime@^7.10.4":
+ version "7.10.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.10.4.tgz#594fb53453ea1b6f0779cceb48ce0718a447feb7"
+ integrity sha512-8ULlGv8p+Vuxu+kz2Y1dk6MYS2b/Dki+NO6/0ZlfSj5tMalfDL7jI/o/2a+rrWLqSXvnadEqc2WguB4gdQIxZw==
+ dependencies:
+ "@babel/helper-module-imports" "^7.10.4"
+ "@babel/helper-plugin-utils" "^7.10.4"
+ resolve "^1.8.1"
+ semver "^5.5.1"
+
"@babel/plugin-transform-shorthand-properties@^7.10.1", "@babel/plugin-transform-shorthand-properties@^7.8.3":
version "7.10.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.1.tgz#e8b54f238a1ccbae482c4dce946180ae7b3143f3"
@@ -3373,6 +3383,17 @@
"@svgr/plugin-svgo" "^5.4.0"
loader-utils "^2.0.0"
+"@testing-library/dom@>=5":
+ version "7.20.1"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.20.1.tgz#bd3fd92eed4a29ea9b2731a96f430566da9b8980"
+ integrity sha512-kvAMxpPPPFHIC5vkuSXpx32Q/c1qHoVVuZEvB/pY4u1lL7nwWdM4P7vjzWlDfAEVNmSK2gHA4YrW5+hLxmFuyA==
+ dependencies:
+ "@babel/runtime" "^7.10.3"
+ "@types/aria-query" "^4.2.0"
+ aria-query "^4.2.2"
+ dom-accessibility-api "^0.4.5"
+ pretty-format "^25.5.0"
+
"@testing-library/dom@^7.17.1":
version "7.19.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.19.0.tgz#35d33bdac587000c4bd19cc73f3c58a05a0f29af"
@@ -3412,11 +3433,21 @@
resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-7.2.1.tgz#2ad4e844175a3738cb9e7064be5ea070b8863a1c"
integrity sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA==
+"@tootallnate/once@1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
+ integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
+
"@types/anymatch@*":
version "1.3.1"
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
+"@types/aria-query@^4.2.0":
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
+ integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
+
"@types/babel-types@*", "@types/babel-types@^7.0.0":
version "7.0.8"
resolved "https://registry.yarnpkg.com/@types/babel-types/-/babel-types-7.0.8.tgz#267f405bda841ffae731e7714166b88254cc3e19"
@@ -4000,6 +4031,18 @@ address@1.1.2, address@^1.0.1:
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA==
+agent-base@5:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c"
+ integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==
+
+agent-base@6:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4"
+ integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg==
+ dependencies:
+ debug "4"
+
aggregate-error@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0"
@@ -4187,6 +4230,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"
+argv@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
+ integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
+
aria-query@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b"
@@ -5735,6 +5783,17 @@ code-point-at@^1.0.0:
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+codecov@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/codecov/-/codecov-3.7.0.tgz#4a09939cde24447a43f36d068e8b4e0188dc3f27"
+ integrity sha512-uIixKofG099NbUDyzRk1HdGtaG8O+PBUAg3wfmjwXw2+ek+PZp+puRvbTohqrVfuudaezivJHFgTtSC3M8MXww==
+ dependencies:
+ argv "0.0.2"
+ ignore-walk "3.0.3"
+ js-yaml "3.13.1"
+ teeny-request "6.0.1"
+ urlgrey "0.4.4"
+
collapse-white-space@^1.0.0, collapse-white-space@^1.0.2:
version "1.0.6"
resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287"
@@ -6373,6 +6432,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9:
dependencies:
ms "2.0.0"
+debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
+ integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
+ dependencies:
+ ms "^2.1.1"
+
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@@ -6387,13 +6453,6 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.2.5:
dependencies:
ms "^2.1.1"
-debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
- integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
- dependencies:
- ms "^2.1.1"
-
decamelize-keys@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9"
@@ -8560,6 +8619,15 @@ http-parser-js@>=0.5.1:
resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.2.tgz#da2e31d237b393aae72ace43882dd7e270a8ff77"
integrity sha512-opCO9ASqg5Wy2FNo7A0sxy71yGbbkJJXLdgMK04Tcypw9jr2MgWbyubb0+WdmDmGnFflO7fRbqbaihh/ENDlRQ==
+http-proxy-agent@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
+ integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
+ dependencies:
+ "@tootallnate/once" "1"
+ agent-base "6"
+ debug "4"
+
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@@ -8574,6 +8642,14 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
+https-proxy-agent@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz#702b71fb5520a132a66de1f67541d9e62154d82b"
+ integrity sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==
+ dependencies:
+ agent-base "5"
+ debug "4"
+
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@@ -8615,6 +8691,13 @@ iferr@^0.1.5:
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
+ignore-walk@3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37"
+ integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==
+ dependencies:
+ minimatch "^3.0.4"
+
ignore@^3.3.5:
version "3.3.10"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
@@ -10152,6 +10235,14 @@ js-tokens@^3.0.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
+js-yaml@3.13.1:
+ version "3.13.1"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
+ integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
js-yaml@^3.13.1:
version "3.14.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
@@ -11217,6 +11308,11 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+mutationobserver-shim@^0.3.7:
+ version "0.3.7"
+ resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz#8bf633b0c0b0291a1107255ed32c13088a8c5bf3"
+ integrity sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==
+
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
@@ -11289,7 +11385,7 @@ node-dir@^0.1.10, node-dir@^0.1.17:
dependencies:
minimatch "^3.0.2"
-node-fetch@^2.6.0:
+node-fetch@^2.2.0, node-fetch@^2.6.0:
version "2.6.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
@@ -13229,6 +13325,13 @@ react-redux@^7.0.2:
prop-types "^15.7.2"
react-is "^16.9.0"
+react-select-event@^4.1.4:
+ version "4.1.4"
+ resolved "https://registry.yarnpkg.com/react-select-event/-/react-select-event-4.1.4.tgz#e0aedcf54d1152ce7742b06d81c4cc27d65e1e32"
+ integrity sha512-GDoyzB/oTEOy/DC2OhSAlcxcys4ULn3W4Rp/H0EypsPFhVe82gO7RUBRZHN3CarqeUdhhLVU0ko2k0eZyhD51A==
+ dependencies:
+ "@testing-library/dom" ">=5"
+
react-select@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-3.1.0.tgz#ab098720b2e9fe275047c993f0d0caf5ded17c27"
@@ -14651,6 +14754,13 @@ stream-each@^1.1.0:
end-of-stream "^1.1.0"
stream-shift "^1.0.0"
+stream-events@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5"
+ integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==
+ dependencies:
+ stubs "^3.0.0"
+
stream-http@^2.7.2:
version "2.8.3"
resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc"
@@ -14894,6 +15004,11 @@ strip-json-comments@^3.1.0:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180"
integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w==
+stubs@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b"
+ integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls=
+
style-inject@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
@@ -15122,6 +15237,17 @@ tar@^2.0.0:
fstream "^1.0.12"
inherits "2"
+teeny-request@6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-6.0.1.tgz#9b1f512cef152945827ba7e34f62523a4ce2c5b0"
+ integrity sha512-TAK0c9a00ELOqLrZ49cFxvPVogMUFaWY8dUsQc/0CuQPGF+BOxOQzXfE413BAk2kLomwNplvdtMpeaeGWmoc2g==
+ dependencies:
+ http-proxy-agent "^4.0.0"
+ https-proxy-agent "^4.0.0"
+ node-fetch "^2.2.0"
+ stream-events "^1.0.5"
+ uuid "^3.3.2"
+
telejson@^3.2.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/telejson/-/telejson-3.3.0.tgz#6d814f3c0d254d5c4770085aad063e266b56ad03"
@@ -15797,6 +15923,11 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
+urlgrey@0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f"
+ integrity sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=
+
use-callback-ref@^1.2.1:
version "1.2.4"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c"