Skip to content

Commit 74590b2

Browse files
committed
custom filter API + exp parser unit test improvements
1 parent 607e197 commit 74590b2

File tree

5 files changed

+163
-72
lines changed

5 files changed

+163
-72
lines changed

src/directive.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ DirProto.applyFilters = function (value) {
157157
var filtered = value, filter
158158
for (var i = 0, l = this.filters.length; i < l; i++) {
159159
filter = this.filters[i]
160-
filtered = filter.apply.call(this.vm, filtered, filter.args)
160+
filtered = filter.apply.apply(this.vm, [filtered].concat(filter.args))
161161
}
162162
return filtered
163163
}

src/exp-parser.js

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ function getVariables (code) {
5353
* key. It then creates any missing bindings on the
5454
* final resolved vm.
5555
*/
56-
function getRel (path, compiler, data) {
56+
function traceScope (path, compiler, data) {
5757
var rel = '',
5858
dist = 0,
5959
self = compiler
@@ -126,17 +126,6 @@ exports.parse = function (exp, compiler, data, filters) {
126126
}
127127
vars = utils.unique(vars)
128128

129-
if (filters) {
130-
filters.forEach(function (filter) {
131-
var args = filter.args
132-
? ',[' + filter.args.map(function (arg) {
133-
return '"' + arg + '"'
134-
}).join(',') + ']'
135-
: ''
136-
exp = 'this.$compiler.getOption("filters", "' + filter.name + '").call(this,' + exp + args + ')'
137-
})
138-
}
139-
140129
var accessors = '',
141130
has = utils.hash(),
142131
strings = [],
@@ -148,11 +137,27 @@ exports.parse = function (exp, compiler, data, filters) {
148137
vars.map(escapeDollar).join('|') +
149138
")[$\\w\\.]*\\b", 'g'
150139
),
151-
body = ('return ' + exp)
140+
body = (' ' + exp)
152141
.replace(stringSaveRE, saveStrings)
153142
.replace(pathRE, replacePath)
154143
.replace(stringRestoreRE, restoreStrings)
155-
body = accessors + body
144+
145+
// wrap expression with computed filters
146+
if (filters) {
147+
filters.forEach(function (filter) {
148+
var args = filter.args
149+
? ',"' + filter.args.join('","') + '"'
150+
: ''
151+
body =
152+
'this.$compiler.getOption("filters", "' +
153+
filter.name +
154+
'").call(this,' +
155+
body + args +
156+
')'
157+
})
158+
}
159+
160+
body = accessors + 'return ' + body
156161

157162
function saveStrings (str) {
158163
var i = strings.length
@@ -164,7 +169,7 @@ exports.parse = function (exp, compiler, data, filters) {
164169
// keep track of the first char
165170
var c = path.charAt(0)
166171
path = path.slice(1)
167-
var val = 'this.' + getRel(path, compiler, data) + path
172+
var val = 'this.' + traceScope(path, compiler, data) + path
168173
if (!has[path]) {
169174
accessors += val + ';'
170175
has[path] = 1

src/filters.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
var keyCodes = {
2-
enter : 13,
3-
tab : 9,
4-
'delete' : 46,
5-
up : 38,
6-
left : 37,
7-
right : 39,
8-
down : 40,
9-
esc : 27
10-
}
2+
enter : 13,
3+
tab : 9,
4+
'delete' : 46,
5+
up : 38,
6+
left : 37,
7+
right : 39,
8+
down : 40,
9+
esc : 27
10+
},
11+
slice = [].slice
1112

1213
module.exports = {
1314

@@ -41,10 +42,10 @@ module.exports = {
4142
/**
4243
* 12345 => $12,345.00
4344
*/
44-
currency: function (value, args) {
45+
currency: function (value, sign) {
4546
if (!value && value !== 0) return ''
46-
var sign = (args && args[0]) || '$',
47-
s = Math.floor(value).toString(),
47+
sign = sign || '$'
48+
var s = Math.floor(value).toString(),
4849
i = s.length % 3,
4950
h = i > 0 ? (s.slice(0, i) + (s.length > 3 ? ',' : '')) : '',
5051
f = '.' + value.toFixed(2).slice(-2)
@@ -60,7 +61,8 @@ module.exports = {
6061
*
6162
* e.g. ['single', 'double', 'triple', 'multiple']
6263
*/
63-
pluralize: function (value, args) {
64+
pluralize: function (value) {
65+
var args = slice.call(arguments, 1)
6466
return args.length > 1
6567
? (args[value - 1] || args[args.length - 1])
6668
: (args[value - 1] || args[0] + 's')
@@ -70,11 +72,11 @@ module.exports = {
7072
* A special filter that takes a handler function,
7173
* wraps it so it only gets triggered on specific keypresses.
7274
*/
73-
key: function (handler, args) {
75+
key: function (handler, key) {
7476
if (!handler) return
75-
var code = keyCodes[args[0]]
77+
var code = keyCodes[key]
7678
if (!code) {
77-
code = parseInt(args[0], 10)
79+
code = parseInt(key, 10)
7880
}
7981
return function (e) {
8082
if (e.keyCode === code) {

test/unit/specs/exp-parser.js

Lines changed: 112 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -87,31 +87,6 @@ describe('Expression Parser', function () {
8787

8888
testCases.forEach(describeCase)
8989

90-
// extra case for invalid expressions
91-
describe('invalid expression', function () {
92-
93-
before(warnSpy.swapWarn)
94-
95-
it('should capture the error and warn', function () {
96-
function noop () {}
97-
ExpParser.parse('a + "fsef', {
98-
createBinding: noop,
99-
hasKey: noop,
100-
vm: {
101-
$compiler: {
102-
bindings: {},
103-
createBinding: noop
104-
},
105-
$data: {}
106-
}
107-
})
108-
assert.ok(warnSpy.warned)
109-
})
110-
111-
after(warnSpy.resetWarn)
112-
113-
})
114-
11590
describe('XSS protection', function () {
11691

11792
var cases = [
@@ -141,6 +116,98 @@ describe('Expression Parser', function () {
141116

142117
})
143118

119+
describe('scope trace', function () {
120+
121+
it('should determine the correct scope for variables', function () {
122+
123+
var bindingsCreated = {}
124+
125+
var getter = ExpParser.parse('a + b', mockCompiler({
126+
parent: {
127+
bindings: {},
128+
createBinding: function (key) {
129+
assert.strictEqual(key, 'a')
130+
bindingsCreated[key] = true
131+
},
132+
hasKey: function (key) {
133+
return key === 'a'
134+
},
135+
parent: {
136+
bindings: {},
137+
createBinding: function (key) {
138+
assert.strictEqual(key, 'b')
139+
bindingsCreated[key] = true
140+
},
141+
hasKey: function (key) {
142+
return key === 'b'
143+
}
144+
}
145+
}
146+
}))
147+
var getterString = getter.toString()
148+
assert.ok(getterString.indexOf('this.$parent.a') > -1)
149+
assert.ok(getterString.indexOf('this.$parent.$parent.b') > -1)
150+
})
151+
152+
})
153+
154+
// extra case for invalid expressions
155+
describe('invalid expression', function () {
156+
157+
before(warnSpy.swapWarn)
158+
159+
it('should capture the error and warn', function () {
160+
ExpParser.parse('a + "fsef', mockCompiler())
161+
assert.ok(warnSpy.warned)
162+
})
163+
164+
after(warnSpy.resetWarn)
165+
166+
})
167+
168+
describe('.eval() with extra data', function () {
169+
170+
it('should be able to eval an epxression with temporary additional data', function () {
171+
var res = ExpParser.eval('a + b', mockCompiler(), { a: 1, b: 2 })
172+
assert.strictEqual(res, 3)
173+
})
174+
175+
})
176+
177+
describe('computed filters', function () {
178+
179+
it('should wrap expression with computed filters', function () {
180+
181+
var filters = [
182+
{ name: 'test', args: ['a', 'b'] },
183+
{ name: 'wrap', args: ['c', 'd'] }
184+
],
185+
filterFns = {
186+
test: function (v, a, b) {
187+
return v + a + b
188+
},
189+
wrap: function (v, c, d) {
190+
return v + c + d
191+
}
192+
}
193+
194+
var compiler = mockCompiler({
195+
getOption: function (type, id) {
196+
return filterFns[id]
197+
}
198+
})
199+
200+
var getter = ExpParser.parse('a + b', compiler, null, filters)
201+
var res = getter.call({
202+
$compiler: compiler,
203+
a: 1,
204+
b: 2
205+
})
206+
assert.strictEqual(res, '3abcd')
207+
})
208+
209+
})
210+
144211
function describeCase (testCase) {
145212
describe(testCase.exp, function () {
146213

@@ -196,4 +263,24 @@ describe('Expression Parser', function () {
196263
})
197264
}
198265

266+
function noop () {}
267+
268+
function mockCompiler (opts) {
269+
var mock = {
270+
createBinding: noop,
271+
hasKey: noop,
272+
vm: {
273+
$compiler: {
274+
bindings: {},
275+
createBinding: noop
276+
},
277+
$data: {}
278+
}
279+
}
280+
for (var key in opts) {
281+
mock[key] = opts[key]
282+
}
283+
return mock
284+
}
285+
199286
})

test/unit/specs/filters.js

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,20 @@ describe('Filters', function () {
4747
var filter = filters.pluralize
4848

4949
it('should simply add "s" if arg length is 1', function () {
50-
var args = ['item'],
51-
res0 = filter(0, args),
52-
res1 = filter(1, args),
53-
res2 = filter(2, args)
50+
var arg = 'item',
51+
res0 = filter(0, arg),
52+
res1 = filter(1, arg),
53+
res2 = filter(2, arg)
5454
assert.strictEqual(res0, 'items')
5555
assert.strictEqual(res1, 'item')
5656
assert.strictEqual(res2, 'items')
5757
})
5858

5959
it('should use corresponding format when arg length is greater than 1', function () {
60-
var args = ['st', 'nd', 'rd'],
61-
res0 = filter(0, args),
62-
res1 = filter(1, args),
63-
res2 = filter(2, args),
64-
res3 = filter(3, args)
60+
var res0 = filter(0, 'st', 'nd', 'rd'),
61+
res1 = filter(1, 'st', 'nd', 'rd'),
62+
res2 = filter(2, 'st', 'nd', 'rd'),
63+
res3 = filter(3, 'st', 'nd', 'rd')
6564
assert.strictEqual(res0, 'rd')
6665
assert.strictEqual(res1, 'st')
6766
assert.strictEqual(res2, 'nd')
@@ -106,23 +105,21 @@ describe('Filters', function () {
106105
var filter = filters.key
107106

108107
it('should return a function that only triggers when key matches', function () {
109-
var args = ['enter'],
110-
triggered = false,
108+
var triggered = false,
111109
handler = filter(function () {
112110
triggered = true
113-
}, args)
111+
}, 'enter')
114112
handler({ keyCode: 0 })
115113
assert.notOk(triggered)
116114
handler({ keyCode: 13 })
117115
assert.ok(triggered)
118116
})
119117

120118
it('should also work for direct keyCode', function () {
121-
var args = [13],
122-
triggered = false,
119+
var triggered = false,
123120
handler = filter(function () {
124121
triggered = true
125-
}, args)
122+
}, 13)
126123
handler({ keyCode: 0 })
127124
assert.notOk(triggered)
128125
handler({ keyCode: 13 })

0 commit comments

Comments
 (0)