Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {render} from './helpers/test-utils'
import document from './helpers/document'

// eslint-disable-next-line max-lines-per-function
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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,
}