Skip to content

Commit 042db4d

Browse files
fix: #49: update-tohavestyle-to-match-cssstyledeclaration (#57)
* #49: Updated toHaveStyle to match CSSStyleDeclaration * #49: added border test
1 parent 9c3ea4e commit 042db4d

File tree

3 files changed

+136
-66
lines changed

3 files changed

+136
-66
lines changed

src/__tests__/to-have-style.js

Lines changed: 80 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,98 @@
11
import {render} from './helpers/test-utils'
22

3-
test('.toHaveStyle', () => {
4-
const {container} = render(`
3+
describe('.toHaveStyle', () => {
4+
test('handles positive test cases', () => {
5+
const {container} = render(`
6+
<div class="label" style="background-color: blue; height: 100%">
7+
Hello World
8+
</div>
9+
`)
10+
11+
const style = document.createElement('style')
12+
style.innerHTML = `
13+
.label {
14+
background-color: black;
15+
color: white;
16+
float: left;
17+
}
18+
`
19+
document.body.appendChild(style)
20+
document.body.appendChild(container)
21+
22+
expect(container.querySelector('.label')).toHaveStyle(`
23+
height: 100%;
24+
color: white;
25+
background-color: blue;
26+
`)
27+
28+
expect(container.querySelector('.label')).toHaveStyle(`
29+
background-color: blue;
30+
color: white;
31+
`)
32+
expect(container.querySelector('.label')).toHaveStyle(
33+
'background-color:blue;color:white',
34+
)
35+
36+
expect(container.querySelector('.label')).not.toHaveStyle(`
37+
color: white;
38+
font-weight: bold;
39+
`)
40+
})
41+
42+
test('handles negative test cases', () => {
43+
const {container} = render(`
544
<div class="label" style="background-color: blue; height: 100%">
645
Hello World
746
</div>
847
`)
948

10-
const style = document.createElement('style')
11-
style.innerHTML = `
49+
const style = document.createElement('style')
50+
style.innerHTML = `
1251
.label {
1352
background-color: black;
1453
color: white;
1554
float: left;
1655
}
1756
`
18-
document.body.appendChild(style)
19-
document.body.appendChild(container)
57+
document.body.appendChild(style)
58+
document.body.appendChild(container)
2059

21-
expect(container.querySelector('.label')).toHaveStyle(`
22-
height: 100%;
23-
color: white;
24-
background-color: blue;
25-
`)
60+
expect(() =>
61+
expect(container.querySelector('.label')).toHaveStyle(
62+
'font-weight: bold',
63+
),
64+
).toThrowError()
65+
expect(() =>
66+
expect(container.querySelector('.label')).not.toHaveStyle('color: white'),
67+
).toThrowError()
2668

27-
expect(container.querySelector('.label')).toHaveStyle(`
28-
background-color: blue;
29-
color: white;
30-
`)
31-
expect(container.querySelector('.label')).toHaveStyle(
32-
'background-color:blue;color:white',
33-
)
69+
// Make sure the test fails if the css syntax is not valid
70+
expect(() =>
71+
expect(container.querySelector('.label')).not.toHaveStyle(
72+
'font-weight bold',
73+
),
74+
).toThrowError()
75+
expect(() =>
76+
expect(container.querySelector('.label')).toHaveStyle('color white'),
77+
).toThrowError()
3478

35-
expect(container.querySelector('.label')).not.toHaveStyle(`
36-
color: white;
37-
font-weight: bold;
38-
`)
79+
document.body.removeChild(style)
80+
document.body.removeChild(container)
81+
})
3982

40-
expect(() =>
41-
expect(container.querySelector('.label')).toHaveStyle('font-weight: bold'),
42-
).toThrowError()
43-
expect(() =>
44-
expect(container.querySelector('.label')).not.toHaveStyle('color: white'),
45-
).toThrowError()
46-
47-
// Make sure the test fails if the css syntax is not valid
48-
expect(() =>
49-
expect(container.querySelector('.label')).not.toHaveStyle(
50-
'font-weight bold',
51-
),
52-
).toThrowError()
53-
expect(() =>
54-
expect(container.querySelector('.label')).toHaveStyle('color white'),
55-
).toThrowError()
56-
57-
document.body.removeChild(style)
58-
document.body.removeChild(container)
83+
test('properly normalizes colors', () => {
84+
const {queryByTestId} = render(`
85+
<span data-testid="color-example" style="background-color: #123456">Hello World</span>
86+
`)
87+
expect(queryByTestId('color-example')).toHaveStyle(
88+
'background-color: #123456',
89+
)
90+
})
91+
92+
test('properly normalizes colors for border', () => {
93+
const {queryByTestId} = render(`
94+
<span data-testid="color-example" style="border: 1px solid #fff">Hello World</span>
95+
`)
96+
expect(queryByTestId('color-example')).toHaveStyle('border: 1px solid #fff')
97+
})
5998
})

src/to-have-style.js

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
import {parse} from 'css'
21
import {matcherHint} from 'jest-matcher-utils'
32
import jestDiff from 'jest-diff'
43
import chalk from 'chalk'
5-
import {checkHtmlElement} from './utils'
4+
import {checkHtmlElement, checkValidCSS} from './utils'
65

7-
function parseCSS(css) {
8-
const ast = parse(`selector { ${css} }`, {silent: true}).stylesheet
9-
if (ast.parsingErrors && ast.parsingErrors.length > 0) {
10-
const {reason, line, column} = ast.parsingErrors[0]
11-
return {
12-
parsingError: `Syntax error parsing expected css: ${reason} in ${line}:${column}`,
13-
}
14-
}
15-
const parsedRules = ast.rules[0].declarations
16-
.filter(d => d.type === 'declaration')
17-
.reduce(
18-
(obj, {property, value}) => Object.assign(obj, {[property]: value}),
19-
{},
20-
)
21-
return {parsedRules}
6+
function getStyleDeclaration(css) {
7+
const copy = document.createElement('div')
8+
copy.style = css
9+
const styles = copy.style
10+
11+
return Array.from(styles).reduce(
12+
(acc, name) => ({...acc, [name]: styles[name]}),
13+
{},
14+
)
2215
}
2316

2417
function isSubset(styles, computedStyle) {
@@ -55,14 +48,11 @@ function expectedDiff(expected, computedStyles) {
5548

5649
export function toHaveStyle(htmlElement, css) {
5750
checkHtmlElement(htmlElement, toHaveStyle, this)
58-
const {parsedRules: expected, parsingError} = parseCSS(css)
59-
if (parsingError) {
60-
return {
61-
pass: this.isNot, // Fail regardless of the test being positive or negative
62-
message: () => parsingError,
63-
}
64-
}
51+
checkValidCSS(css, toHaveStyle, this)
52+
53+
const expected = getStyleDeclaration(css)
6554
const received = getComputedStyle(htmlElement)
55+
6656
return {
6757
pass: isSubset(expected, received),
6858
message: () => {

src/utils.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
printReceived,
88
stringify,
99
} from 'jest-matcher-utils'
10+
import {parse} from 'css'
1011

1112
class HtmlElementTypeError extends Error {
1213
constructor(received, matcherFn, context) {
@@ -40,6 +41,39 @@ function checkHtmlElement(htmlElement, ...args) {
4041
}
4142
}
4243

44+
class InvalidCSSError extends Error {
45+
constructor(received, matcherFn) {
46+
super()
47+
48+
/* istanbul ignore next */
49+
if (Error.captureStackTrace) {
50+
Error.captureStackTrace(this, matcherFn)
51+
}
52+
this.message = [
53+
received.message,
54+
'',
55+
receivedColor(`Failing css:`),
56+
receivedColor(`${received.css}`),
57+
].join('\n')
58+
}
59+
}
60+
61+
function checkValidCSS(css, ...args) {
62+
const ast = parse(`selector { ${css} }`, {silent: true}).stylesheet
63+
64+
if (ast.parsingErrors && ast.parsingErrors.length > 0) {
65+
const {reason, line} = ast.parsingErrors[0]
66+
67+
throw new InvalidCSSError(
68+
{
69+
css,
70+
message: `Syntax error parsing expected css: ${reason} on line: ${line}`,
71+
},
72+
...args,
73+
)
74+
}
75+
}
76+
4377
class InvalidDocumentError extends Error {
4478
constructor(message, matcherFn) {
4579
super()
@@ -108,4 +142,11 @@ function deprecate(name, replacementText) {
108142
)
109143
}
110144

111-
export {checkDocumentKey, checkHtmlElement, deprecate, getMessage, matches}
145+
export {
146+
checkDocumentKey,
147+
checkHtmlElement,
148+
checkValidCSS,
149+
deprecate,
150+
getMessage,
151+
matches,
152+
}

0 commit comments

Comments
 (0)