Skip to content

Commit d964ebd

Browse files
committed
feat: make .text and .value support .include, .members, and .satisfy
1 parent 32b12fb commit d964ebd

File tree

13 files changed

+421
-201
lines changed

13 files changed

+421
-201
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ Then, we can add our assertion to the chain.
3535
- `await expect(selector).to.have.text('string')` - Test the text value of the selected element(s) against supplied string. Succeeds if at least one element matches exactly
3636
- `await expect(selector).to.have.text(/regex/)` - Test the text value of the selected element(s) against the supplied regular expression. Succeeds if at least one element matches
3737
- `await expect(selector).to.have.text(/regex/)` - Test the text value of the selected element(s) against the supplied regular expression. Succeeds if at least one element matches
38+
- `await expect(selector).text.to.ordered.include.members(['foo', 'bar'])` - Test that the text of the selected elements includes `'foo'` followed by `'bar'`
39+
- `await expect(selector).text.to.have.members(['foo', 'bar'])` - Test that the text of the selected elements is exactly the set `['foo', 'bar']`, in any order
40+
- `await expect(selector).text.to.satisfy(fn)` - Test that `fn` returns `true` when called with the array of texts of the selected elements
3841
- `await expect(selector).to.have.attribute('attributeName')` - Test whether [at least one] matching element has the given attribute
3942
- `await expect(selector).to.have.attribute('attributeName', 'string')` - Test the attribute value of the selected element(s) against supplied string. Succeeds if at least one element matches exactly
4043
- `await expect(selector).to.have.attribute('attributeName', /regex/)` - Test the attribute value of the selected element(s) against supplied regular expression. Succeeds if at least one element matches exactly
@@ -45,6 +48,9 @@ Then, we can add our assertion to the chain.
4548
- `await expect(selector).to.have.count.at.most(n)` - Test that at most `n` elements exist in the DOM with the supplied selector
4649
- `await expect(selector).to.have.value('string')` - Test that [at least one] selected element has a value matching the given string
4750
- `await expect(selector).to.have.value(/regex/)` - Test that [at least one] selected element has a value matching the given regular expression
51+
- `await expect(selector).value.to.ordered.include.members(['foo', 'bar'])` - Test that the value of the selected elements includes `'foo'` followed by `'bar'`
52+
- `await expect(selector).value.to.have.members(['foo', 'bar'])` - Test that the value of the selected elements is exactly the set `['foo', 'bar']`, in any order
53+
- `await expect(selector).value.to.satisfy(fn)` - Test that `fn` returns `true` when called with the array of values of the selected elements
4854
- `await expect(selector).to.have.focus()` - (alias for `to.be.focused()`)
4955

5056
You can also always add a `not` in there to negate the assertion:

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"trailingComma": "es5"
4848
},
4949
"config": {
50-
"mocha": "-r @babel/register test/configure.js 'test/**/*.js'",
50+
"mocha": "-r @babel/register test/configure.js test/index.js 'test/**/*.js'",
5151
"commitizen": {
5252
"path": "cz-conventional-changelog"
5353
}
@@ -96,7 +96,7 @@
9696
"@jedwards1211/eslint-config-flow": "^2.0.0",
9797
"babel-eslint": "^10.0.1",
9898
"babel-plugin-istanbul": "^5.1.0",
99-
"chai": "^4.2.0",
99+
"chai": "^4.3.0",
100100
"chai-as-promised": "^7.1.1",
101101
"codecov": "^3.1.0",
102102
"copy": "^0.3.2",
@@ -111,7 +111,7 @@
111111
"husky": "^1.1.4",
112112
"istanbul": "^0.4.5",
113113
"lint-staged": "^8.0.4",
114-
"mocha": "^8.2.1",
114+
"mocha": "^8.3.0",
115115
"nyc": "^13.1.0",
116116
"prettier": "^1.15.2",
117117
"prettier-eslint": "^8.8.2",

src/assertions/text.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,27 @@
66

77
import getElements from '../util/getElements'
88

9-
const text = (client, chai, utils, options) =>
10-
async function(expected) {
9+
const text = (client, chai, utils, options) => {
10+
async function assertText(expected) {
1111
const negate = utils.flag(this, 'negate')
1212

13+
const { getValueAndSelector } = utils.flag(this, 'chai-webdriverio-async')
14+
const [texts, selector] = await getValueAndSelector()
15+
1316
const expectedStr =
1417
typeof expected === 'string' ? JSON.stringify(expected) : expected
1518

16-
const [elements, selector] = await getElements(
17-
utils.flag(this, 'object'),
18-
client
19-
)
20-
if (!elements.length) {
19+
if (!texts.length) {
2120
throw new chai.AssertionError(
2221
negate
2322
? `Expected element <${selector}> to not have text ${expectedStr}, but no matching elements were found`
2423
: `Expected element <${selector}> to have text ${expectedStr}, but no matching elements were found`
2524
)
2625
}
2726

28-
const texts = []
29-
const filteredList = (await Promise.all(
30-
elements.map(async element => {
31-
const text = await element.getText()
32-
texts.push(text)
33-
return expected instanceof RegExp
34-
? text.match(expected)
35-
: text === expected
36-
})
37-
)).filter(Boolean)
27+
const filteredList = texts.filter(text =>
28+
expected instanceof RegExp ? text.match(expected) : text === expected
29+
)
3830

3931
this.assert(
4032
filteredList.length > 0,
@@ -46,5 +38,18 @@ const text = (client, chai, utils, options) =>
4638
.join(', ')}`
4739
)
4840
}
41+
assertText.chain = function chainText() {
42+
const obj = utils.flag(this, 'object')
43+
utils.flag(this, 'chai-webdriverio-async', {
44+
type: 'text',
45+
message: `elements' text for #{selector}`,
46+
getValueAndSelector: async () => {
47+
const [elements, selector] = await getElements(obj, client)
48+
return [await Promise.all(elements.map(e => e.getText())), selector]
49+
},
50+
})
51+
}
52+
return assertText
53+
}
4954

5055
export default text

src/assertions/value.js

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,27 @@
66

77
import getElements from '../util/getElements'
88

9-
const value = (client, chai, utils, options) =>
10-
async function(expected) {
9+
const value = (client, chai, utils, options) => {
10+
async function assertValue(expected) {
1111
const negate = utils.flag(this, 'negate')
1212

13+
const { getValueAndSelector } = utils.flag(this, 'chai-webdriverio-async')
14+
const [values, selector] = await getValueAndSelector()
15+
1316
const expectedStr =
1417
typeof expected === 'string' ? JSON.stringify(expected) : expected
1518

16-
const [elements, selector] = await getElements(
17-
utils.flag(this, 'object'),
18-
client
19-
)
20-
if (!elements.length) {
19+
if (!values.length) {
2120
throw new chai.AssertionError(
2221
negate
2322
? `Expected element <${selector}> to not have value ${expectedStr}, but no matching elements were found`
2423
: `Expected element <${selector}> to have value ${expectedStr}, but no matching elements were found`
2524
)
2625
}
2726

28-
const values = []
29-
const filteredList = (await Promise.all(
30-
elements.map(async element => {
31-
const value = await element.getValue()
32-
values.push(value)
33-
return expected instanceof RegExp
34-
? value.match(expected)
35-
: value === expected
36-
})
37-
)).filter(Boolean)
27+
const filteredList = values.filter(value =>
28+
expected instanceof RegExp ? value.match(expected) : value === expected
29+
)
3830

3931
this.assert(
4032
filteredList.length > 0,
@@ -46,5 +38,18 @@ const value = (client, chai, utils, options) =>
4638
.join(', ')}`
4739
)
4840
}
41+
assertValue.chain = function chainValue() {
42+
const obj = utils.flag(this, 'object')
43+
utils.flag(this, 'chai-webdriverio-async', {
44+
type: 'value',
45+
message: `elements' values for #{selector}`,
46+
getValueAndSelector: async () => {
47+
const [elements, selector] = await getElements(obj, client)
48+
return [await Promise.all(elements.map(e => e.getValue())), selector]
49+
},
50+
})
51+
}
52+
return assertValue
53+
}
4954

5055
export default value

src/index.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,64 @@ export default function(client, options = {}) {
6060
return (async () => {
6161
const { getValueAndSelector, ...rest } = ourFlag
6262
const [value, selector] = await getValueAndSelector()
63-
return handler.call(this, { ...rest, value, selector, args })
63+
if (!handler) return _super.apply(this, args)
64+
return handler.call(this, {
65+
...rest,
66+
_super,
67+
value,
68+
selector,
69+
args,
70+
})
6471
})()
6572
} else {
66-
_super.apply(this, arguments)
73+
_super.apply(this, args)
6774
}
6875
}
6976
})
7077
}
7178

79+
function defaultOverwriteAssert(_super) {
80+
return function chaiWebdriverIOAssertion(...args) {
81+
const ourFlag = utils.flag(this, 'chai-webdriverio-async')
82+
if (ourFlag) {
83+
return (async () => {
84+
const { getValueAndSelector, message } = ourFlag
85+
const [value, selector] = await getValueAndSelector()
86+
const assertion = new Assertion(value)
87+
utils.transferFlags(this, assertion)
88+
utils.flag(assertion, 'object', value)
89+
if (message) {
90+
utils.flag(
91+
assertion,
92+
'message',
93+
[
94+
utils.flag(this, 'message'),
95+
message.replace(/#\{selector\}/g, `<${selector}>`),
96+
]
97+
.filter(Boolean)
98+
.join(': ')
99+
)
100+
}
101+
return _super.apply(assertion, args)
102+
})()
103+
} else {
104+
_super.apply(this, args)
105+
}
106+
}
107+
}
108+
109+
function defaultOverwriteMethod(name) {
110+
Assertion.overwriteMethod(name, defaultOverwriteAssert)
111+
}
112+
113+
function defaultOverwriteChainableMethod(name) {
114+
Assertion.overwriteChainableMethod(
115+
name,
116+
defaultOverwriteAssert,
117+
chain => chain
118+
)
119+
}
120+
72121
overwriteMethod('above', function assertAbove({
73122
value,
74123
selector,
@@ -124,5 +173,25 @@ export default function(client, options = {}) {
124173
value
125174
)
126175
})
176+
177+
overwriteMethod('within', function assertAtMost({
178+
value,
179+
selector,
180+
args: [lower, upper],
181+
}) {
182+
this.assert(
183+
value >= lower && value <= upper,
184+
`Expected <${selector}> to appear in the DOM between ${lower} and ${upper} times, but it shows up ${value} times instead.`,
185+
`Expected <${selector}> not to appear in the DOM between ${lower} and ${upper} times, but it shows up ${value} times instead.`
186+
)
187+
})
188+
189+
defaultOverwriteMethod('members')
190+
defaultOverwriteMethod('oneOf')
191+
defaultOverwriteMethod('satisfy')
192+
defaultOverwriteChainableMethod('include')
193+
defaultOverwriteChainableMethod('includes')
194+
defaultOverwriteChainableMethod('contain')
195+
defaultOverwriteChainableMethod('contains')
127196
}
128197
}

test/assertions/attribute-test.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,18 @@
44
* https://github.com/marcodejongh/chai-webdriverio
55
*/
66

7-
import chai, { expect } from 'chai'
8-
import FakeClient from '../stubs/fake-client'
7+
import { expect } from 'chai'
98
import FakeElement from '../stubs/fake-element'
109
import { describe, beforeEach, it } from 'mocha'
11-
import chaiWebdriverio from '../../src'
10+
import fakeClient from '../stubs/fakeClient'
1211

13-
const fakeClient = new FakeClient()
1412
const fakeElement1 = new FakeElement()
1513
const fakeElement2 = new FakeElement()
1614

1715
describe('attribute', () => {
1816
beforeEach(() => {
19-
fakeClient.__resetStubs__()
2017
fakeElement1.__resetStubs__()
2118
fakeElement2.__resetStubs__()
22-
23-
chai.use(chaiWebdriverio(fakeClient))
2419
})
2520

2621
describe(`When element doesn't exist`, function() {

test/assertions/booleanAssertionTest.js

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,26 @@
1-
import chai, { expect } from 'chai'
2-
1+
import { expect } from 'chai'
32
import { describe, beforeEach, afterEach, it } from 'mocha'
4-
import FakeClient from '../stubs/fake-client'
53
import FakeElement from '../stubs/fake-element'
6-
import chaiWebdriverio from '../../src'
74
import { upperFirst, lowerCase } from 'lodash'
5+
import fakeClient from '../stubs/fakeClient'
86

97
export const booleanAssertionTest = ({
108
method,
119
expectation = lowerCase(method),
1210
allowNone,
1311
}) =>
1412
describe(method, () => {
15-
let fakeClient
1613
let fakeElement1
1714

1815
beforeEach(() => {
19-
fakeClient = new FakeClient()
2016
fakeElement1 = new FakeElement()
2117

2218
fakeElement1[`is${upperFirst(method)}`].resolves(false)
2319
fakeClient.$$.withArgs('.some-selector').resolves([fakeElement1])
2420
fakeClient.$$.withArgs('.other-selector').resolves([])
25-
26-
chai.use(chaiWebdriverio(fakeClient))
2721
})
2822

2923
afterEach(() => {
30-
fakeClient.__resetStubs__()
3124
fakeElement1.__resetStubs__()
3225
})
3326

test/assertions/count-test.js

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,21 @@
44
* https://github.com/marcodejongh/chai-webdriverio
55
*/
66

7-
import chai, { expect } from 'chai'
8-
import FakeClient from '../stubs/fake-client'
7+
import { expect } from 'chai'
98
import FakeElement from '../stubs/fake-element'
10-
import chaiWebdriverio from '../../src/index'
9+
import { describe, beforeEach, it } from 'mocha'
10+
import fakeClient from '../stubs/fakeClient'
1111

1212
describe('count', () => {
1313
let elements
14-
let fakeClient
1514

1615
beforeEach(() => {
1716
elements = [new FakeElement(), new FakeElement()]
18-
fakeClient = new FakeClient()
1917

2018
fakeClient.$$.rejects('ArgumentError')
2119
fakeClient.$$.withArgs('.some-selector').resolves(elements)
22-
23-
chai.use(chaiWebdriverio(fakeClient))
2420
})
2521

26-
afterEach(() => fakeClient.__resetStubs__())
27-
2822
describe('When not negated', () => {
2923
it(`resolves when element count matches expectation`, async function() {
3024
await expect('.some-selector').to.have.count(2)
@@ -174,4 +168,36 @@ describe('count', () => {
174168
)
175169
})
176170
})
171+
172+
describe(`when not negated with within`, function() {
173+
it(`resolves when element count is within expectation`, async function() {
174+
await expect('.some-selector').to.have.count.within(2, 4)
175+
await expect('.some-selector').to.have.count.within(1, 2)
176+
await expect('.some-selector').to.have.count.within(1, 4)
177+
})
178+
it(`rejects when element count is not within expectation`, async function() {
179+
await expect(
180+
expect('.some-selector')
181+
.to.have.count.within(0, 1)
182+
.then(null)
183+
).to.be.rejectedWith(
184+
'Expected <.some-selector> to appear in the DOM between 0 and 1 times, but it shows up 2 times instead.'
185+
)
186+
})
187+
})
188+
describe(`when negated with within`, function() {
189+
it(`resolves when element count is not within expectation`, async function() {
190+
await expect('.some-selector').to.not.have.count.within(0, 1)
191+
await expect('.some-selector').to.not.have.count.within(3, 4)
192+
})
193+
it(`rejects when element count is within expectation`, async function() {
194+
await expect(
195+
expect('.some-selector')
196+
.to.not.have.count.within(1, 2)
197+
.then(null)
198+
).to.be.rejectedWith(
199+
'Expected <.some-selector> not to appear in the DOM between 1 and 2 times, but it shows up 2 times instead.'
200+
)
201+
})
202+
})
177203
})

0 commit comments

Comments
 (0)