Skip to content

Commit 070140c

Browse files
committed
add toHaveAttribute custom matcher
1 parent b6c2507 commit 070140c

File tree

9 files changed

+112
-21
lines changed

9 files changed

+112
-21
lines changed

.babelrc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
["env", {
44
"modules": false,
55
"targets": {
6-
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
6+
"browsers": [
7+
"> 1%",
8+
"last 2 versions",
9+
"not ie <= 8"
10+
]
711
}
8-
}]
9-
],
12+
}]
13+
],
1014
"plugins": [
1115
"transform-object-rest-spread",
1216
"transform-runtime"
1317
],
1418
"env": {
1519
"test": {
16-
"presets": ["env"]
20+
"presets": ["env"]
1721
}
1822
}
1923
}

src/extend-expect.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
import extensions from './jest-extensions'
22

3-
const {toBeInTheDOM, toHaveTextContent} = extensions
4-
expect.extend({toBeInTheDOM, toHaveTextContent})
3+
const {toBeInTheDOM, toHaveTextContent, toHaveAttribute} = extensions
4+
expect.extend({toBeInTheDOM, toHaveTextContent, toHaveAttribute})

src/jest-extensions.js

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
import {matcherHint, printReceived, printExpected} from 'jest-matcher-utils' //eslint-disable-line import/no-extraneous-dependencies
1+
//eslint-disable-next-line import/no-extraneous-dependencies
2+
import {
3+
matcherHint,
4+
printReceived,
5+
printExpected,
6+
stringify,
7+
RECEIVED_COLOR as receivedColor,
8+
EXPECTED_COLOR as expectedColor,
9+
} from 'jest-matcher-utils'
210
import {matches} from './utils'
311

412
function getDisplayName(subject) {
@@ -9,10 +17,30 @@ function getDisplayName(subject) {
917
}
1018
}
1119

20+
function checkHtmlElement(htmlElement) {
21+
if (!(htmlElement instanceof HTMLElement)) {
22+
throw new Error(
23+
`The given subject is a ${getDisplayName(
24+
htmlElement,
25+
)}, not an HTMLElement`,
26+
)
27+
}
28+
}
29+
1230
const assertMessage = (assertionName, message, received, expected) =>
1331
`${matcherHint(`${assertionName}`, 'received', '')} \n${message}: ` +
1432
`${printExpected(expected)} \nReceived: ${printReceived(received)}`
1533

34+
function printAttribute(name, value) {
35+
return value === undefined ? name : `${name}=${stringify(value)}`
36+
}
37+
38+
function getAttributeComment(name, value) {
39+
return value === undefined
40+
? `element.hasAttribute(${stringify(name)})`
41+
: `element.getAttribute(${stringify(name)}) === ${stringify(value)}`
42+
}
43+
1644
const extensions = {
1745
toBeInTheDOM(received) {
1846
getDisplayName(received)
@@ -42,13 +70,7 @@ const extensions = {
4270
},
4371

4472
toHaveTextContent(htmlElement, checkWith) {
45-
if (!(htmlElement instanceof HTMLElement))
46-
throw new Error(
47-
`The given subject is a ${getDisplayName(
48-
htmlElement,
49-
)}, not an HTMLElement`,
50-
)
51-
73+
checkHtmlElement(htmlElement)
5274
const textContent = htmlElement.textContent
5375
const pass = matches(textContent, htmlElement, checkWith)
5476
if (pass) {
@@ -75,6 +97,41 @@ const extensions = {
7597
}
7698
}
7799
},
100+
101+
toHaveAttribute(htmlElement, name, expectedValue) {
102+
checkHtmlElement(htmlElement)
103+
const isExpectedValuePresent = expectedValue !== undefined
104+
const hasAttribute = htmlElement.hasAttribute(name)
105+
const receivedValue = htmlElement.getAttribute(name)
106+
return {
107+
pass: isExpectedValuePresent
108+
? hasAttribute && receivedValue === expectedValue
109+
: hasAttribute,
110+
message: () => {
111+
const to = this.isNot ? 'not to' : 'to'
112+
const receivedAttribute = receivedColor(
113+
hasAttribute
114+
? printAttribute(name, receivedValue)
115+
: 'attribute was not found',
116+
)
117+
const expectedMsg = `Expected the element ${to} have attribute:\n ${expectedColor(
118+
printAttribute(name, expectedValue),
119+
)}`
120+
const matcher = matcherHint(
121+
`${this.isNot ? '.not' : ''}.toHaveAttribute`,
122+
'element',
123+
printExpected(name),
124+
{
125+
secondArgument: isExpectedValuePresent
126+
? printExpected(expectedValue)
127+
: undefined,
128+
comment: getAttributeComment(name, expectedValue),
129+
},
130+
)
131+
return `${matcher}\n\n${expectedMsg}\nReceived:\n ${receivedAttribute}`
132+
},
133+
}
134+
},
78135
}
79136

80137
export default extensions

tests/__tests__/components/Validate.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ export default {
3434
},
3535
methods: {
3636
submit () {
37-
this.$validator.validateAll()
3837
}
3938
}
4039
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<button data-testid="ok-button" type="submit" disabled>
3+
OK
4+
</button>
5+
</template>

tests/__tests__/components/queries/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import AltText from './AltText'
2+
import Attribute from './Attribute'
23
import Empty from './Empty'
34
import LabelText from './LabelText'
45
import Placeholder from './Placeholder'
@@ -9,6 +10,7 @@ import JestHelpers from './JestHelpers'
910

1011
export {
1112
AltText,
13+
Attribute,
1214
Empty,
1315
GetByAltText,
1416
LabelText,

tests/__tests__/element-queries.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import '../../src/extend-expect'
33

44
import {
55
Empty,
6+
Attribute,
67
GetByAltText,
78
LabelText,
89
LabelWithNoFormControl,
@@ -100,4 +101,30 @@ test('using jest helpers to assert element states', () => {
100101
).toThrowError()
101102
})
102103

104+
test('using jest helpers to check element attributes', () => {
105+
const {queryByTestId} = render(Attribute)
106+
107+
expect(queryByTestId('ok-button')).toHaveAttribute('disabled')
108+
expect(queryByTestId('ok-button')).toHaveAttribute('type')
109+
expect(queryByTestId('ok-button')).not.toHaveAttribute('class')
110+
expect(queryByTestId('ok-button')).toHaveAttribute('type', 'submit')
111+
expect(queryByTestId('ok-button')).not.toHaveAttribute('type', 'button')
112+
113+
expect(() =>
114+
expect(queryByTestId('ok-button')).not.toHaveAttribute('disabled'),
115+
).toThrowError()
116+
expect(() =>
117+
expect(queryByTestId('ok-button')).not.toHaveAttribute('type'),
118+
).toThrowError()
119+
expect(() =>
120+
expect(queryByTestId('ok-button')).toHaveAttribute('class'),
121+
).toThrowError()
122+
expect(() =>
123+
expect(queryByTestId('ok-button')).not.toHaveAttribute('type', 'submit'),
124+
).toThrowError()
125+
expect(() =>
126+
expect(queryByTestId('ok-button')).toHaveAttribute('type', 'button'),
127+
).toThrowError()
128+
})
129+
103130
/* eslint jsx-a11y/label-has-for:0 */

tests/__tests__/form.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import Vue from 'vue'
2-
import { render, Simulate } from '../../src'
2+
import { render, Simulate, wait } from '../../src'
33
import Login from './components/Login'
44

5-
test('login form submits', () => {
5+
test('login form submits', async () => {
66
const fakeUser = {username: 'jackiechan', password: 'hiya! 🥋'}
77
const handleSubmit = jest.fn()
88
const {updateState, getByLabelText, getByText} = render(
@@ -16,8 +16,6 @@ test('login form submits', () => {
1616
// Act - this is waiting on an issue in @vue/test-utils to allow v-model to be updated by
1717
// changes to DOM elements
1818

19-
// Simulate.change(usernameNode, fakeUser.username)
20-
// Simulate.change(passwordNode, fakeUser.password)
2119
updateState(fakeUser)
2220

2321
// NOTE: in jsdom, it's not possible to trigger a form submission

tests/__tests__/validate-plugin.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import '../../src/extend-expect'
66
import Validate from './components/Validate'
77

88
test('can validate using plugin', async () => {
9-
const { getByPlaceholderText, queryByTestId, html } = render(Validate, {},
9+
const { getByPlaceholderText, queryByTestId } = render(Validate, {},
1010
vue => vue.use(VeeValidate, { events: 'blur' }))
1111

1212
const usernameInput = getByPlaceholderText('Username...')
@@ -15,6 +15,5 @@ test('can validate using plugin', async () => {
1515

1616
await wait()
1717

18-
console.log(html())
1918
expect(queryByTestId('username-errors')).toBeInTheDOM()
2019
})

0 commit comments

Comments
 (0)