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

Enhance toHaveStyle to accept JS as css #196

Merged
merged 2 commits into from Jan 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 17 additions & 5 deletions README.md
Expand Up @@ -46,7 +46,6 @@ clear to read and to maintain.
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Usage](#usage)
- [Custom matchers](#custom-matchers)
Expand Down Expand Up @@ -642,7 +641,7 @@ expect(getByTestId('login-form')).toHaveFormValues({
### `toHaveStyle`

```typescript
toHaveStyle(css: string)
toHaveStyle(css: string | object)
```

This allows you to check if a certain element has some specific css properties
Expand All @@ -652,7 +651,10 @@ expected properties applied, not just some of them.
#### Examples

```html
<button data-testid="delete-button" style="display: none; color: red">
<button
data-testid="delete-button"
style="display: none; background-color: red"
>
Delete item
</button>
```
Expand All @@ -661,14 +663,23 @@ expected properties applied, not just some of them.
const button = getByTestId('delete-button')

expect(button).toHaveStyle('display: none')
expect(button).toHaveStyle({display: 'none'})
expect(button).toHaveStyle(`
color: red;
background-color: red;
display: none;
`)
expect(button).toHaveStyle({
backgroundColor: 'red',
display: 'none',
})
expect(button).not.toHaveStyle(`
color: blue;
background-color: blue;
display: none;
`)
expect(button).not.toHaveStyle({
backgroundColor: 'blue',
display: 'none',
})
```

This also works with rules that are applied to the element via a class name for
Expand Down Expand Up @@ -928,6 +939,7 @@ Thanks goes to these people ([emoji key][emojis]):

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->

<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification.
Expand Down
24 changes: 24 additions & 0 deletions src/__tests__/to-have-style.js
@@ -1,6 +1,7 @@
import {render} from './helpers/test-utils'
import document from './helpers/document'

// eslint-disable-next-line max-lines-per-function
gnapse marked this conversation as resolved.
Show resolved Hide resolved
describe('.toHaveStyle', () => {
test('handles positive test cases', () => {
const {container} = render(`
Expand Down Expand Up @@ -144,4 +145,27 @@ describe('.toHaveStyle', () => {
'whatever: anything',
)
})

test('handles styles as object', () => {
const {container} = render(`
<div class="label" style="background-color: blue; height: 100%">
Hello World
</div>
`)

expect(container.querySelector('.label')).toHaveStyle({
backgroundColor: 'blue',
})
expect(container.querySelector('.label')).toHaveStyle({
backgroundColor: 'blue',
height: '100%',
})
expect(container.querySelector('.label')).not.toHaveStyle({
backgroundColor: 'red',
height: '100%',
})
expect(container.querySelector('.label')).not.toHaveStyle({
whatever: 'anything',
})
})
})
41 changes: 40 additions & 1 deletion src/__tests__/utils.js
@@ -1,4 +1,9 @@
import {deprecate, checkHtmlElement, HtmlElementTypeError} from '../utils'
import {
deprecate,
checkHtmlElement,
HtmlElementTypeError,
parseJStoCSS,
} from '../utils'
import document from './helpers/document'

test('deprecate', () => {
Expand Down Expand Up @@ -77,3 +82,37 @@ describe('checkHtmlElement', () => {
}).toThrow(HtmlElementTypeError)
})
})

describe('parseJStoCSS', () => {
describe('when all the styles are valid', () => {
it('returns the JS parsed as CSS text', () => {
expect(
parseJStoCSS(document, {
backgroundColor: 'blue',
height: '100%',
}),
).toBe('background-color: blue; height: 100%;')
})
})

describe('when some style is invalid', () => {
it('returns the JS parsed as CSS text without the invalid style', () => {
expect(
parseJStoCSS(document, {
backgroundColor: 'blue',
whatever: 'anything',
}),
).toBe('background-color: blue;')
})
})

describe('when all the styles are invalid', () => {
it('returns an empty string', () => {
expect(
parseJStoCSS(document, {
whatever: 'anything',
}),
).toBe('')
})
})
})
18 changes: 13 additions & 5 deletions src/to-have-style.js
@@ -1,7 +1,7 @@
import {matcherHint} from 'jest-matcher-utils'
import jestDiff from 'jest-diff'
import chalk from 'chalk'
import {checkHtmlElement, parseCSS} from './utils'
import {checkHtmlElement, parseCSS, parseJStoCSS} from './utils'

function getStyleDeclaration(document, css) {
const styles = {}
Expand All @@ -17,9 +17,12 @@ function getStyleDeclaration(document, css) {
}

function isSubset(styles, computedStyle) {
return Object.entries(styles).every(
([prop, value]) =>
computedStyle.getPropertyValue(prop.toLowerCase()) === value,
return (
!!Object.keys(styles).length &&
Copy link
Member

Choose a reason for hiding this comment

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

I guess you added this because we now need to return false if the object is empty, right?

Also, why not the more explicit alternative:

Suggested change
!!Object.keys(styles).length &&
Object.keys(styles).length > 0 &&

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess you added this because we now need to return false if the object is empty, right?

Yes.

Object.entries(styles).every(
([prop, value]) =>
computedStyle.getPropertyValue(prop.toLowerCase()) === value,
)
)
}

Expand Down Expand Up @@ -48,9 +51,14 @@ function expectedDiff(expected, computedStyles) {
return diffOutput.replace(`${chalk.red('+ Received')}\n`, '')
}

function getCSStoParse(document, css) {
return typeof css === 'object' ? parseJStoCSS(document, css) : css
}

export function toHaveStyle(htmlElement, css) {
checkHtmlElement(htmlElement, toHaveStyle, this)
const parsedCSS = parseCSS(css, toHaveStyle, this)
const cssToParse = getCSStoParse(htmlElement.ownerDocument, css)
const parsedCSS = parseCSS(cssToParse, toHaveStyle, this)
const {getComputedStyle} = htmlElement.ownerDocument.defaultView

const expected = getStyleDeclaration(htmlElement.ownerDocument, parsedCSS)
Expand Down
7 changes: 7 additions & 0 deletions src/utils.js
Expand Up @@ -192,6 +192,12 @@ function compareArraysAsSet(a, b) {
return undefined
}

function parseJStoCSS(document, css) {
const sandboxElement = document.createElement('div')
Object.assign(sandboxElement.style, css)
return sandboxElement.style.cssText
}
Comment on lines +195 to +199
Copy link
Member

Choose a reason for hiding this comment

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

This is brilliant 👏


export {
HtmlElementTypeError,
checkHtmlElement,
Expand All @@ -203,4 +209,5 @@ export {
getTag,
getSingleElementValue,
compareArraysAsSet,
parseJStoCSS,
}