diff --git a/src/__tests__/to-have-style.js b/src/__tests__/to-have-style.js
index 5c930e10..b66b9bc2 100644
--- a/src/__tests__/to-have-style.js
+++ b/src/__tests__/to-have-style.js
@@ -1,59 +1,98 @@
import {render} from './helpers/test-utils'
-test('.toHaveStyle', () => {
- const {container} = render(`
+describe('.toHaveStyle', () => {
+ test('handles positive test cases', () => {
+ const {container} = render(`
+
+ Hello World
+
+ `)
+
+ const style = document.createElement('style')
+ style.innerHTML = `
+ .label {
+ background-color: black;
+ color: white;
+ float: left;
+ }
+ `
+ document.body.appendChild(style)
+ document.body.appendChild(container)
+
+ expect(container.querySelector('.label')).toHaveStyle(`
+ height: 100%;
+ color: white;
+ background-color: blue;
+ `)
+
+ expect(container.querySelector('.label')).toHaveStyle(`
+ background-color: blue;
+ color: white;
+ `)
+ expect(container.querySelector('.label')).toHaveStyle(
+ 'background-color:blue;color:white',
+ )
+
+ expect(container.querySelector('.label')).not.toHaveStyle(`
+ color: white;
+ font-weight: bold;
+ `)
+ })
+
+ test('handles negative test cases', () => {
+ const {container} = render(`
Hello World
`)
- const style = document.createElement('style')
- style.innerHTML = `
+ const style = document.createElement('style')
+ style.innerHTML = `
.label {
background-color: black;
color: white;
float: left;
}
`
- document.body.appendChild(style)
- document.body.appendChild(container)
+ document.body.appendChild(style)
+ document.body.appendChild(container)
- expect(container.querySelector('.label')).toHaveStyle(`
- height: 100%;
- color: white;
- background-color: blue;
- `)
+ expect(() =>
+ expect(container.querySelector('.label')).toHaveStyle(
+ 'font-weight: bold',
+ ),
+ ).toThrowError()
+ expect(() =>
+ expect(container.querySelector('.label')).not.toHaveStyle('color: white'),
+ ).toThrowError()
- expect(container.querySelector('.label')).toHaveStyle(`
- background-color: blue;
- color: white;
- `)
- expect(container.querySelector('.label')).toHaveStyle(
- 'background-color:blue;color:white',
- )
+ // Make sure the test fails if the css syntax is not valid
+ expect(() =>
+ expect(container.querySelector('.label')).not.toHaveStyle(
+ 'font-weight bold',
+ ),
+ ).toThrowError()
+ expect(() =>
+ expect(container.querySelector('.label')).toHaveStyle('color white'),
+ ).toThrowError()
- expect(container.querySelector('.label')).not.toHaveStyle(`
- color: white;
- font-weight: bold;
- `)
+ document.body.removeChild(style)
+ document.body.removeChild(container)
+ })
- expect(() =>
- expect(container.querySelector('.label')).toHaveStyle('font-weight: bold'),
- ).toThrowError()
- expect(() =>
- expect(container.querySelector('.label')).not.toHaveStyle('color: white'),
- ).toThrowError()
-
- // Make sure the test fails if the css syntax is not valid
- expect(() =>
- expect(container.querySelector('.label')).not.toHaveStyle(
- 'font-weight bold',
- ),
- ).toThrowError()
- expect(() =>
- expect(container.querySelector('.label')).toHaveStyle('color white'),
- ).toThrowError()
-
- document.body.removeChild(style)
- document.body.removeChild(container)
+ test('properly normalizes colors', () => {
+ const {queryByTestId} = render(`
+ Hello World
+ `)
+ expect(queryByTestId('color-example')).toHaveStyle(
+ 'background-color: #123456',
+ )
+ })
+
+ test('properly normalizes colors for border', () => {
+ const {queryByTestId} = render(`
+ Hello World
+ `)
+ expect(queryByTestId('color-example')).toHaveStyle('border: 1px solid #fff')
+ })
})
diff --git a/src/to-have-style.js b/src/to-have-style.js
index 70670a10..2f9493b1 100644
--- a/src/to-have-style.js
+++ b/src/to-have-style.js
@@ -1,24 +1,17 @@
-import {parse} from 'css'
import {matcherHint} from 'jest-matcher-utils'
import jestDiff from 'jest-diff'
import chalk from 'chalk'
-import {checkHtmlElement} from './utils'
+import {checkHtmlElement, checkValidCSS} from './utils'
-function parseCSS(css) {
- const ast = parse(`selector { ${css} }`, {silent: true}).stylesheet
- if (ast.parsingErrors && ast.parsingErrors.length > 0) {
- const {reason, line, column} = ast.parsingErrors[0]
- return {
- parsingError: `Syntax error parsing expected css: ${reason} in ${line}:${column}`,
- }
- }
- const parsedRules = ast.rules[0].declarations
- .filter(d => d.type === 'declaration')
- .reduce(
- (obj, {property, value}) => Object.assign(obj, {[property]: value}),
- {},
- )
- return {parsedRules}
+function getStyleDeclaration(css) {
+ const copy = document.createElement('div')
+ copy.style = css
+ const styles = copy.style
+
+ return Array.from(styles).reduce(
+ (acc, name) => ({...acc, [name]: styles[name]}),
+ {},
+ )
}
function isSubset(styles, computedStyle) {
@@ -55,14 +48,11 @@ function expectedDiff(expected, computedStyles) {
export function toHaveStyle(htmlElement, css) {
checkHtmlElement(htmlElement, toHaveStyle, this)
- const {parsedRules: expected, parsingError} = parseCSS(css)
- if (parsingError) {
- return {
- pass: this.isNot, // Fail regardless of the test being positive or negative
- message: () => parsingError,
- }
- }
+ checkValidCSS(css, toHaveStyle, this)
+
+ const expected = getStyleDeclaration(css)
const received = getComputedStyle(htmlElement)
+
return {
pass: isSubset(expected, received),
message: () => {
diff --git a/src/utils.js b/src/utils.js
index 2bd89492..d83ca385 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -7,6 +7,7 @@ import {
printReceived,
stringify,
} from 'jest-matcher-utils'
+import {parse} from 'css'
class HtmlElementTypeError extends Error {
constructor(received, matcherFn, context) {
@@ -40,6 +41,39 @@ function checkHtmlElement(htmlElement, ...args) {
}
}
+class InvalidCSSError extends Error {
+ constructor(received, matcherFn) {
+ super()
+
+ /* istanbul ignore next */
+ if (Error.captureStackTrace) {
+ Error.captureStackTrace(this, matcherFn)
+ }
+ this.message = [
+ received.message,
+ '',
+ receivedColor(`Failing css:`),
+ receivedColor(`${received.css}`),
+ ].join('\n')
+ }
+}
+
+function checkValidCSS(css, ...args) {
+ const ast = parse(`selector { ${css} }`, {silent: true}).stylesheet
+
+ if (ast.parsingErrors && ast.parsingErrors.length > 0) {
+ const {reason, line} = ast.parsingErrors[0]
+
+ throw new InvalidCSSError(
+ {
+ css,
+ message: `Syntax error parsing expected css: ${reason} on line: ${line}`,
+ },
+ ...args,
+ )
+ }
+}
+
class InvalidDocumentError extends Error {
constructor(message, matcherFn) {
super()
@@ -108,4 +142,11 @@ function deprecate(name, replacementText) {
)
}
-export {checkDocumentKey, checkHtmlElement, deprecate, getMessage, matches}
+export {
+ checkDocumentKey,
+ checkHtmlElement,
+ checkValidCSS,
+ deprecate,
+ getMessage,
+ matches,
+}