Skip to content

Commit 7415849

Browse files
committed
feat(filter): Add filter feature on Selector functions and WrapperArray
1 parent fce6e6e commit 7415849

File tree

9 files changed

+76
-24
lines changed

9 files changed

+76
-24
lines changed

flow/wrapper.flow.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,22 @@ import type Wrapper from '~src/Wrapper'
44
import type WrapperArray from '~src/WrapperArray'
55

66
declare type Selector = any
7+
declare type WrapperPredicate = (wrapper: Wrapper, index?: number, array?: Array<Wrapper>) => boolean;
78

89
declare interface BaseWrapper { // eslint-disable-line no-undef
910
at(index: number): Wrapper | void,
1011
attributes(): { [name: string]: string } | void,
1112
classes(): Array<string> | void,
12-
contains(selector: Selector): boolean | void,
13+
contains(selector: Selector, filter?: WrapperPredicate): boolean | void,
1314
emitted(event?: string): { [name: string]: Array<Array<any>> } | Array<Array<any>> | void,
1415
emittedByOrder(): Array<{ name: string; args: Array<any> }> | void,
1516
exists(): boolean,
1617
hasAttribute(attribute: string, value: string): boolean | void,
1718
hasClass(className: string): boolean | void,
1819
hasProp(prop: string, value: string): boolean | void,
1920
hasStyle(style: string, value: string): boolean | void,
20-
find(selector: Selector): Wrapper | void,
21-
findAll(selector: Selector): WrapperArray | void,
21+
find(selector: Selector, filter?: WrapperPredicate): Wrapper | void,
22+
findAll(selector: Selector, filter?: WrapperPredicate): WrapperArray | void,
2223
html(): string | void,
2324
is(selector: Selector): boolean | void,
2425
isEmpty(): boolean | void,

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
],
1111
"scripts": {
1212
"build": "node build/build.js",
13-
"build:test": "NODE_ENV=test node build/build.js",
13+
"build:test": "cross-env NODE_ENV=test node build/build.js",
1414
"coverage": "cross-env NODE_ENV=coverage nyc --reporter=lcov --reporter=text npm run test:unit",
1515
"docs": "cd docs && gitbook install && gitbook serve",
1616
"docs:deploy": "build/update-docs.sh",

src/wrappers/wrapper-array.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ export default class WrapperArray implements BaseWrapper {
2020
return this.wrappers[index]
2121
}
2222

23+
filter (predicate: WrapperPredicate) {
24+
return new WrapperArray(this.wrappers.filter(predicate))
25+
}
26+
2327
attributes (): void {
2428
this.throwErrorIfWrappersIsEmpty('attributes')
2529

src/wrappers/wrapper.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,17 @@ export default class Wrapper implements BaseWrapper {
8080
/**
8181
* Checks if wrapper contains provided selector.
8282
*/
83-
contains (selector: Selector) {
83+
contains (selector: Selector, predicate?: WrapperPredicate) {
8484
const selectorType = getSelectorTypeOrThrow(selector, 'contains')
8585
const nodes = findAll(this.vm, this.vnode, selectorType, selector)
86+
let wrappers = nodes.map(node =>
87+
createWrapper(node, this.update, this.options)
88+
)
89+
if (predicate) {
90+
wrappers = wrappers.filter(predicate)
91+
}
8692
const is = selectorType === REF_SELECTOR ? false : this.is(selector)
87-
return nodes.length > 0 || is
93+
return wrappers.length > 0 || is
8894
}
8995

9096
/**
@@ -220,27 +226,35 @@ export default class Wrapper implements BaseWrapper {
220226
/**
221227
* Finds first node in tree of the current wrapper that matches the provided selector.
222228
*/
223-
find (selector: Selector): Wrapper | ErrorWrapper | VueWrapper {
229+
find (selector: Selector, predicate?: WrapperPredicate): Wrapper | ErrorWrapper | VueWrapper {
224230
const selectorType = getSelectorTypeOrThrow(selector, 'find')
225-
const nodes = findAll(this.vm, this.vnode, selectorType, selector)
226-
if (nodes.length === 0) {
231+
let wrappers = findAll(this.vm, this.vnode, selectorType, selector).map(node =>
232+
createWrapper(node, this.update, this.options)
233+
)
234+
if (predicate) {
235+
wrappers = wrappers.filter(predicate)
236+
}
237+
if (wrappers.length === 0) {
227238
if (selector.ref) {
228239
return new ErrorWrapper(`ref="${selector.ref}"`)
229240
}
230241
return new ErrorWrapper(typeof selector === 'string' ? selector : 'Component')
231242
}
232-
return createWrapper(nodes[0], this.update, this.options)
243+
return wrappers[0]
233244
}
234245

235246
/**
236247
* Finds node in tree of the current wrapper that matches the provided selector.
237248
*/
238-
findAll (selector: Selector): WrapperArray {
249+
findAll (selector: Selector, predicate?: WrapperPredicate): WrapperArray {
239250
const selectorType = getSelectorTypeOrThrow(selector, 'findAll')
240251
const nodes = findAll(this.vm, this.vnode, selectorType, selector)
241-
const wrappers = nodes.map(node =>
252+
let wrappers = nodes.map(node =>
242253
createWrapper(node, this.update, this.options)
243254
)
255+
if (predicate) {
256+
wrappers = wrappers.filter(predicate)
257+
}
244258
return new WrapperArray(wrappers)
245259
}
246260

test/unit/specs/mount/Wrapper/contains.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ describe('contains', () => {
1414
expect(wrapper.contains('input')).to.equal(true)
1515
})
1616

17+
it('returns false if wrapper contains element filtered by predicate', () => {
18+
const compiled = compileToFunctions('<div><input class="filtered" /></div>')
19+
const wrapper = mount(compiled)
20+
expect(wrapper.contains('input', w => !w.hasClass('filtered'))).to.equal(false)
21+
})
22+
1723
it('returns true if wrapper contains Vue component', () => {
1824
const wrapper = mount(ComponentWithChild)
1925
expect(wrapper.contains(Component)).to.equal(true)

test/unit/specs/mount/Wrapper/find.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ describe('find', () => {
2323
expect(wrapper.find('.foo').vnode).to.be.an('object')
2424
})
2525

26+
it('returns first Wrapper matching class selector and filter predicate passed', () => {
27+
const compiled = compileToFunctions('<div><div class="foo fooFilter">filtered</div><div class="foo">not filtered</div></div>')
28+
const wrapper = mount(compiled)
29+
expect(wrapper.find('.foo', w => !w.hasClass('fooFilter')).text()).to.be.equals('not filtered')
30+
})
31+
2632
it('returns Wrapper matching class selector passed if nested in a transition', () => {
2733
const compiled = compileToFunctions('<transition><div /></transition>')
2834
const wrapper = mount(compiled)

test/unit/specs/mount/Wrapper/findAll.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ describe('findAll', () => {
2525
expect(fooArr.length).to.equal(1)
2626
})
2727

28+
it('returns an array of Wrapper of elements matching class selector passed and filter predicate', () => {
29+
const compiled = compileToFunctions('<div><div class="foo fooFiltered">filtered</div><div class="foo">not filtered</div></div>')
30+
const wrapper = mount(compiled)
31+
const fooArr = wrapper.findAll('.foo', (wrapper) => !wrapper.hasClass('fooFiltered'))
32+
expect(fooArr.length).to.equal(1)
33+
expect(fooArr.at(0).text()).to.equal('not filtered')
34+
})
35+
2836
it('returns an array of Wrapper of elements matching class selector passed if they are nested in a transition', () => {
2937
const compiled = compileToFunctions('<transition><div /></transition>')
3038
const wrapper = mount(compiled)

test/unit/specs/wrappers/wrapper-array.spec.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ describe('WrapperArray', () => {
1414
return wrapperArray
1515
}
1616

17-
it('returns class with length equal to lenght of wrappers passed in constructor', () => {
17+
it('returns class with length equal to length of wrappers passed in constructor', () => {
1818
const wrapperArray = getWrapperArray()
1919
expect(wrapperArray.length).to.equal(3)
2020
})
@@ -24,6 +24,13 @@ describe('WrapperArray', () => {
2424
expect(wrapperArray.at(0).text()).to.equal('1')
2525
})
2626

27+
it('returns filtered wrapper when filter is called', () => {
28+
const wrapperArray = getWrapperArray()
29+
expect(wrapperArray.filter(w => {
30+
return w.text() !== '2'
31+
}).length).to.equal(2)
32+
})
33+
2734
const methods = ['at', 'attributes', 'classes', 'contains', 'emitted', 'emittedByOrder', 'hasAttribute',
2835
'hasClass', 'hasProp', 'hasStyle', 'find', 'findAll', 'html', 'text', 'is', 'isEmpty', 'isVueInstance',
2936
'name', 'props', 'setComputed', 'setMethods', 'setData', 'setProps', 'trigger', 'update', 'destroy']

types/index.d.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ type VueClass<V extends Vue> = (new (...args: any[]) => V) & typeof Vue
1616
*/
1717
type Selector = string | Component
1818

19+
/**
20+
* Utility type for wrapper predicate
21+
*/
22+
declare type WrapperPredicate<V extends Vue> = (wrapper: Wrapper<V>, index?: number, array?: Array<Wrapper<V>>) => boolean;
23+
1924
/**
2025
* Utility type for slots
2126
*/
@@ -43,7 +48,7 @@ type RefSelector = {
4348
* It has common methods on both Wrapper and WrapperArray
4449
*/
4550
interface BaseWrapper {
46-
contains (selector: Selector): boolean
51+
contains (selector: Selector, predicate?: WrapperPredicate<Vue>): boolean
4752
exists (): boolean
4853

4954
attributes(): { [name: string]: string } | void
@@ -73,17 +78,17 @@ interface Wrapper<V extends Vue> extends BaseWrapper {
7378
readonly element: HTMLElement
7479
readonly options: WrapperOptions
7580

76-
find<R extends Vue> (selector: VueClass<R>): Wrapper<R>
77-
find<R extends Vue> (selector: ComponentOptions<R>): Wrapper<R>
78-
find (selector: FunctionalComponentOptions): Wrapper<Vue>
79-
find (selector: string): Wrapper<Vue>
80-
find (selector: RefSelector): Wrapper<Vue>
81+
find<R extends Vue> (selector: VueClass<R>, predicate?: WrapperPredicate<R>): Wrapper<R>
82+
find<R extends Vue> (selector: ComponentOptions<R>, predicate?: WrapperPredicate<R>): Wrapper<R>
83+
find (selector: FunctionalComponentOptions, predicate?: WrapperPredicate<Vue>): Wrapper<Vue>
84+
find (selector: string, predicate?: WrapperPredicate<Vue>): Wrapper<Vue>
85+
find (selector: RefSelector, predicate?: WrapperPredicate<Vue>): Wrapper<Vue>
8186

82-
findAll<R extends Vue> (selector: VueClass<R>): WrapperArray<R>
83-
findAll<R extends Vue> (selector: ComponentOptions<R>): WrapperArray<R>
84-
findAll (selector: FunctionalComponentOptions): WrapperArray<Vue>
85-
findAll (selector: string): WrapperArray<Vue>
86-
findAll (selector: RefSelector): WrapperArray<Vue>
87+
findAll<R extends Vue> (selector: VueClass<R>, predicate?: WrapperPredicate<R>): WrapperArray<R>
88+
findAll<R extends Vue> (selector: ComponentOptions<R>, predicate?: WrapperPredicate<R>): WrapperArray<R>
89+
findAll (selector: FunctionalComponentOptions, predicate?: WrapperPredicate<Vue>): WrapperArray<Vue>
90+
findAll (selector: string, predicate?: WrapperPredicate<Vue>): WrapperArray<Vue>
91+
findAll (selector: RefSelector, predicate?: WrapperPredicate<Vue>): WrapperArray<Vue>
8792

8893
html (): string
8994
text (): string
@@ -98,6 +103,7 @@ interface WrapperArray<V extends Vue> extends BaseWrapper {
98103
readonly wrappers: Array<Wrapper<V>>
99104

100105
at (index: number): Wrapper<V>
106+
filter (predicate: WrapperPredicate<V>): WrapperArray<V>
101107
}
102108

103109
interface WrapperOptions {

0 commit comments

Comments
 (0)