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'
});
```
84 changes: 84 additions & 0 deletions lib/__tests__/does-not-match-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint-env jest */

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

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

beforeEach(() => {
assert = new TestAssertions();
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';
});

describe('success scenarios', () => {
test('one element (selector passed) not matching compareSelector', () => {
assert.dom('p.first').doesNotMatchSelector('p + p');

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

test('one element (element passed) not matching compareSelector', () => {
let element = document.querySelector('p.first')
assert.dom(element).doesNotMatchSelector('p + p');

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

test('multiple elements all not matching compareSelector', () => {
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('failure scenarios', () => {
test('one element (selector passed) matching compareSelector', () => {
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 must not also match the selector div>p:nth-child(3).',
result: false
}]);
});

test('one element (element passed) matching compareSelector', () => {
let element = document.querySelector('p.last');
assert.dom(element).doesNotMatchSelector('div>p:nth-child(3)');

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

test('multiple elements, some matching compareSelector', () => {
assert.dom('p').doesNotMatchSelector('div>p');

expect(assert.results).toEqual([{
actual: 3,
expected: 0,
message: '3 elements out of 3, selected by p, must not also match the selector div>p.',
result: false
}]);
});
})
});
84 changes: 84 additions & 0 deletions lib/__tests__/matches-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* eslint-env jest */

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

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

beforeEach(() => {
assert = new TestAssertions();
document.body.innerHTML = '<div><p class="first"></p><p></p><p class="last"></p></div>';
});

describe('success scenarios', () => {
test('succeeds for one element (selector passed) matching the selector', () => {
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 one element (element passed) matching compareSelector', () => {
const element = document.querySelector('p.last');
assert.dom(element).matchesSelector('div>p:last-child');

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

test('succeeds for multiple elements, all sucessfully matched', () => {
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('failure scenarios', () => {
test('one element (selector passed) not matching compareSelector', () => {
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('one element (element passed) not matching compareSelector', () => {
const element = document.querySelector('p.first');
assert.dom(element).matchesSelector('div>p:last-child');

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

test('multiple elements not all matching compareSelector', () => {
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
}]);
});
})
});
76 changes: 75 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,80 @@ 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 = this.target instanceof Element ? [this.target] : this.findElements();
let targets = targetElements.length;
let matchFailures = matchesSelector(targetElements, compareSelector);
let singleElement: boolean = targets === 1;
let selectedByPart = (this.target instanceof Element) ? 'passed' : `selected by ${this.target}`;

if (matchFailures === 0) {
// no failures matching.
if (!message) {
message = singleElement ?
`The element ${selectedByPart} also matches the selector ${compareSelector}.` :
`${targets} elements, selected by ${this.target}, also match the selector ${compareSelector}.`
}
this.pushResult({ result: true, expected: targets, actual: targets, message });
} else {
let difference = targets - matchFailures;
// there were failures when matching.
if (!message) {
message = singleElement ?
`The element ${selectedByPart} did not also match the selector ${compareSelector}.` :
`${matchFailures} out of ${targets} elements selected by ${this.target} did not also match the selector ${compareSelector}.`;
}
this.pushResult({ result: false, expected: targets, 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 = this.target instanceof Element ? [this.target] : this.findElements();
let targets = targetElements.length;
let matchFailures = matchesSelector(targetElements, compareSelector);
let singleElement: boolean = targets === 1;
let selectedByPart = this.target instanceof Element ? 'passed' : `selected by ${this.target}`;

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

/**
* @private
*/
Expand Down
8 changes: 8 additions & 0 deletions lib/assertions/matches-selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default function matchesSelector(
elements: Array<Element>,
compareSelector: string
): number {
let failures = elements.filter(it => !it.matches(compareSelector));

return failures.length;
}