/test/comprehensions.coffee
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 """