Skip to content
This repository has been archived by the owner on Sep 20, 2018. It is now read-only.

Commit

Permalink
[added] any(), and none() assert methods
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Jan 14, 2016
1 parent 75a3b84 commit a2a5b48
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 20 deletions.
60 changes: 49 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ The supported selector syntax is subset of standard css selectors:

- classes: `.foo`
- attributes: `div[propName="hi"]` or `div[boolProp]`
- `>`: direct descendent `div > .foo`
- `>`: direct descendant `div > .foo`
- `+`: adjacent sibling selector
- `~`: general sibling selector
- `:has()`: parent selector `div:has(a.foo)`
Expand All @@ -150,7 +150,7 @@ The supported selector syntax is subset of standard css selectors:
- `:text` matches "text" (renderable) nodes, which may be a non string value (like a number)
- `:dom` matches only DOM components
- `:composite` matches composite (user defined) components
- `:contains(some text)` matches nodes that have a text node descendent containing the provided text
- `:contains(some text)` matches nodes that have a text node descendant containing the provided text
- `:textContent(some text)` matches whose text content matches the provided text

Selector support is derived from the underlying selector engine: [bill](https://github.com/jquense/bill). New minor
Expand Down Expand Up @@ -212,7 +212,7 @@ teaspoon's API.
### Using `tap()`

[`tap()`](#fntap---functioncollection) provides a way to quickly step in the middle of a chain of queries and
collections to make a quite assertion. Below we quickly make a few changes to the component props and
collections to make a quick assertion. Below we quickly make a few changes to the component props and
check that the rendered output is what we'd expect.

```js
Expand All @@ -238,8 +238,8 @@ $(<Greeting name='rikki-tikki-tavi'/>)

### Test specific querying ("ref" style querying).

An age old struggle and gotcha with testing HTML output is that tests are usually not very resilient to
DOM structure changes. You may move a save button into a (or out of) some div your test used to find the button,
An age old struggle with testing HTML output is that tests are usually not very resilient to
DOM structure changes. You may move a save button into (or out of) some div that your test used to find the button,
breaking the test. A classic technique to avoid this is the just use css classes, however it can be hard to
distinguish between styling classes, and testing hooks.

Expand All @@ -248,7 +248,7 @@ In a React environment we can do one better, adding test specific attribute. Thi
that style of selection, its selector engine is more than powerful enough to allow that pattern of querying.

You can choose any prop name you like, but we recommend picking one that isn't likely to collide with a
component's "real" props. In this example lets use `_testID`.
component's "real" props. In this example let's use `_testID`.

```js
let Greeting = props => <div>hello <strong _testID='name'>{props.name}</strong></div>;
Expand All @@ -260,7 +260,7 @@ $(<Greeting name='Betty' />)
.should.equal('Betty')
```

You can adapt and expand this pattern however your team likes, maybe just using the single testing prop or a suite.
You can adapt and expand this pattern however your team likes, maybe just using the single testing prop or a few.
You can also add some helper methods or pseudo selectors to help codify enforce your teams testing conventions.

## Adding collection methods and pseudo selectors
Expand Down Expand Up @@ -302,7 +302,7 @@ $.registerPseudo('disabled', (node, innerSelector)=> {
})
```

If you want your psuedo selector to accept something other than a _selector_ as it's inner argument
If you want your pseudo selector to accept something other than a _selector_ as it's inner argument
(as in `:has('foo')`), then pass `false` as the second argument (`registerPseudo(myPseudo, false, testFunction)`).

## API
Expand Down Expand Up @@ -365,7 +365,7 @@ Since shallow collections are not "live" in the same way a real rendered compone
need to manually update the root collection to flush changes (such as those triggered by a child component).

In general you may not have to ever use `update()` since teaspoon tries to take care of all that for
you by spying on the `componentDidUpdate` lifecycle hook of root component instance.
you by spying on the `componentDidUpdate` life-cycle hook of root component instance.

##### `$.instance.fn.unmount()`

Expand Down Expand Up @@ -613,7 +613,7 @@ $list.find('li.foo').only().length // 1

##### `$.fn.single(selector)`

Find and assert that only item matches the provided selector.
Find assert that only item matches the provided selector.

```js
let $list = $(
Expand All @@ -629,6 +629,44 @@ $list.single('li') // Error! Matched more than one <li/>
$list.single('.foo').length // 1
```


##### `$.fn.any([selector])`

Assert that the collection contains one or more nodes.
Optionally search by a provided selector.

```js
let $list = $(
<ul>
<li>1</li>
<li className='foo'>2</li>
<li>3</li>
</ul>
);

$list.any('p') // Error!

$list.any('li').length // 3
```

##### `$.fn.none([selector])`

Assert that the collection contains no nodes. Optionally search by a provided selector.

```js
let $list = $(
<ul>
<li>1</li>
<li className='foo'>2</li>
<li>3</li>
</ul>
);

$list.none('li') // Error!

$list.none('p').length // 0
```

##### `$.fn.text()`

Return the text content of the matched Collection.
Expand Down Expand Up @@ -656,7 +694,7 @@ Trigger a "synthetic" React event on the collection items. works just like `Reac
##### `$.element.fn.trigger(String eventName, [Object data])`

Simulates (poorly) event triggering for shallow collections. The method looks for a prop
following the convention 'on[EventName]': `trigger('click')` calls `props.onClick()`, and rerenders the root collection
following the convention 'on[EventName]': `trigger('click')` calls `props.onClick()`, and re-renders the root collection

Events don't bubble and don't have a proper event object.

Expand Down
37 changes: 29 additions & 8 deletions src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,6 @@ let common = {
return this.first()
},

single(selector) {
return selector
? this.find(selector).only()
: this.only()
},

unwrap() {
return this.single()[0]
},
Expand All @@ -171,10 +165,37 @@ function unwrap(arr){
return arr && arr.length === 1 ? arr[0] : arr
}

let asserts = {
none: [
c => c.length === 0,
c => `The query found ${c.length}, but expected to find zero nodes.`
],
any: [
c => c.length !== 0,
c => `The query found ${c.length}, but expected to find 1 or more nodes.`
],
single: [
c => c.length === 1,
c => `The query found: ${c.length} items not 1`
]
}

Object.keys(asserts).forEach(name => {
let [ test, msg ] = asserts[name];

common[name] = function (selector) {
if (selector)
return this.find(selector)[name]()

invariant(test(this), msg(this))
return this
}
})

// return values
;['every', 'some']
.forEach(method => {
let fn = [][method];
let fn = Array.prototype[method];

common[method] = function (...args) {
return fn.apply(this, args)
Expand All @@ -184,7 +205,7 @@ function unwrap(arr){
// return collections
;['map', 'reduce', 'reduceRight']
.forEach(method => {
let fn = [][method];
let fn = Array.prototype[method];

common[method] = function (...args) {
return $(this, fn.apply(this, args))
Expand Down
23 changes: 22 additions & 1 deletion test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ describe('common', ()=> {

it('.single()', ()=> {
render(<Example />).single(Stateless).length.should.equal(1)
render(<Example />).find(Stateless).single().length.should.equal(1)
})

it('.single() should throw more than one match is found', ()=> {
Expand All @@ -508,7 +509,27 @@ describe('common', ()=> {

it('.single() should throw when nothing found', ()=> {
;(()=> render(<Example />)
.single('article')).should.throw()
.single('article')).should.throw(/found: 0.+not 1/)
})

it('.any()', ()=> {
render(<Example />).any(Stateless).length.should.equal(1)
render(<Example />).find('ul > li').any().length.should.equal(3)
})

it('.any() should throw if no match is found', ()=> {
;(()=> render(<Example />)
.any('article')).should.throw(/found 0.+expected to find 1 or more/)
})

it('.none()', ()=> {
render(<Example />).none('article').length.should.equal(0)
render(<Example />).find('article').none().length.should.equal(0)
})

it('.none() should throw if a match is found', ()=> {
;(()=> render(<Example />)
.none('li')).should.throw(/found 3.+expected to find zero/)
})

it('.unwrap()', ()=> {
Expand Down

0 comments on commit a2a5b48

Please sign in to comment.