Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add matchesSelector() assertions #208

Merged
merged 14 commits into from
Jan 9, 2019
58 changes: 48 additions & 10 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@
- [hasNoValue](#hasnovalue)
- [Parameters](#parameters-22)
- [Examples](#examples-22)
- [matchesSelector](#matchesselector)
- [Parameters](#parameters-23)
- [Examples](#examples-23)
- [doesNotMatchSelector](#doesnotmatchselector)
- [Parameters](#parameters-24)
- [Examples](#examples-24)
- [hasStyle](#hasstyle)
- [Parameters](#parameters-23)
- [Examples](#examples-23)
- [Parameters](#parameters-25)
- [Examples](#examples-25)

## assert.dom()

Expand Down Expand Up @@ -111,7 +117,7 @@ Assert an [HTMLElement](https://developer.mozilla.org/docs/Web/HTML/Element) (or
#### Examples

```javascript
assert.dom('#title').exists();
assert.dom('#title').exists();
assert.dom('.choice').exists({ count: 4 });
```

Expand Down Expand Up @@ -417,10 +423,10 @@ attribute and stripping/collapsing whitespace.
#### Examples

```javascript
// <h2 id="title">
// Welcome to <b>QUnit</b>
// </h2>

// <h2 id="title">
// Welcome to <b>QUnit</b>
// </h2>
assert.dom('#title').hasText('Welcome to QUnit');
```

Expand Down Expand Up @@ -544,6 +550,38 @@ Assert that the `value` property of an [HTMLInputElement](https://developer.mozi
assert.dom('input.username').hasNoValue();
```

### matchesSelector

Assert that the target selector selects only Elements that are also selected by
compareSelector.

#### Parameters

- `compareSelector` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `message` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?**

#### Examples

```javascript
assert.dom('p.red').matchesSelector('div.wrapper p:last-child')
```

### doesNotMatchSelector

Assert that the target selector selects only Elements that are not also selected by
compareSelector.

#### Parameters

- `compareSelector` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)**
- `message` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?**

#### Examples

```javascript
assert.dom('input').doesNotMatchSelector('input[disabled]')
```

## hasStyle

- **See: [#hasClass](#hasClass)**
Expand All @@ -559,8 +597,8 @@ Assert that the [HTMLElement][] has the `expected` style declarations using
### Examples

```javascript
assert.dom('.progress-bar').hasStyle({
opacity: 1,
display: 'block'
assert.dom('.progress-bar').hasStyle({
opacity: 1,
display: 'block'
});
```
123 changes: 123 additions & 0 deletions lib/__tests__/matches-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/* eslint-env jest */

import TestAssertions from '../helpers/test-assertions';

describe('assert.dom(...).matchesSelector()', () => {
let assert;

beforeEach(() => {
assert = new TestAssertions();
});

describe('matchesSelector: success scenarios', () => {
test('succeeds for one element matching the selector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p.last').matchesSelector('div>p:last-child');

expect(assert.results).toEqual([{
actual: 1,
expected: 1,
message: 'The element selected by p.last also matches the selector div>p:last-child.',
result: true
}]);
});

test('succeeds for multiple elements, all sucessfully matched', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>.';

assert.dom('p').matchesSelector('div>p');

expect(assert.results).toEqual([{
actual: 3,
expected: 3,
message: '3 elements, selected by p, also match the selector div>p.',
result: true
}]);
});
});
lindem marked this conversation as resolved.
Show resolved Hide resolved

describe('matchesSelector: failure scenarios', () => {
test('fails for one element not matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p.first').matchesSelector('div>p:last-child');

expect(assert.results).toEqual([{
actual: 0,
expected: 1,
message: 'The element selected by p.first did not also match the selector div>p:last-child.',
result: false
}]);
});

test('fails for multiple element not all matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p').matchesSelector('p + p');

expect(assert.results).toEqual([{
actual: 2,
expected: 3,
message: '1 out of 3 elements selected by p did not also match the selector p + p.',
result: false
}]);
});
})

describe('doesNotMatchSelector: success scenarios', () => {
lindem marked this conversation as resolved.
Show resolved Hide resolved
test('succeeds for one element not matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p.first').doesNotMatchSelector('p + p');

expect(assert.results).toEqual([{
actual: 0,
expected: 0,
message: 'The element selected by p.first did not also match the selector p + p.',
result: true
}]);
});

test('succeeds for multiple elements not matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p + p').doesNotMatchSelector('div>p.first');

expect(assert.results).toEqual([{
actual: 0,
expected: 0,
message: '2 elements, selected by p + p, did not also match the selector div>p.first.',
result: true
}]);
});
});

describe('doesNotMatchSelector: failure scenarios', () => {
test('succeeds for one element not matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p.last').doesNotMatchSelector('div>p:nth-child(3)');

expect(assert.results).toEqual([{
actual: 1,
expected: 0,
message: 'The element selected by p.last did also match the selector div>p:nth-child(3).',
result: false
}]);
});

test('succeeds for multiple elements not matching compareSelector', () => {
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';

assert.dom('p').doesNotMatchSelector('div>p');

expect(assert.results).toEqual([{
actual: 3,
expected: 0,
message: '3 elements out of 3, selected by p, did also match the selector div>p.',
result: false
}]);
});
})
});
70 changes: 69 additions & 1 deletion lib/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import isRequired from './assertions/is-required';
import isNotRequired from './assertions/is-not-required';
import isVisible from './assertions/is-visible';
import isDisabled from './assertions/is-disabled';

import matchesSelector from './assertions/matches-selector';
import elementToString from './helpers/element-to-string';
import collapseWhitespace from './helpers/collapse-whitespace';

Expand Down Expand Up @@ -765,6 +765,74 @@ export default class DOMAssertions {
this.hasNoValue(message);
}

/**
* Assert that the target selector selects only Elements that are also selected by
* compareSelector.
*
* @param {string} compareSelector
* @param {string?} message
*
* @example
* assert.dom('p.red').matchesSelector('div.wrapper p:last-child')
*/
matchesSelector(compareSelector: string, message?: string) {
let [targetElements, matchFailures] = matchesSelector(this.target, compareSelector);
let singleElement: boolean = targetElements === 1;

if (matchFailures === 0) {
// no failures matching.
if (!message) {
message = singleElement ?
`The element selected by ${this.target} also matches the selector ${compareSelector}.` :
`${targetElements} elements, selected by ${this.target}, also match the selector ${compareSelector}.`
}
this.pushResult({result: true, expected: targetElements, actual: targetElements, message});
} else {
let difference = targetElements - matchFailures;
// there were failures when matching.
if (!message) {
message = singleElement ?
`The element selected by ${this.target} did not also match the selector ${compareSelector}.` :
`${matchFailures} out of ${targetElements} elements selected by ${this.target} did not also match the selector ${compareSelector}.`;
}
this.pushResult({ result: false, expected: targetElements, actual: difference, message });
}
}

/**
* Assert that the target selector selects only Elements that are not also selected by
* compareSelector.
*
* @param {string} compareSelector
* @param {string?} message
*
* @example
* assert.dom('input').doesNotMatchSelector('input[disabled]')
*/
doesNotMatchSelector(compareSelector: string, message?: string) {
let [targetElements, matchFailures] = matchesSelector(this.target, compareSelector);
let singleElement: boolean = targetElements === 1;

if (matchFailures === targetElements) {
// the assertion is successful because no element matched the other selector.
if (!message) {
message = singleElement ?
`The element selected by ${this.target} did not also match the selector ${compareSelector}.` :
`${targetElements} elements, selected by ${this.target}, did not also match the selector ${compareSelector}.`;
}
this.pushResult({ result: true, expected: 0, actual: 0, message })
} else {
let difference = targetElements - matchFailures;
// the assertion fails because at least one element matched the other selector.
if (!message) {
message = singleElement ?
`The element selected by ${this.target} did also match the selector ${compareSelector}.` :
`${difference} elements out of ${targetElements}, selected by ${this.target}, did also match the selector ${compareSelector}.`;
}
this.pushResult({ result: false, expected: 0, actual: difference, message })
}
}

/**
* @private
*/
Expand Down
15 changes: 15 additions & 0 deletions lib/assertions/matches-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function matchesSelector(
targetSelector: string | Element,
compareSelector: string
): Array<number> {
let targetElements: Array<Element> =
typeof targetSelector === 'string'
? Array.from(document.querySelectorAll(targetSelector))
: [targetSelector];
lindem marked this conversation as resolved.
Show resolved Hide resolved

let failures = targetElements.reduce(function(acc, element) {
return element.matches(compareSelector) ? acc : acc + 1;
}, 0);
lindem marked this conversation as resolved.
Show resolved Hide resolved

return [targetElements.length, failures];
}