PageRenderTime 135ms CodeModel.GetById 60ms app.highlight 69ms RepoModel.GetById 1ms app.codeStats 1ms

/test/comprehensions.coffee

http://github.com/jashkenas/coffee-script
CoffeeScript | 620 lines | 515 code | 92 blank | 13 comment | 55 complexity | 2272f85e3db37b48190e990de8168ec4 MD5 | raw file
  1# Comprehensions
  2# --------------
  3
  4# * Array Comprehensions
  5# * Range Comprehensions
  6# * Object Comprehensions
  7# * Implicit Destructuring Assignment
  8# * Comprehensions with Nonstandard Step
  9
 10# TODO: refactor comprehension tests
 11
 12test "Basic array comprehensions.", ->
 13
 14  nums    = (n * n for n in [1, 2, 3] when n & 1)
 15  results = (n * 2 for n in nums)
 16
 17  ok results.join(',') is '2,18'
 18
 19
 20test "Basic object comprehensions.", ->
 21
 22  obj   = {one: 1, two: 2, three: 3}
 23  names = (prop + '!' for prop of obj)
 24  odds  = (prop + '!' for prop, value of obj when value & 1)
 25
 26  ok names.join(' ') is "one! two! three!"
 27  ok odds.join(' ')  is "one! three!"
 28
 29
 30test "Basic range comprehensions.", ->
 31
 32  nums = (i * 3 for i in [1..3])
 33
 34  negs = (x for x in [-20..-5*2])
 35  negs = negs[0..2]
 36
 37  result = nums.concat(negs).join(', ')
 38
 39  ok result is '3, 6, 9, -20, -19, -18'
 40
 41
 42test "With range comprehensions, you can loop in steps.", ->
 43
 44  results = (x for x in [0...15] by 5)
 45  ok results.join(' ') is '0 5 10'
 46
 47  results = (x for x in [0..100] by 10)
 48  ok results.join(' ') is '0 10 20 30 40 50 60 70 80 90 100'
 49
 50
 51test "And can loop downwards, with a negative step.", ->
 52
 53  results = (x for x in [5..1])
 54
 55  ok results.join(' ') is '5 4 3 2 1'
 56  ok results.join(' ') is [(10-5)..(-2+3)].join(' ')
 57
 58  results = (x for x in [10..1])
 59  ok results.join(' ') is [10..1].join(' ')
 60
 61  results = (x for x in [10...0] by -2)
 62  ok results.join(' ') is [10, 8, 6, 4, 2].join(' ')
 63
 64
 65test "Range comprehension gymnastics.", ->
 66
 67  eq "#{i for i in [5..1]}", '5,4,3,2,1'
 68  eq "#{i for i in [5..-5] by -5}", '5,0,-5'
 69
 70  a = 6
 71  b = 0
 72  c = -2
 73
 74  eq "#{i for i in [a..b]}", '6,5,4,3,2,1,0'
 75  eq "#{i for i in [a..b] by c}", '6,4,2,0'
 76
 77
 78test "Multiline array comprehension with filter.", ->
 79
 80  evens = for num in [1, 2, 3, 4, 5, 6] when not (num & 1)
 81             num *= -1
 82             num -= 2
 83             num * -1
 84  eq evens + '', '4,6,8'
 85
 86
 87  test "The in operator still works, standalone.", ->
 88
 89    ok 2 of evens
 90
 91
 92test "all isn't reserved.", ->
 93
 94  all = 1
 95
 96
 97test "Ensure that the closure wrapper preserves local variables.", ->
 98
 99  obj = {}
100
101  for method in ['one', 'two', 'three'] then do (method) ->
102    obj[method] = ->
103      "I'm " + method
104
105  ok obj.one()   is "I'm one"
106  ok obj.two()   is "I'm two"
107  ok obj.three() is "I'm three"
108
109
110test "Index values at the end of a loop.", ->
111
112  i = 0
113  for i in [1..3]
114    -> 'func'
115    break if false
116  ok i is 4
117
118
119test "Ensure that local variables are closed over for range comprehensions.", ->
120
121  funcs = for i in [1..3]
122    do (i) ->
123      -> -i
124
125  eq (func() for func in funcs).join(' '), '-1 -2 -3'
126  ok i is 4
127
128
129test "Even when referenced in the filter.", ->
130
131  list = ['one', 'two', 'three']
132
133  methods = for num, i in list when num isnt 'two' and i isnt 1
134    do (num, i) ->
135      -> num + ' ' + i
136
137  ok methods.length is 2
138  ok methods[0]() is 'one 0'
139  ok methods[1]() is 'three 2'
140
141
142test "Even a convoluted one.", ->
143
144  funcs = []
145
146  for i in [1..3]
147    do (i) ->
148      x = i * 2
149      ((z)->
150        funcs.push -> z + ' ' + i
151      )(x)
152
153  ok (func() for func in funcs).join(', ') is '2 1, 4 2, 6 3'
154
155  funcs = []
156
157  results = for i in [1..3]
158    do (i) ->
159      z = (x * 3 for x in [1..i])
160      ((a, b, c) -> [a, b, c].join(' ')).apply this, z
161
162  ok results.join(', ') is '3  , 3 6 , 3 6 9'
163
164
165test "Naked ranges are expanded into arrays.", ->
166
167  array = [0..10]
168  ok(num % 2 is 0 for num in array by 2)
169
170
171test "Nested shared scopes.", ->
172
173  foo = ->
174    for i in [0..7]
175      do (i) ->
176        for j in [0..7]
177          do (j) ->
178            -> i + j
179
180  eq foo()[3][4](), 7
181
182
183test "Scoped loop pattern matching.", ->
184
185  a = [[0], [1]]
186  funcs = []
187
188  for [v] in a
189    do (v) ->
190      funcs.push -> v
191
192  eq funcs[0](), 0
193  eq funcs[1](), 1
194
195
196test "Nested comprehensions.", ->
197
198  multiLiner =
199    for x in [3..5]
200      for y in [3..5]
201        [x, y]
202
203  singleLiner =
204    (([x, y] for y in [3..5]) for x in [3..5])
205
206  ok multiLiner.length is singleLiner.length
207  ok 5 is multiLiner[2][2][1]
208  ok 5 is singleLiner[2][2][1]
209
210
211test "Comprehensions within parentheses.", ->
212
213  result = null
214  store = (obj) -> result = obj
215  store (x * 2 for x in [3, 2, 1])
216
217  ok result.join(' ') is '6 4 2'
218
219
220test "Closure-wrapped comprehensions that refer to the 'arguments' object.", ->
221
222  expr = ->
223    result = (item * item for item in arguments)
224
225  ok expr(2, 4, 8).join(' ') is '4 16 64'
226
227
228test "Fast object comprehensions over all properties, including prototypal ones.", ->
229
230  class Cat
231    constructor: -> @name = 'Whiskers'
232    breed: 'tabby'
233    hair:  'cream'
234
235  whiskers = new Cat
236  own = (value for own key, value of whiskers)
237  all = (value for key, value of whiskers)
238
239  ok own.join(' ') is 'Whiskers'
240  ok all.sort().join(' ') is 'Whiskers cream tabby'
241
242
243test "Optimized range comprehensions.", ->
244
245  exxes = ('x' for [0...10])
246  ok exxes.join(' ') is 'x x x x x x x x x x'
247
248
249test "#3671: Allow step in optimized range comprehensions.", ->
250
251  exxes = ('x' for [0...10] by 2)
252  eq exxes.join(' ') , 'x x x x x'
253
254
255test "#3671: Disallow guard in optimized range comprehensions.", ->
256
257  throwsCompileError "exxes = ('x' for [0...10] when a)"
258
259
260test "Loop variables should be able to reference outer variables", ->
261  outer = 1
262  do ->
263    null for outer in [1, 2, 3]
264  eq outer, 3
265
266
267test "Lenient on pure statements not trying to reach out of the closure", ->
268
269  val = for i in [1]
270    for j in [] then break
271    i
272  ok val[0] is i
273
274
275test "Comprehensions only wrap their last line in a closure, allowing other lines
276  to have pure expressions in them.", ->
277
278  func = -> for i in [1]
279    break if i is 2
280    j for j in [1]
281
282  ok func()[0][0] is 1
283
284  i = 6
285  odds = while i--
286    continue unless i & 1
287    i
288
289  ok odds.join(', ') is '5, 3, 1'
290
291
292test "Issue #897: Ensure that plucked function variables aren't leaked.", ->
293
294  facets = {}
295  list = ['one', 'two']
296
297  (->
298    for entity in list
299      facets[entity] = -> entity
300  )()
301
302  eq typeof entity, 'undefined'
303  eq facets['two'](), 'two'
304
305
306test "Issue #905. Soaks as the for loop subject.", ->
307
308  a = {b: {c: [1, 2, 3]}}
309  for d in a.b?.c
310    e = d
311
312  eq e, 3
313
314
315test "Issue #948. Capturing loop variables.", ->
316
317  funcs = []
318  list  = ->
319    [1, 2, 3]
320
321  for y in list()
322    do (y) ->
323      z = y
324      funcs.push -> "y is #{y} and z is #{z}"
325
326  eq funcs[1](), "y is 2 and z is 2"
327
328
329test "Cancel the comprehension if there's a jump inside the loop.", ->
330
331  result = try
332    for i in [0...10]
333      continue if i < 5
334    i
335
336  eq result, 10
337
338
339test "Comprehensions over break.", ->
340
341  arrayEq (break for [1..10]), []
342
343
344test "Comprehensions over continue.", ->
345
346  arrayEq (continue for [1..10]), []
347
348
349test "Comprehensions over function literals.", ->
350
351  a = 0
352  for f in [-> a = 1]
353    do (f) ->
354      do f
355
356  eq a, 1
357
358
359test "Comprehensions that mention arguments.", ->
360
361  list = [arguments: 10]
362  args = for f in list
363    do (f) ->
364      f.arguments
365  eq args[0], 10
366
367
368test "expression conversion under explicit returns", ->
369  nonce = {}
370  fn = ->
371    return (nonce for x in [1,2,3])
372  arrayEq [nonce,nonce,nonce], fn()
373  fn = ->
374    return [nonce for x in [1,2,3]][0]
375  arrayEq [nonce,nonce,nonce], fn()
376  fn = ->
377    return [(nonce for x in [1..3])][0]
378  arrayEq [nonce,nonce,nonce], fn()
379
380
381test "implicit destructuring assignment in object of objects", ->
382  a={}; b={}; c={}
383  obj = {
384    a: { d: a },
385    b: { d: b }
386    c: { d: c }
387  }
388  result = ([y,z] for y, { d: z } of obj)
389  arrayEq [['a',a],['b',b],['c',c]], result
390
391
392test "implicit destructuring assignment in array of objects", ->
393  a={}; b={}; c={}; d={}; e={}; f={}
394  arr = [
395    { a: a, b: { c: b } },
396    { a: c, b: { c: d } },
397    { a: e, b: { c: f } }
398  ]
399  result = ([y,z] for { a: y, b: { c: z } } in arr)
400  arrayEq [[a,b],[c,d],[e,f]], result
401
402
403test "implicit destructuring assignment in array of arrays", ->
404  a={}; b={}; c={}; d={}; e={}; f={}
405  arr = [[a, [b]], [c, [d]], [e, [f]]]
406  result = ([y,z] for [y, [z]] in arr)
407  arrayEq [[a,b],[c,d],[e,f]], result
408
409test "issue #1124: don't assign a variable in two scopes", ->
410  lista = [1, 2, 3, 4, 5]
411  listb = (_i + 1 for _i in lista)
412  arrayEq [2, 3, 4, 5, 6], listb
413
414test "#1326: `by` value is uncached", ->
415  a = [0,1,2]
416  fi = gi = hi = 0
417  f = -> ++fi
418  g = -> ++gi
419  h = -> ++hi
420
421  forCompile = []
422  rangeCompileSimple = []
423
424  #exercises For.compile
425  for v, i in a by f()
426    forCompile.push i
427
428  #exercises Range.compileSimple
429  rangeCompileSimple = (i for i in [0..2] by g())
430
431  arrayEq a, forCompile
432  arrayEq a, rangeCompileSimple
433  #exercises Range.compile
434  eq "#{i for i in [0..2] by h()}", '0,1,2'
435
436test "#1669: break/continue should skip the result only for that branch", ->
437  ns = for n in [0..99]
438    if n > 9
439      break
440    else if n & 1
441      continue
442    else
443      n
444  eq "#{ns}", '0,2,4,6,8'
445
446  # `else undefined` is implied.
447  ns = for n in [1..9]
448    if n % 2
449      continue unless n % 5
450      n
451  eq "#{ns}", "1,,3,,,7,,9"
452
453  # Ditto.
454  ns = for n in [1..9]
455    switch
456      when n % 2
457        continue unless n % 5
458        n
459  eq "#{ns}", "1,,3,,,7,,9"
460
461test "#1850: inner `for` should not be expression-ized if `return`ing", ->
462  eq '3,4,5', do ->
463    for a in [1..9] then \
464    for b in [1..9]
465      c = Math.sqrt a*a + b*b
466      return String [a, b, c] unless c % 1
467
468test "#1910: loop index should be mutable within a loop iteration and immutable between loop iterations", ->
469  n = 1
470  iterations = 0
471  arr = [0..n]
472  for v, k in arr
473    ++iterations
474    v = k = 5
475    eq 5, k
476  eq 2, k
477  eq 2, iterations
478
479  iterations = 0
480  for v in [0..n]
481    ++iterations
482  eq 2, k
483  eq 2, iterations
484
485  arr = ([v, v + 1] for v in [0..5])
486  iterations = 0
487  for [v0, v1], k in arr when v0
488    k += 3
489    ++iterations
490  eq 6, k
491  eq 5, iterations
492
493test "#2007: Return object literal from comprehension", ->
494  y = for x in [1, 2]
495    foo: "foo" + x
496  eq 2, y.length
497  eq "foo1", y[0].foo
498  eq "foo2", y[1].foo
499
500  x = 2
501  y = while x
502    x: --x
503  eq 2, y.length
504  eq 1, y[0].x
505  eq 0, y[1].x
506
507test "#2274: Allow @values as loop variables", ->
508  obj = {
509    item: null
510    method: ->
511      for @item in [1, 2, 3]
512        null
513  }
514  eq obj.item, null
515  obj.method()
516  eq obj.item, 3
517
518test "#4411: Allow @values as loop indices", ->
519  obj =
520    index: null
521    get: -> @index
522    method: ->
523      @get() for _, @index in [1, 2, 3]
524  eq obj.index, null
525  arrayEq obj.method(), [0, 1, 2]
526  eq obj.index, 3
527
528test "#2525, #1187, #1208, #1758, looping over an array forwards", ->
529  list = [0, 1, 2, 3, 4]
530
531  ident = (x) -> x
532
533  arrayEq (i for i in list), list
534
535  arrayEq (index for i, index in list), list
536
537  arrayEq (i for i in list by 1), list
538
539  arrayEq (i for i in list by ident 1), list
540
541  arrayEq (i for i in list by ident(1) * 2), [0, 2, 4]
542
543  arrayEq (index for i, index in list by ident(1) * 2), [0, 2, 4]
544
545test "#2525, #1187, #1208, #1758, looping over an array backwards", ->
546  list = [0, 1, 2, 3, 4]
547  backwards = [4, 3, 2, 1, 0]
548
549  ident = (x) -> x
550
551  arrayEq (i for i in list by -1), backwards
552
553  arrayEq (index for i, index in list by -1), backwards
554
555  arrayEq (i for i in list by ident -1), backwards
556
557  arrayEq (i for i in list by ident(-1) * 2), [4, 2, 0]
558
559  arrayEq (index for i, index in list by ident(-1) * 2), [4, 2, 0]
560
561test "splats in destructuring in comprehensions", ->
562  list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
563  arrayEq (seq for [rep, seq...] in list), [[1, 2], [3, 4], [5, 6]]
564
565test "#156: expansion in destructuring in comprehensions", ->
566  list = [[0, 1, 2], [2, 3, 4], [4, 5, 6]]
567  arrayEq (last for [..., last] in list), [2, 4, 6]
568
569test "#3778: Consistently always cache for loop range boundaries and steps, even
570      if they are simple identifiers", ->
571  a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by  a then a = 4; n)
572  a = 1; arrayEq [1, 2, 3], (for n in [1, 2, 3] by +a then a = 4; n)
573  a = 1; arrayEq [1, 2, 3], (for n in [a..3]          then a = 4; n)
574  a = 1; arrayEq [1, 2, 3], (for n in [+a..3]         then a = 4; n)
575  a = 3; arrayEq [1, 2, 3], (for n in [1..a]          then a = 4; n)
576  a = 3; arrayEq [1, 2, 3], (for n in [1..+a]         then a = 4; n)
577  a = 1; arrayEq [1, 2, 3], (for n in [1..3] by  a    then a = 4; n)
578  a = 1; arrayEq [1, 2, 3], (for n in [1..3] by +a    then a = 4; n)
579
580test "for pattern variables can linebreak/indent", ->
581  listOfObjects = [a: 1]
582  sum = 0
583  for {
584    a
585    somethingElse
586    anotherProperty
587  } in listOfObjects
588    sum += a
589  eq a, 1
590
591  sum = 0
592  sum += a for {
593    a,
594    somethingElse,
595    anotherProperty,
596  } in listOfObjects
597  eq a, 1
598
599  listOfArrays = [[2]]
600  sum = 0
601  for [
602    a
603    nonexistentElement
604    anotherNonexistentElement
605  ] in listOfArrays
606    sum += a
607  eq a, 2
608
609  sum = 0
610  sum += a for [
611    a,
612    nonexistentElement,
613    anotherNonexistentElement
614  ] in listOfArrays
615  eq a, 2
616
617test "#5309: comprehension as postfix condition", ->
618  doesNotThrowCompileError """
619    throw new Error "DOOM was called with a null element" unless elm? for elm in elms
620  """