mirror of https://github.com/openedx/paragon.git
fix(docs,asinput): semantic-release & jest docs; asInput a11y fix
docs(readme): add semantic-release documentation docs(readme): add semantic-release notes to documentation docs(readme): add cli gif link docs(readme): fix some formatting docs(readme): add more background information fix(asinput): Accessibility fixes for asInput (#97) * fix(asinput): Add screen-reader text for danger icon * fix(asinput): Load form-control-feedback on page load w/ aria-live fix(readme): Document how to run jest tests in Chrome DevTools (#96) Also adds an npm run test-debug script.
This commit is contained in:
parent
213b4faed1
commit
7bc95c139f
|
|
@ -30,6 +30,13 @@ exports[`Storyshots CheckBox basic usage 1`] = `
|
|||
>
|
||||
check me out!
|
||||
</label>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput1"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -51,6 +58,13 @@ exports[`Storyshots CheckBox call a function 1`] = `
|
|||
>
|
||||
check out the console
|
||||
</label>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput1"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -72,6 +86,13 @@ exports[`Storyshots CheckBox default checked 1`] = `
|
|||
>
|
||||
(un)check me out
|
||||
</label>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput1"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -93,6 +114,13 @@ exports[`Storyshots CheckBox disabled 1`] = `
|
|||
>
|
||||
you cannot check me out
|
||||
</label>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput1"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -204,7 +232,7 @@ exports[`Storyshots InputSelect basic usage 1`] = `
|
|||
Fruits
|
||||
</label>
|
||||
<select
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput2"
|
||||
className="form-control"
|
||||
id="asInput2"
|
||||
name="fruits"
|
||||
|
|
@ -234,6 +262,13 @@ exports[`Storyshots InputSelect basic usage 1`] = `
|
|||
banana
|
||||
</option>
|
||||
</select>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput2"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -248,7 +283,7 @@ exports[`Storyshots InputSelect separate labels and values 1`] = `
|
|||
New England States
|
||||
</label>
|
||||
<select
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput2"
|
||||
className="form-control"
|
||||
id="asInput2"
|
||||
name="new-england-states"
|
||||
|
|
@ -288,6 +323,13 @@ exports[`Storyshots InputSelect separate labels and values 1`] = `
|
|||
Vermont
|
||||
</option>
|
||||
</select>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput2"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -302,7 +344,7 @@ exports[`Storyshots InputSelect separate option groups 1`] = `
|
|||
Northeast States
|
||||
</label>
|
||||
<select
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput2"
|
||||
className="form-control"
|
||||
id="asInput2"
|
||||
name="northeast-states"
|
||||
|
|
@ -390,6 +432,13 @@ exports[`Storyshots InputSelect separate option groups 1`] = `
|
|||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput2"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -404,7 +453,7 @@ exports[`Storyshots InputSelect with validation 1`] = `
|
|||
Favorite Color
|
||||
</label>
|
||||
<select
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput2"
|
||||
className="form-control"
|
||||
id="asInput2"
|
||||
name="color"
|
||||
|
|
@ -449,6 +498,13 @@ exports[`Storyshots InputSelect with validation 1`] = `
|
|||
purple
|
||||
</option>
|
||||
</select>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput2"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -489,7 +545,7 @@ exports[`Storyshots InputText focus test 1`] = `
|
|||
Data Input
|
||||
</label>
|
||||
<input
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-data"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -503,6 +559,13 @@ exports[`Storyshots InputText focus test 1`] = `
|
|||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-data"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -518,7 +581,7 @@ exports[`Storyshots InputText minimal usage 1`] = `
|
|||
First Name
|
||||
</label>
|
||||
<input
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput3"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -532,6 +595,13 @@ exports[`Storyshots InputText minimal usage 1`] = `
|
|||
type="text"
|
||||
value="Foo Bar"
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput3"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -546,7 +616,7 @@ exports[`Storyshots InputText validation 1`] = `
|
|||
Username
|
||||
</label>
|
||||
<input
|
||||
aria-describedby="undefined description-username"
|
||||
aria-describedby="error-username description-username"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -560,6 +630,13 @@ exports[`Storyshots InputText validation 1`] = `
|
|||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-username"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
<small
|
||||
className="form-text"
|
||||
id="description-username"
|
||||
|
|
@ -580,7 +657,7 @@ exports[`Storyshots InputText validation with danger theme 1`] = `
|
|||
Username
|
||||
</label>
|
||||
<input
|
||||
aria-describedby="undefined description-asInput3"
|
||||
aria-describedby="error-asInput3 description-asInput3"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -598,6 +675,13 @@ exports[`Storyshots InputText validation with danger theme 1`] = `
|
|||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback invalid-feedback"
|
||||
id="error-asInput3"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
<small
|
||||
className="form-text"
|
||||
id="description-asInput3"
|
||||
|
|
@ -1089,7 +1173,7 @@ exports[`Storyshots Modal modal with element body 1`] = `
|
|||
E-Mail Address
|
||||
</label>
|
||||
<input
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput3"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -1103,6 +1187,13 @@ exports[`Storyshots Modal modal with element body 1`] = `
|
|||
type="text"
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput3"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btn"
|
||||
|
|
@ -2076,7 +2167,7 @@ exports[`Storyshots Textarea minimal usage 1`] = `
|
|||
First Name
|
||||
</label>
|
||||
<textarea
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput4"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -2093,6 +2184,13 @@ exports[`Storyshots Textarea minimal usage 1`] = `
|
|||
}
|
||||
value="Foo Bar"
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput4"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -2107,7 +2205,7 @@ exports[`Storyshots Textarea scrollable 1`] = `
|
|||
Information
|
||||
</label>
|
||||
<textarea
|
||||
aria-describedby={undefined}
|
||||
aria-describedby="error-asInput4"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -2124,6 +2222,13 @@ exports[`Storyshots Textarea scrollable 1`] = `
|
|||
}
|
||||
value="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput4"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
|
@ -2138,7 +2243,7 @@ exports[`Storyshots Textarea validation 1`] = `
|
|||
Username
|
||||
</label>
|
||||
<textarea
|
||||
aria-describedby="undefined description-asInput4"
|
||||
aria-describedby="error-asInput4 description-asInput4"
|
||||
aria-invalid={false}
|
||||
className="form-control"
|
||||
disabled={false}
|
||||
|
|
@ -2155,6 +2260,13 @@ exports[`Storyshots Textarea validation 1`] = `
|
|||
}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
aria-live="polite"
|
||||
className="form-control-feedback"
|
||||
id="error-asInput4"
|
||||
>
|
||||
<span />
|
||||
</div>
|
||||
<small
|
||||
className="form-text"
|
||||
id="description-asInput4"
|
||||
|
|
|
|||
84
README.md
84
README.md
|
|
@ -73,6 +73,18 @@ npm run test
|
|||
|
||||
To add unit tests for a component, create a file in your component's directory named `<ComponentName>.test.js`. Jest will automatically pick up this file and run the tests as part of the suite. Take a look at [Dropdown.test.jsx](https://github.com/edx/paragon/blob/master/src/Dropdown/Dropdown.test.jsx) or [CheckBox.test.jsx](https://github.com/edx/paragon/blob/master/src/CheckBox/CheckBox.test.jsx) for examples of good component unit tests.
|
||||
|
||||
#### Run Unit Tests in Chrome DevTools Inspector
|
||||
|
||||
To run the unit tests in the Chrome DevTools inspector, run:
|
||||
|
||||
```
|
||||
npm run debug-test
|
||||
```
|
||||
|
||||
Then, open `chrome://inspect` in your Chrome browser and select the "node_modules/.bin/jest" target to open the Chrome DevTools. You can set breakpoints in Chrome DevTools or insert a `debugger;` statement into the code to pause execution at that point.
|
||||
|
||||

|
||||
|
||||
### Snapshot Testing
|
||||
|
||||
Jest has built-in [snapshot testing](http://facebook.github.io/jest/docs/en/snapshot-testing.html#snapshot-testing-with-jest) functionality which serves as a good means of smoketesting components to ensure they render in a predictable way. Paragon's Jest snapshots are automatically generated from components' Storybook stories using the [Storyshots addon](https://github.com/storybooks/storybook/blob/4b6a93acfbaf044d85dd8ee7a7671239ea1ba01d/addons/storyshots/README.md) -- pretty cool, huh?
|
||||
|
|
@ -88,3 +100,75 @@ If the snapshot tests fail, it's generally pretty easy to tell whether it's happ
|
|||
### Coverage
|
||||
|
||||
Paragon measures code coverage using Jest's built-in `--coverage` flag (which I believe uses istanbul under the hood) and report it via [Coveralls](https://coveralls.io/github/edx/paragon). Shoot for 100% test coverage on your PRs, but use your best judgment if you're really struggling to cover those last few lines. At the very least, don't *reduce* total coverage. Coveralls will fail your build if your PR reduces coverage.
|
||||
|
||||
## Semantic Release
|
||||
|
||||
Paragon uses the [`semantic-release` package](https://github.com/semantic-release/semantic-release) to automate its release process (creating Git tags, creating GitHub releases, and publishing to NPM).
|
||||
|
||||
### Commit Messages
|
||||
|
||||
[`semantic-release` analyzes commit messages to determine whether to create a `major`, `minor`, or `patch` release](https://github.com/semantic-release/semantic-release#default-commit-message-format) (or to skip a release).
|
||||
Paragon currently uses [the default commit analyzer release rules](https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js#L8-L11) which means that there are **4** commit types that will trigger a release:
|
||||
1. `feat` (`minor` release)
|
||||
2. `fix` (`patch` release)
|
||||
3. `perf` (`patch` release)
|
||||
4. `breaking` (`major` release)
|
||||
|
||||
#### `Angular` Commit Message Convention
|
||||
|
||||
Paragon currently uses [the `Angular` commit message convention](https://gist.github.com/stephenparish/9941e89d80e2bc58a153). As documented in the previously linked gist, a commit that follows the `Angular` commit message convention has four parts:
|
||||
1. A `type` - is this commit a `feat`, `fix`, `chore`, `docs`, etc.? There is a set of `type` values to choose from, but again, **only the `feat`, `fix`, `perf`, and `breaking`** `type` values will trigger a release.
|
||||
2. A `scope` - what is this commit impacting? Did you fix a bug in the `Hyperlink` component? Did you add a new feature to the `RadioButtonGroup` component? Currently, the `scope` must be lower-case and `-` separated though switching this to being `camelCase` is currently being investigated.
|
||||
3. A `subject` - provide a short description of _what_ your change is.
|
||||
* use imperative, present tense language (so `change` not `changed` or `changes`)
|
||||
* don't capitalize the first letter
|
||||
* don't add a period (`.`) at the end
|
||||
* there is a 50 character limit
|
||||
4. A `body` (optional) - add more detail about your change
|
||||
5. A `footer` (optional)
|
||||
* > All breaking changes have to be mentioned in footer with the description of the change, justification and migration notes - [`Angular` commit message specification](https://gist.github.com/stephenparish/9941e89d80e2bc58a153#breaking-changes)
|
||||
|
||||
##### Examples
|
||||
This will lead to a patch version bump
|
||||
> fix(someComponent): fix tab accessibility issue in someComponent
|
||||
|
||||
This will lead to a minor version bump
|
||||
> feat(newComponent): add newComponent`
|
||||
|
||||
This will lead to a patch version bump - note the body
|
||||
>fix(anotherComponent): fix escape key accessibility issue
|
||||
>
|
||||
> Unable to clear anotherComponent's popup using escape key.
|
||||
> By updating the onKeyPress event handler to close popup window on escape key press, accessibility issue was resolved.
|
||||
|
||||
|
||||
#### Using `commitlint` in Paragon
|
||||
|
||||
Paragon uses the [`commitlint`](https://github.com/marionebl/commitlint) package to lint commit messages. Paragon currently has [a `commit message` hook](https://github.com/edx/paragon/blob/master/package.json#L20) that runs `commitlint`'s, well, commit linting, on the given commit message using the configuration specified in [`commitlint.config.js`](https://github.com/edx/paragon/blob/master/commitlint.config.js). If a commit message fails linting, the commit associated with that message does not end up getting committed.
|
||||
|
||||
`commitlint` also comes with [a helpful CLI](https://github.com/marionebl/commitlint/tree/master/@commitlint/cli) that walks one through the entire semantic commit process. This CLI can be triggered by running the `npm run gc` command.
|
||||
|
||||

|
||||
|
||||
As noted above, the only required fields are **`type`, `scope`, and `subject`**. The `body` and `footer` can be skipped through the CLI by typing `:skip`.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
Since all pull requests should be `squashed` / `rebased` down to a single semantically-formatted commit, it is recommended that **all pull requests have a title that matches the commit message**. This way, whether you're merging a pull request, squashing and merging a pull request, or rebasing and merging a pull request, the correct commit message will be analyzed by `semantic-release`.
|
||||
|
||||
### Merging, Building, and Releasing
|
||||
|
||||
> When `semantic-release` is set up it will do that after every successful continuous integration build of your master branch (or any other branch you specify) and publish the new version for you
|
||||
> - [`semantic-release` README](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
Release-related activity only happens
|
||||
1. After a successful Travis build on `master` ([the `semantic-release` NPM command is only executed as part of the `after_success` Travis hook](https://github.com/edx/paragon/blob/master/.travis.yml#L21-L22).)
|
||||
2. If commit(s) exist with a format that would trigger a release
|
||||
|
||||
If a release occurs, a new GitHub release should be found with relevant notes that are parsed from commits associated with that release.
|
||||
|
||||
So for the following release
|
||||

|
||||
the commit message for that release was `fix(asinput): Override state value when props value changes`, and a new `patch` release was created with release notes from the commit message.
|
||||
|
||||
After a GitHub release is created, the package is then published to NPM.
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
|
|
@ -8376,6 +8376,199 @@
|
|||
"throat": "4.1.0"
|
||||
}
|
||||
},
|
||||
"jest-cli": {
|
||||
"version": "21.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-21.2.1.tgz",
|
||||
"integrity": "sha512-T1BzrbFxDIW/LLYQqVfo94y/hhaj1NzVQkZgBumAC+sxbjMROI7VkihOdxNR758iYbQykL2ZOWUBurFgkQrzdg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "3.0.0",
|
||||
"chalk": "2.3.0",
|
||||
"glob": "7.1.2",
|
||||
"graceful-fs": "4.1.11",
|
||||
"is-ci": "1.0.10",
|
||||
"istanbul-api": "1.2.1",
|
||||
"istanbul-lib-coverage": "1.1.1",
|
||||
"istanbul-lib-instrument": "1.9.1",
|
||||
"istanbul-lib-source-maps": "1.2.2",
|
||||
"jest-changed-files": "21.2.0",
|
||||
"jest-config": "21.2.1",
|
||||
"jest-environment-jsdom": "21.2.1",
|
||||
"jest-haste-map": "21.2.0",
|
||||
"jest-message-util": "21.2.1",
|
||||
"jest-regex-util": "21.2.0",
|
||||
"jest-resolve-dependencies": "21.2.0",
|
||||
"jest-runner": "21.2.1",
|
||||
"jest-runtime": "21.2.1",
|
||||
"jest-snapshot": "21.2.1",
|
||||
"jest-util": "21.2.1",
|
||||
"micromatch": "2.3.11",
|
||||
"node-notifier": "5.1.2",
|
||||
"pify": "3.0.0",
|
||||
"slash": "1.0.0",
|
||||
"string-length": "2.0.0",
|
||||
"strip-ansi": "4.0.0",
|
||||
"which": "1.3.0",
|
||||
"worker-farm": "1.5.2",
|
||||
"yargs": "9.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-escapes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz",
|
||||
"integrity": "sha512-O/klc27mWNUigtv0F8NJWbLF00OcegQalkqKURWdosW08YZKi4m6CnSUSvIZG1otNJbTWhN01Hhz389DW7mvDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
|
||||
"integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
|
||||
"dev": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
|
||||
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"string-width": "1.0.2",
|
||||
"strip-ansi": "3.0.1",
|
||||
"wrap-ansi": "2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
|
||||
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
|
||||
"dev": true
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
|
||||
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"code-point-at": "1.1.0",
|
||||
"is-fullwidth-code-point": "1.0.0",
|
||||
"strip-ansi": "3.0.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"load-json-file": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
|
||||
"integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "4.1.11",
|
||||
"parse-json": "2.2.0",
|
||||
"pify": "2.3.0",
|
||||
"strip-bom": "3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
|
||||
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"error-ex": "1.3.1"
|
||||
}
|
||||
},
|
||||
"path-type": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
|
||||
"integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pify": "2.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
"integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"load-json-file": "2.0.0",
|
||||
"normalize-package-data": "2.4.0",
|
||||
"path-type": "2.0.0"
|
||||
}
|
||||
},
|
||||
"read-pkg-up": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
|
||||
"integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"find-up": "2.1.0",
|
||||
"read-pkg": "2.0.0"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "3.0.0"
|
||||
}
|
||||
},
|
||||
"yargs": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz",
|
||||
"integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "4.1.0",
|
||||
"cliui": "3.2.0",
|
||||
"decamelize": "1.2.0",
|
||||
"get-caller-file": "1.0.2",
|
||||
"os-locale": "2.1.0",
|
||||
"read-pkg-up": "2.0.0",
|
||||
"require-directory": "2.1.1",
|
||||
"require-main-filename": "1.0.1",
|
||||
"set-blocking": "2.0.0",
|
||||
"string-width": "2.1.1",
|
||||
"which-module": "2.0.0",
|
||||
"y18n": "3.2.1",
|
||||
"yargs-parser": "7.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-config": {
|
||||
"version": "21.2.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-config/-/jest-config-21.2.1.tgz",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"build": "NODE_ENV=production webpack",
|
||||
"build-storybook": "build-storybook",
|
||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||
"debug-test": "node --inspect-brk node_modules/.bin/jest --runInBand --coverage",
|
||||
"deploy-storybook": "storybook-to-ghpages",
|
||||
"gc": "commit",
|
||||
"lint": "eslint --ext .js --ext .jsx .",
|
||||
|
|
@ -69,6 +70,7 @@
|
|||
"husky": "^0.14.1",
|
||||
"identity-obj-proxy": "^3.0.0",
|
||||
"jest": "^21.0.1",
|
||||
"jest-cli": "^21.2.1",
|
||||
"node-sass": "^4.5.3",
|
||||
"postcss-scss": "^1.0.1",
|
||||
"react-router-dom": "^4.1.1",
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ storiesOf('InputText', module)
|
|||
feedback = {
|
||||
isValid: false,
|
||||
validationMessage: 'Username must be at least 3 characters in length.',
|
||||
dangerIconDescription: 'Error',
|
||||
};
|
||||
}
|
||||
return feedback;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
@import "~bootstrap/scss/_forms";
|
||||
@import "~bootstrap/scss/mixins/_forms";
|
||||
@import "~bootstrap/scss/utilities/_screenreaders.scss";
|
||||
|
||||
.fa-icon-spacing {
|
||||
padding: 0px 5px 0px 0px;
|
||||
|
|
|
|||
|
|
@ -150,6 +150,7 @@ describe('asInput()', () => {
|
|||
};
|
||||
wrapper = mount(<InputTestComponent {...props} />);
|
||||
});
|
||||
|
||||
it('without theme', () => {
|
||||
wrapper.find('input').simulate('blur');
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -157,19 +158,32 @@ describe('asInput()', () => {
|
|||
expect(err.exists()).toEqual(true);
|
||||
expect(err.text()).toEqual(validationResult.validationMessage);
|
||||
});
|
||||
|
||||
it('with danger theme', () => {
|
||||
wrapper.setProps({ themes: ['danger'] });
|
||||
wrapper.find('input').simulate('blur');
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
validationResult.dangerIconDescription = 'Error';
|
||||
|
||||
// error div exists on the page when form is loaded
|
||||
const err = wrapper.find('.form-control-feedback');
|
||||
expect(err.exists()).toEqual(true);
|
||||
expect(err.text()).toEqual(validationResult.validationMessage);
|
||||
expect(err.hasClass('invalid-feedback')).toEqual(true);
|
||||
expect(err.prop('aria-live')).toEqual('polite');
|
||||
expect(err.text()).toEqual('');
|
||||
|
||||
wrapper.find('input').simulate('blur');
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(err.exists()).toEqual(true);
|
||||
expect(err.text()).toEqual(validationResult.dangerIconDescription +
|
||||
validationResult.validationMessage);
|
||||
|
||||
const dangerIcon = wrapper.find('.fa-exclamation-circle');
|
||||
expect(dangerIcon.exists()).toEqual(true);
|
||||
expect(dangerIcon.hasClass('fa')).toEqual(true);
|
||||
|
||||
const dangerIconDescription = wrapper.find('.sr-only');
|
||||
expect(dangerIconDescription.exists()).toEqual(true);
|
||||
expect(dangerIconDescription.text()).toEqual(validationResult.dangerIconDescription);
|
||||
|
||||
const inputElement = wrapper.find('.form-control');
|
||||
expect(inputElement.hasClass('is-invalid')).toEqual(true);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,24 +60,45 @@ const asInput = (WrappedComponent, labelFirst = true) => {
|
|||
const descriptionId = `description-${this.state.id}`;
|
||||
const desc = {};
|
||||
|
||||
if (!this.state.isValid) {
|
||||
const hasDangerTheme = this.hasDangerTheme();
|
||||
const hasDangerTheme = this.hasDangerTheme();
|
||||
|
||||
desc.error = (
|
||||
<div className={classNames(styles['form-control-feedback'], { [styles['invalid-feedback']]: hasDangerTheme })} id={errorId} key="0">
|
||||
{ hasDangerTheme &&
|
||||
<span
|
||||
className={classNames(FontAwesomeStyles.fa, FontAwesomeStyles['fa-exclamation-circle'], styles['fa-icon-spacing'])}
|
||||
aria-hidden
|
||||
/>
|
||||
}
|
||||
<span>
|
||||
{this.state.validationMessage}
|
||||
desc.error = (
|
||||
<div
|
||||
className={classNames(
|
||||
styles['form-control-feedback'],
|
||||
{ [styles['invalid-feedback']]: hasDangerTheme },
|
||||
)}
|
||||
id={errorId}
|
||||
key="0"
|
||||
aria-live="polite"
|
||||
>
|
||||
{ this.state.isValid ? (
|
||||
<span />
|
||||
) : [
|
||||
(hasDangerTheme &&
|
||||
<span key="0">
|
||||
<span
|
||||
className={classNames(
|
||||
FontAwesomeStyles.fa,
|
||||
FontAwesomeStyles['fa-exclamation-circle'],
|
||||
styles['fa-icon-spacing'],
|
||||
)}
|
||||
aria-hidden
|
||||
/>
|
||||
<span
|
||||
className={classNames(styles['sr-only'])}
|
||||
>
|
||||
{this.state.dangerIconDescription}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
desc.describedBy = errorId;
|
||||
}
|
||||
),
|
||||
<span key="1">
|
||||
{this.state.validationMessage}
|
||||
</span>,
|
||||
]}
|
||||
</div>
|
||||
);
|
||||
desc.describedBy = errorId;
|
||||
|
||||
if (this.props.description) {
|
||||
desc.description = (
|
||||
|
|
|
|||
Loading…
Reference in New Issue