/**
 * Copyright (c) 2014, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * https://raw.github.com/facebook/regenerator/master/LICENSE file. An
 * additional grant of patent rights can be found in the PATENTS file in
 * the same directory.
 */

var assert = require("assert");
var runningInTranslation = /\.wrap\(/.test(function*(){});
var iteratorSymbol = typeof Symbol === "function"
  && Symbol.iterator
  || "@@iterator";

function check(g, yields, returnValue) {
  for (var i = 0; i < yields.length; ++i) {
    var info = g.next(i);
    assert.deepEqual(info.value, yields[i]);
    assert.strictEqual(info.done, false);
  }

  assert.deepEqual(
    i > 0 ? g.next(i) : g.next(),
    { value: returnValue, done: true }
  );
}

// A version of `throw` whose behavior can't be statically analyzed.
// Useful for testing dynamic exception dispatching.
function raise(argument) {
  throw argument;
}

function assertAlreadyFinished(generator) {
  assert.deepEqual(generator.next(), {
    value: void 0,
    done: true
  });
}

describe("regeneratorRuntime", function() {
  it("should be defined globally", function() {
    var global = Function("return this")();
    assert.ok("regeneratorRuntime" in global);
    assert.strictEqual(global.regeneratorRuntime, regeneratorRuntime);
  });

  it("should have a .wrap method", function() {
    assert.strictEqual(typeof regeneratorRuntime.wrap, "function");
  });

  it("should have a .mark method", function() {
    assert.strictEqual(typeof regeneratorRuntime.mark, "function");
  });

  it("should be the object name returned by util.runtimeProperty", function() {
    assert.strictEqual(
      require("../lib/util").runtimeProperty("foo").object.name,
      "regeneratorRuntime"
    );
  });
});

(runningInTranslation ? describe : xdescribe)("@@iterator", function() {
  it("is defined on Generator.prototype and returns this", function() {
    function *gen(){}
    var iterator = gen();
    assert.ok(!iterator.hasOwnProperty(iteratorSymbol));
    assert.ok(!Object.getPrototypeOf(iterator).hasOwnProperty(iteratorSymbol));
    assert.ok(Object.getPrototypeOf(
      Object.getPrototypeOf(iterator)
    ).hasOwnProperty(iteratorSymbol));
    assert.strictEqual(iterator[iteratorSymbol](), iterator);
  });
});

describe("simple argument yielder", function() {
  it("should yield only its first argument", function() {
    function *gen(x) {
      yield x;
    }

    check(gen("oyez"), ["oyez"]);
    check(gen("foo", "bar"), ["foo"]);
  });

  it("should support multiple yields in expression", function() {
    function *gen() { return (yield 0) + (yield 0); }
    var itr = gen();
    itr.next();
    itr.next(1);
    assert.equal(itr.next(2).value, 3);
  });
});

function *range(n) {
  for (var i = 0; i < n; ++i) {
    yield i;
  }
}

describe("range generator", function() {
  it("should yield the empty range", function() {
    check(range(0), []);
  })

  it("should yield the range 0..n-1", function() {
    check(range(5), [0, 1, 2, 3, 4]);
  });
});

describe("collatz generator", function() {
  function *gen(n) {
    var count = 0;

    yield n;

    while (n !== 1) {
      count += 1;

      if (n % 2) {
        yield n = n * 3 + 1;
      } else {
        yield n >>= 1;
      }
    }

    return count;
  }

  function collatz(n) {
    var result = [n];

    while (n !== 1) {
      if (n % 2) {
        n *= 3;
        n += 1;
      } else {
        n >>= 1;
      }

      result.push(n);
    }

    return result;
  }

  var seven = collatz(7);
  var fiftyTwo = seven.slice(seven.indexOf(52));
  var eightyTwo = collatz(82);

  it("seven", function() {
    check(gen(7), seven, 16);
  });

  it("fifty two", function() {
    check(gen(52), fiftyTwo, 11);
  });

  it("eighty two", function() {
    check(gen(82), eightyTwo, 110);
  });
});

describe("throw", function() {
  (runningInTranslation ? it : xit)("should complete generator", function() {
    function *gen(x) {
      throw 1;
    }

    var u = gen();

    try {
      u.next();
    } catch (err) {
      assert.strictEqual(err, 1);
    }

    assertAlreadyFinished(u);
  });
});

describe("try-catch generator", function() {
  function *usingThrow(x) {
    yield 0;
    try {
      yield 1;
      if (x % 2 === 0)
        throw 2;
      yield x;
    } catch (x) {
      yield x;
    }
    yield 3;
  }

  function *usingRaise(x) {
    yield 0;
    try {
      yield 1;
      if (x % 2 === 0)
        raise(2);
      yield x;
    } catch (x) {
      yield x;
    }
    yield 3;
  }

  it("should catch static exceptions properly", function() {
    check(usingThrow(4), [0, 1, 2, 3]);
    check(usingThrow(5), [0, 1, 5, 3]);
  });

  it("should catch dynamic exceptions properly", function() {
    check(usingRaise(4), [0, 1, 2, 3]);
    check(usingRaise(5), [0, 1, 5, 3]);
  });
});

describe("nested generators in try-catch", function() {
  function *gen() {
    try {
       nonExistent;
    } catch (e) {
      yield function* () {
        yield e;
      }
    }
  }

  it('should get a reference to the caught error', function () {
    var genFun2 = gen().next().value;
    assert.ok(regeneratorRuntime.isGeneratorFunction(genFun2));
    var gen2 = genFun2();
    var res = gen2.next();
    assert.ok(res.value instanceof ReferenceError);
    // Note that we don't do strict equality over the message because it varies
    // across browsers (if we ever want to run tests in browsers).
    assert.ok(res.value.message.match(/nonExistent/));
  });

});

describe("try-finally generator", function() {
  function *usingThrow(condition) {
    yield 0;
    try {
      yield 1;
      throw 2;
      yield 3;
    } finally {
      if (condition) {
        yield 4;
        return 5;
      }
      yield 6;
      return 7;
    }
  }

  function *usingRaise(condition) {
    yield 0;
    try {
      yield 1;
      raise(2);
      yield 3;
    } finally {
      if (condition) {
        yield 4;
        return 5;
      }
      yield 6;
      return 7;
    }
  }

  function *usingAbrupt(abruptType, finallyAbruptType) {
    yield 0;
    for (;;) {
      try {
        yield 1;
        if (abruptType === "return") {
          return 2;
        } else if (abruptType === "break") {
          break;
        } else if (abruptType === "continue") {
          abruptType = "return";
          continue;
        }
      }
      finally {
        yield 3;
        if (finallyAbruptType === "return") {
          return 4;
        } else if (finallyAbruptType === "break") {
          break;
        } else if (finallyAbruptType === "continue") {
          finallyAbruptType = null;
          continue;
        }
      }
    }
    return 5;
  }

  it("should honor return", function() {
    check(usingAbrupt("return", null), [0, 1, 3], 2);
  });

  it("should honor break", function() {
    check(usingAbrupt("break", null), [0, 1, 3], 5);
  });

  it("should honor continue", function() {
    check(usingAbrupt("continue", null), [0, 1, 3, 1, 3], 2);
  });

  it("should override abrupt with return", function() {
    check(usingAbrupt("return", "return"), [0, 1, 3], 4);
    check(usingAbrupt("break", "return"), [0, 1, 3], 4);
    check(usingAbrupt("continue", "return"), [0, 1, 3], 4);
  });

  it("should override abrupt with break", function() {
    check(usingAbrupt("return", "break"), [0, 1, 3], 5);
    check(usingAbrupt("break", "break"), [0, 1, 3], 5);
    check(usingAbrupt("continue", "break"), [0, 1, 3], 5);
  });

  it("should override abrupt with continue", function() {
    check(usingAbrupt("return", "continue"), [0, 1, 3, 1, 3], 2);
    check(usingAbrupt("break", "continue"), [0, 1, 3, 1, 3], 5);
    check(usingAbrupt("continue", "continue"), [0, 1, 3, 1, 3], 2);
  });

  it("should execute finally blocks statically", function() {
    check(usingThrow(true), [0, 1, 4], 5);
    check(usingThrow(false), [0, 1, 6], 7);
  });

  it("should execute finally blocks dynamically", function() {
    check(usingRaise(true), [0, 1, 4], 5);
    check(usingRaise(false), [0, 1, 6], 7);
  });

  it("should execute finally blocks before throwing", function() {
    var uncaughtError = new Error("uncaught");

    function *uncaught(condition) {
      try {
        yield 0;
        if (condition) {
          yield 1;
          raise(uncaughtError);
        }
        yield 2;
      } finally {
        yield 3;
      }
      yield 4;
    }

    check(uncaught(false), [0, 2, 3, 4]);

    var u = uncaught(true);

    assert.deepEqual(u.next(), { value: 0, done: false });
    assert.deepEqual(u.next(), { value: 1, done: false });
    assert.deepEqual(u.next(), { value: 3, done: false });

    try {
      u.next();
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, uncaughtError);
    }
  });

  it("should throw correct error when finally contains catch", function() {
    var right = new Error("right");
    var wrong = new Error("wrong");

    function *gen() {
      try {
        yield 0;
        raise(right);
      } finally {
        yield 1;
        try {
          raise(wrong);
        } catch (err) {
          assert.strictEqual(err, wrong);
          yield 2;
        }
      }
    }

    var g = gen();

    assert.deepEqual(g.next(), {
      value: 0,
      done: false
    });

    assert.deepEqual(g.next(), {
      value: 1,
      done: false
    });

    assert.deepEqual(g.next(), {
      value: 2,
      done: false
    });

    try {
      g.next();
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, right);
    }
  });

  it("should run finally after break within try", function() {
    function *gen() {
      try {
        yield 0;
        while (true) {
          yield 1;
          break;
        }
      } finally {
        yield 2;
      }
      yield 3;
    }

    check(gen(), [0, 1, 2, 3]);
  });
});

describe("try-catch-finally generator", function() {
  function *usingThrow() {
    yield 0;
    try {
      try {
        yield 1;
        throw 2;
        yield 3;
      } catch (x) {
        throw yield x;
      } finally {
        yield 5;
      }
    } catch (thrown) {
      yield thrown;
    }
    yield 6;
  }

  function *usingRaise() {
    yield 0;
    try {
      try {
        yield 1;
        raise(2);
        yield 3;
      } catch (x) {
        throw yield x;
      } finally {
        yield 5;
      }
    } catch (thrown) {
      yield thrown;
    }
    yield 6;
  }

  it("should statically catch and then finalize", function() {
    check(usingThrow(), [0, 1, 2, 5, 3, 6]);
  });

  it("should dynamically catch and then finalize", function() {
    check(usingRaise(), [0, 1, 2, 5, 3, 6]);
  });

  it("should execute catch and finally blocks at most once", function() {
    var error = new Error();

    function *gen() {
      try {
        switch (1) {
        case 1:
          yield "a";
          break;
        default:
          break;
        }
        throw error;
      } catch (e) {
        assert.strictEqual(e, error);
        yield "b";
        do {
          do {
            yield "c";
            break;
          } while (false);
          yield "d";
          break;
        } while (false);
        yield "e";
      } finally {
        yield "f";
      }
    }

    check(gen(), ["a", "b", "c", "d", "e", "f"]);
  });

  it("should handle backwards jumps in labeled loops", function() {
    function *gen() {
      var firstTime = true;
      outer:
      while (true) {
        yield 0;
        try {
          while (true) {
            yield 1;
            if (firstTime) {
              firstTime = false;
              yield 2;
              continue outer;
            } else {
              yield 3;
              break;
            }
          }
          yield 4;
          break;
        } finally {
          yield 5;
        }
        yield 6;
      }
      yield 7;
    }

    check(gen(), [0, 1, 2, 5, 0, 1, 3, 4, 5, 7]);
  });

  it("should handle loop continue statements properly", function() {
    var error = new Error("thrown");
    var markers = [];

    function *gen() {
      var c = 2;
      while (c > 0) {
        try {
          markers.push("try");
          yield c;
        } catch (e) {
          assert.strictEqual(e, error);
          markers.push("catch");
          continue;
        } finally {
          markers.push("finally");
        }
        markers.push("decrement");
        --c;
      }
    }

    var g = gen();

    assert.deepEqual(g.next(), { value: 2, done: false });
    assert.deepEqual(g.throw(error), { value: 2, done: false });
    assert.deepEqual(g.next(), { value: 1, done: false });
    assert.deepEqual(g.next(), { value: void 0, done: true });

    assert.deepEqual(markers, [
      "try",
      "catch",
      "finally",
      "try",
      "finally",
      "decrement",
      "try",
      "finally",
      "decrement"
    ]);
  });
});

describe("dynamic exception", function() {
  function *gen(x, fname) {
    try {
      return fns[fname](x);
    } catch (thrown) {
      yield thrown;
    }
  }

  var fns = {
    f: function(x) {
      throw x;
    },

    g: function(x) {
      return x;
    }
  };

  it("should be dispatched correctly", function() {
    check(gen("asdf", "f"), ["asdf"]);
    check(gen("asdf", "g"), [], "asdf");
  });
});

describe("nested finally blocks", function() {
  function *usingThrow() {
    try {
      try {
        try {
          throw "thrown";
        } finally {
          yield 1;
        }
      } catch (thrown) {
        yield thrown;
      } finally {
        yield 2;
      }
    } finally {
      yield 3;
    }
  }

  function *usingRaise() {
    try {
      try {
        try {
          raise("thrown");
        } finally {
          yield 1;
        }
      } catch (thrown) {
        yield thrown;
      } finally {
        yield 2;
      }
    } finally {
      yield 3;
    }
  }

  it("should statically execute in order", function() {
    check(usingThrow(), [1, "thrown", 2, 3]);
  });

  it("should dynamically execute in order", function() {
    check(usingRaise(), [1, "thrown", 2, 3]);
  });
});

describe("for-in loop generator", function() {
  it("should handle the simple case", function() {
    function *gen() {
      var count = 0;
      var obj = {foo: 1, bar: 2};
      for (var key in obj) {
        assert(obj.hasOwnProperty(key), key + " must be own property");
        yield [key, obj[key]];
        count += 1;
      }
      return count;
    }

    check(gen(), [["foo", 1], ["bar", 2]], 2);
  });

  it("should handle break in loop", function() {
    function *gen(obj) {
      var count = 0;
      for (var key in (yield "why not", obj)) {
        if (obj.hasOwnProperty(key)) {
          if (key === "skip") {
            break;
          }
          count += 1;
          yield [key, obj[key]];
        }
      }
      return count;
    }

    check(
      gen({ a: 1, b: 2, skip: 3, c: 4 }),
      ["why not", ["a", 1], ["b", 2]],
      2
    );
  });

  it("should handle property deletion in loop", function() {
    function *gen() {
      var count = 0;
      var obj = {foo: 1, bar: 2};
      for (var key in obj) {
        assert(obj.hasOwnProperty(key), key + " must be own property");
        yield [key, obj[key]];
        delete obj.bar;
        count += 1;
      }
      return count;
    }

    check(gen(), [["foo", 1]], 1);
  });

  it("should loop over inherited properties", function() {
    function *gen() {
      var count = 0;
      function Foo() {
        this.baz = 1
      }
      Foo.prototype.bar = 2;

      var foo = new Foo();
      for (var key in foo) {
        yield [key, foo[key]];
        count += 1;
      }
      return count;
    }

    check(gen(), [["baz", 1], ["bar", 2]], 2);
  });

  it("should handle risky object expressions", function() {
    function a(sent) {
      assert.strictEqual(sent, 1);
      a.called = true;
    }

    function b(sent) {
      assert.strictEqual(sent, 2);
      b.called = true;
      return { callee: b };
    }

    function *gen() {
      assert.ok(!a.called);
      assert.ok(!b.called);
      for (var key in a(yield 0), b(yield 1)) {
        assert.ok(a.called);
        assert.ok(b.called);
        assert.strictEqual(yield key, 3);
      }

      for (var key in a(1), { foo: "foo", bar: "bar" }) {
        yield key;
      }
    }

    check(gen(), [0, 1, "callee", "foo", "bar"]);
  });

  it("should allow non-Identifier left-hand expressions", function() {
    var obj = {};
    var baz = { a: 1, b: 2, c: 3 };
    var markers = [];

    function foo() {
      markers.push("called foo");
      return obj;
    }

    function *gen() {
      for (foo().bar in baz) {
        markers.push(obj.bar);
        yield obj.bar;
      }
    }

    check(gen(), ["a", "b", "c"]);

    assert.deepEqual(markers, [
      "called foo",
      "a",
      "called foo",
      "b",
      "called foo",
      "c"
    ]);
  });
});

describe("yield chain", function() {
  function *gen(n) {
    return yield yield yield yield n;
  }

  it("should have correct associativity", function() {
    check(gen(5), [5, 1, 2, 3], 4);
    check(gen("asdf"), ["asdf", 1, 2, 3], 4);
  });
});

describe("object literal generator", function() {
  function *gen(a, b) {
    yield {
      a: a - (yield a),
      b: yield b
    };
  }

  it("should yield the correct object", function() {
    check(gen(1, 2), [1, 2, { a: 0, b: 2 }]);
    check(gen(4, 2), [4, 2, { a: 3, b: 2 }]);
  });
});

describe("switch statement generator", function() {
  function *gen(a) {
    switch (yield a) {
    case (yield "x") - a:
      return "first case";
    case (yield "y") - a:
      return "second case";
    }
  }

  it("should jump to the correct cases", function() {
    check(gen(1), [1, "x"], "first case");
    check(gen(2), [2, "x", "y"], "second case");
  });
});

describe("infinite sequence generator", function() {
  function *gen(start, step) {
    step = step || 1;
    while (true) {
      yield start;
      start += step;
    }
  }

  function *limit(g, stop) {
    while (true) {
      var info = g.next();
      if (info.done) {
        return;
      } else if (info.value < stop) {
        yield info.value;
      } else {
        return;
      }
    }
  }

  it("should generate a lot of plausible values", function() {
    var g = gen(10, 2);

    assert.deepEqual(g.next(), { value: 10, done: false });
    assert.deepEqual(g.next(), { value: 12, done: false });
    assert.deepEqual(g.next(), { value: 14, done: false });
    assert.deepEqual(g.next(), { value: 16, done: false });

    var sum = 10 + 12 + 14 + 16;

    for (var n = 0; n < 1000; ++n) {
      var info = g.next();
      sum += info.value;
      assert.strictEqual(info.done, false);
    }

    assert.strictEqual(sum, 1017052);
  });

  it("should allow limiting", function() {
    check(limit(gen(10, 3), 20), [10, 13, 16, 19]);
  });
});

describe("generator function expression", function() {
  it("should behave just like a declared generator", function() {
    check(function *(x, y) {
      yield x;
      yield y;
      yield x + y;
      return x * y;
    }(3, 7), [3, 7, 10], 21);
  })
});

describe("generator reentry attempt", function() {
  function *gen(x) {
    try {
      (yield x).next(x);
    } catch (err) {
      yield err;
    }
    return x + 1;
  }

  it("should complain with a TypeError", function() {
    var g = gen(3);
    assert.deepEqual(g.next(), { value: 3, done: false });
    var complaint = g.next(g); // Sending the generator to itself.
    assert.ok(complaint.value instanceof Error);
    assert.strictEqual(
      complaint.value.message,
      "Generator is already running"
    );
    assert.deepEqual(g.next(), { value: 4, done: true });
  });
});

describe("completed generator", function() {
  function *gen() {
    return "ALL DONE";
  }

  (runningInTranslation ? it : xit)
  ("should refuse to resume", function() {
    var g = gen();

    assert.deepEqual(g.next(), {
      value: "ALL DONE", done: true
    });

    assertAlreadyFinished(g);
  });
});

describe("delegated yield", function() {
  it("should delegate correctly", function() {
    function *gen(condition) {
      yield 0;
      if (condition) {
        yield 1;
        yield* gen(false);
        yield 2;
      }
      yield 3;
    }

    check(gen(true), [0, 1, 0, 3, 2, 3]);
    check(gen(false), [0, 3]);
  });

  it("should cope with empty delegatees", function() {
    function *gen(condition) {
      if (condition) {
        yield 0;
        yield* gen(false);
        yield 1;
      }
    }

    check(gen(true), [0, 1]);
    check(gen(false), []);
  });

  it("should support deeper nesting", function() {
    function *outer(n) {
      yield n;
      yield* middle(n - 1, inner(n + 10));
      yield n + 1;
    }

    function *middle(n, plusTen) {
      yield n;
      yield* inner(n - 1);
      yield n + 1;
      yield* plusTen;
    }

    function *inner(n) {
      yield n;
    }

    check(outer(5), [5, 4, 3, 5, 15, 6]);
  });

  it("should pass sent values through", function() {
    function *outer(n) {
      yield* inner(n << 1);
      yield "zxcv";
    }

    function *inner(n) {
      return yield yield yield n;
    }

    var g = outer(3);
    assert.deepEqual(g.next(), { value: 6, done: false });
    assert.deepEqual(g.next(1), { value: 1, done: false });
    assert.deepEqual(g.next(2), { value: 2, done: false });
    assert.deepEqual(g.next(4), { value: "zxcv", done: false });
    assert.deepEqual(g.next(5), { value: void 0, done: true });
  });

  it("should be governed by enclosing try statements", function() {
    var error = new Error("thrown");

    function *outer(n) {
      try {
        yield 0;
        yield* inner(n);
        yield 1;
      } catch (err) {
        yield err.message;
      }
      yield 4;
    }

    function *inner(n) {
      while (n --> 0) {
        try {
          if (n === 3) {
            raise(error);
          }
        } finally {
          yield n;
        }
      }
    }

    check(outer(3), [0, 2, 1, 0, 1, 4]);
    check(outer(5), [0, 4, 3, "thrown", 4]);
  });

  it("should dispatch .thrown exceptions correctly", function() {
    var count = 0;

    function *gen() {
      yield* inner();
      try {
        yield* inner();
      } catch (err) {
        // pass
      }
      return yield* inner();
    }

    function *inner() {
      return yield count++;
    }

    var g = gen();

    assert.deepEqual(g.next(), {
      value: 0,
      done: false
    });

    assert.deepEqual(g.next(), {
      value: 1,
      done: false
    });

    assert.deepEqual(g.throw(new Error("lol")), {
      value: 2,
      done: false,
    });

    assert.deepEqual(g.next("sent"), {
      value: "sent",
      done: true
    });
  });

  it("should call .return methods of delegate iterators", function() {
    var throwee = new Error("argument to gen.throw");
    var thrownFromThrow = new Error("thrown from throw method");
    var thrownFromReturn = new Error("thrown from return method");

    function *gen(delegate) {
      try {
        return yield* delegate;
      } catch (err) {
        return err;
      }
    }

    function check(throwMethod, returnMethod) {
      var throwCalled = false;
      var returnCalled = false;
      var count = 0;
      var iterator = {
        next: function() {
          return { value: count++, done: false };
        }
      };

      iterator[iteratorSymbol] = function() {
        return this;
      };

      if (throwMethod) {
        iterator["throw"] = function() {
          throwCalled = true;
          return throwMethod.apply(this, arguments);
        };
      }

      if (returnMethod) {
        iterator["return"] = function() {
          returnCalled = true;
          return returnMethod.apply(this, arguments);
        };
      }

      var g = gen(iterator);

      assert.deepEqual(g.next(), { value: 0, done: false });
      assert.deepEqual(g.next(), { value: 1, done: false });
      assert.deepEqual(g.next(), { value: 2, done: false });
      assert.deepEqual(g.next(), { value: 3, done: false });

      assert.strictEqual(throwCalled, false);
      assert.strictEqual(returnCalled, false);

      var result = {};

      result.throwResult = g.throw(throwee);
      result.throwCalled = throwCalled;
      result.returnCalled = returnCalled;

      return result;
    }

    var checkResult = check(undefined, function() {
      throw thrownFromReturn;
    });
    if (runningInTranslation) {
      // BUG: Node v0.11 and v0.12 neglect to call .return here.
      assert.strictEqual(checkResult.throwResult.value, thrownFromReturn);
    } else {
      // This is the TypeError that results from trying to call the
      // undefined .throw method of the iterator.
      assert.ok(checkResult.throwResult.value instanceof TypeError);
    }
    assert.strictEqual(checkResult.throwResult.done, true);
    assert.strictEqual(checkResult.throwCalled, false);
    // BUG: Node v0.11 and v0.12 neglect to call .return here.
    assert.strictEqual(checkResult.returnCalled, runningInTranslation);

    checkResult = check(undefined, function() {
      return { value: "from return", done: true };
    });
    assert.notStrictEqual(checkResult.throwResult.value, throwee);
    // This is the TypeError that results from trying to call the
    // undefined .throw method of the iterator.
    assert.ok(checkResult.throwResult.value instanceof TypeError);
    assert.strictEqual(checkResult.throwResult.done, true);
    assert.strictEqual(checkResult.throwCalled, false);
    // BUG: Node v0.11 and v0.12 neglect to call .return here.
    assert.strictEqual(checkResult.returnCalled, runningInTranslation);

    var checkResult = check(function(thrown) {
      return { value: "from throw", done: true };
    }, function() {
      throw thrownFromReturn;
    });
    assert.strictEqual(checkResult.throwResult.value, "from throw");
    assert.strictEqual(checkResult.throwResult.done, true);
    assert.strictEqual(checkResult.throwCalled, true);
    assert.strictEqual(checkResult.returnCalled, false);

    var checkResult = check(function(thrown) {
      throw thrownFromThrow;
    }, function() {
      throw thrownFromReturn;
    });
    assert.strictEqual(checkResult.throwResult.value, thrownFromThrow);
    assert.strictEqual(checkResult.throwResult.done, true);
    assert.strictEqual(checkResult.throwCalled, true);
    assert.strictEqual(checkResult.returnCalled, false);

    var checkResult = check(undefined, undefined);
    assert.notStrictEqual(checkResult.throwResult.value, throwee);
    // This is the TypeError that results from trying to call the
    // undefined .throw method of the iterator.
    assert.ok(checkResult.throwResult.value instanceof TypeError);
    assert.strictEqual(checkResult.throwResult.done, true);
    assert.strictEqual(checkResult.throwCalled, false);
    assert.strictEqual(checkResult.returnCalled, false);
  });

  it("should not be required to have a .return method", function() {
    function *gen(delegate) {
      return yield* delegate;
    }

    var inner = range(5);
    var iterator = { next: inner.next.bind(inner) };
    iterator[iteratorSymbol] = function() {
      return this;
    };

    var g = gen(iterator);
    assert.deepEqual(g.next(), { value: 0, done: false });
    assert.deepEqual(g.next(), { value: 1, done: false });
    assert.deepEqual(g.next(), { value: 2, done: false });

    if (typeof g.return === "function") {
      assert.deepEqual(g.return(-1), { value: -1, done: true });
      assert.deepEqual(g.next(), { value: void 0, done: true });
    }
  });

  (runningInTranslation ? it : xit)
  ("should support any iterable argument", function() {
    function *gen() {
      yield 0;
      yield* [
        yield "one",
        yield "two",
        yield "three"
      ];
      yield 5;
    }

    check(gen(), [0, "one", "two", "three", 2, 3, 4, 5]);

    function *string() {
      return yield* "asdf";
    }

    check(string(), ["a", "s", "d", "f"]);
  });

  it("should evaluate to the return value of the delegate", function() {
    function *inner() {
      yield 1;
      return 2;
    }

    function *outer(delegate) {
      return yield* delegate;
    }

    check(outer(inner()), [1], 2);

    var arrayDelegate = [3, 4];
    if (!runningInTranslation) {
      // Node v0.11 doesn't know how to turn arrays into iterators over
      // their elements without a little help.
      arrayDelegate = regeneratorRuntime.values(arrayDelegate);
    }
    check(outer(arrayDelegate), [3, 4], void 0); // See issue #143.

    if (!runningInTranslation) {
      return;
    }

    check(outer({
      next: function() {
        return { value: "oyez", done: true };
      }
    }), [], "oyez");
  });

  it("should work as a subexpression", function() {
    function *inner(arg) {
      return arg;
    }

    function *gen(delegate) {
      // Unfortunately these parentheses appear to be necessary.
      return 1 + (yield* delegate);
    }

    check(gen(inner(2)), [], 3);
    check(gen(inner(3)), [], 4);

    if (!runningInTranslation) {
      return;
    }

    check(gen({
      next: function() {
        return { value: "foo", done: true };
      }
    }), [], "1foo");
  });
});

describe("function declaration hoisting", function() {
  it("should work even if the declarations are out of order", function() {
    function *gen(n) {
      yield increment(n);

      function increment(x) {
        return x + 1;
      }

      if (n % 2) {
        yield halve(decrement(n));

        function halve(x) {
          return x >> 1;
        }

        function decrement(x) {
          return x - 1;
        }
      } else {
        // The behavior of function declarations nested inside conditional
        // blocks is notoriously underspecified, and in V8 it appears the
        // halve function is still defined when we take this branch, so
        // "undefine" it for consistency with regenerator semantics.
        halve = void 0;
      }

      yield typeof halve;

      yield increment(increment(n));
    }

    check(gen(3), [4, 1, "function", 5]);
    check(gen(4), [5, "undefined", 6]);
  });

  it("should work for nested generator function declarations", function() {
    function *outer(n) {
      yield 0;
      assert.ok(regeneratorRuntime.isGeneratorFunction(inner));
      return yield* inner(n);

      // Note that this function declaration comes after everything else
      // in the outer function, but needs to be fully available above.
      function *inner(n) {
        yield n - 1;
        yield n;
        return yield n + 1;
      }
    }

    check(outer(2), [0, 1, 2, 3], 4);
  });

  it("should not interfere with function rebinding", function() {
    function rebindTo(value) {
      var oldValue = toBeRebound;
      toBeRebound = value;
      return oldValue;
    }

    function *toBeRebound() {
      var originalValue = toBeRebound;
      yield toBeRebound;
      assert.strictEqual(rebindTo(42), originalValue);
      yield toBeRebound;
      assert.strictEqual(rebindTo("asdf"), 42);
      yield toBeRebound;
    }

    var original = toBeRebound;
    check(toBeRebound(), [original, 42, "asdf"]);

    function attemptToRebind(value) {
      var oldValue = safe;
      safe = value;
      return oldValue;
    }

    var safe = function *safe() {
      var originalValue = safe;
      yield safe;
      assert.strictEqual(attemptToRebind(42), originalValue);
      yield safe;
      assert.strictEqual(attemptToRebind("asdf"), 42);
      yield safe;
    }

    original = safe;
    check(safe(), [safe, safe, safe]);
  });
});

describe("the arguments object", function() {
  it("should work in simple variadic functions", function() {
    function *sum() {
      var result = 0;

      for (var i = 0; i < arguments.length; ++i) {
        yield result += arguments[i];
      }

      return result;
    }

    check(sum(1, 2, 3), [1, 3, 6], 6);
    check(sum(9, -5, 3, 0, 2), [9, 4, 7, 7, 9], 9);
  });

  it("should alias function parameters", function() {
    function *gen(x, y) {
      yield x;
      ++arguments[0];
      yield x;

      yield y;
      --arguments[1];
      yield y;

      var temp = y;
      y = x;
      x = temp;

      yield x;
      yield y;
    }

    check(gen(3, 7), [3, 4, 7, 6, 6, 4]);
    check(gen(10, -5), [10, 11, -5, -6, -6, 11]);
  });

  it("should be shadowable by explicit declarations", function() {
    function *asParameter(x, arguments) {
      arguments = arguments + 1;
      yield x + arguments;
    }

    check(asParameter(4, 5), [10]);
    check(asParameter("asdf", "zxcv"), ["asdfzxcv1"]);

    function *asVariable(x) {
      // TODO References to arguments before the variable declaration
      // seem to see the object instead of the undefined value.
      var arguments = x + 1;
      yield arguments;
    }

    check(asVariable(4), [5]);
    check(asVariable("asdf"), ["asdf1"]);
  });

  it("should not get confused by properties", function() {
    function *gen(args) {
      var obj = { arguments: args };
      yield obj.arguments;
      obj.arguments = "oyez";
      yield obj;
    }

    check(gen(42), [42, { arguments: "oyez" }]);
  });

  it("supports .callee", function() {
    function *gen(doYield) {
      yield 1;
      if (doYield) {
        yield 2;
      } else {
        yield 3
        yield* arguments.callee(true);
        yield 4
      }
      yield 5;
    }

    check(gen(false), [1, 3, 1, 2, 5, 4, 5]);
  });
});

describe("catch parameter shadowing", function() {
  it("should leave outer variables unmodified", function() {
    function *gen(x) {
      var y = x + 1;
      try {
        throw x + 2;
      } catch (x) {
        yield x;
        x += 1;
        yield x;
      }
      yield x;
      try {
        throw x + 3;
      } catch (y) {
        yield y;
        y *= 2;
        yield y;
      }
      yield y;
    }

    check(gen(1), [3, 4, 1, 4, 8, 2]);
    check(gen(2), [4, 5, 2, 5, 10, 3]);
  });

  it("should not replace variables defined in inner scopes", function() {
    function *gen(x) {
      try {
        throw x;
      } catch (x) {
        yield x;

        yield (function(x) {
          return x += 1;
        }(x + 1));

        yield (function() {
          var x = arguments[0];
          return x * 2;
        }(x + 2));

        yield (function() {
          function notCalled(x) {
            throw x;
          }

          x >>= 1;
          return x;
        }());

        yield x -= 1;
      }

      yield x;
    }

    check(gen(10), [10, 12, 24, 5, 4, 10]);
    check(gen(11), [11, 13, 26, 5, 4, 11]);
  });

  it("should allow nested catch parameters of the same name", function() {
    function *gen() {
      try {
        raise("e1");
      } catch (e) {
        yield e;
        try {
          raise("e2");
        } catch (e) {
          yield e;
        }
        yield e;
      }
    }

    check(gen(), ["e1", "e2", "e1"]);
  });

  it("should not interfere with non-referential identifiers", function() {
    function *gen() {
      try {
        yield 1;
        raise(new Error("oyez"));
        yield 2;
      } catch (e) {
        yield 3;
        e.e = "e.e";
        e[e.message] = "e.oyez";
        return {
          e: e,
          identity: function(x) {
            var e = x;
            return e;
          }
        };
      }
      yield 4;
    }

    var g = gen();
    assert.deepEqual(g.next(), { value: 1, done: false });
    assert.deepEqual(g.next(), { value: 3, done: false });

    var info = g.next();
    assert.strictEqual(info.done, true);
    assert.strictEqual(info.value.e.message, "oyez");
    assert.strictEqual(info.value.e.e, "e.e");
    assert.strictEqual(info.value.e.oyez, "e.oyez");
    assert.strictEqual(info.value.identity("same"), "same");
  });
});

describe("empty while loops", function() {
  it("should be preserved in generated code", function() {
    function *gen(x) {
      while (x) {
        // empty while loop
      }

      do {
        // empty do-while loop
      } while (x);

      return gen.toString();
    }

    var info = gen(false).next();
    assert.strictEqual(info.done, true);
    assert.ok(/empty while loop/.test(info.value));
    assert.ok(/empty do-while loop/.test(info.value));
  });
});

describe("object literals with multiple yields", function() {
  it("should receive different sent values", function() {
    function *gen(fn) {
      return {
        a: yield "a",
        b: yield "b",
        c: fn(yield "c", yield "d"),
        d: [yield "e", yield "f"]
      };
    }

    check(gen(function sum(x, y) {
      return x + y;
    }), ["a", "b", "c", "d", "e", "f"], {
      a: 1,
      b: 2,
      c: 3 + 4,
      d: [5, 6]
    });
  });
});

describe("generator .throw method", function() {
  (runningInTranslation ? it : xit)("should complete generator", function() {
    function *gen(x) {
      yield 2;
      throw 1;
    }

    var u = gen();

    u.next();

    try {
      u.throw(2);
    } catch (err) {
      assert.strictEqual(err, 2);
    }

    assertAlreadyFinished(u);
  });

  it("should work after the final call to .next", function() {
    function *gen() {
      yield 1;
    }

    var g = gen();
    assert.deepEqual(g.next(), { value: 1, done: false });

    var exception = new Error("unhandled exception");
    try {
      g.throw(exception);
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, exception);
    }
  });

  it("should immediately complete a new-born generator", function() {
    var began = false;

    function *gen() {
      began = true;
      yield 1;
    }

    var g = gen();
    var exception = new Error("unhandled exception");
    try {
      g.throw(exception);
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, exception);
      assert.strictEqual(began, false);
    }
  });

  it("should not propagate errors handled inside a delegate", function() {
    function *outer() {
      try {
        yield* inner();
      } catch (err) {
        return -1;
      }
      return 1;
    }

    function *inner() {
      try {
        yield void 0;
      } catch (e) {
        return;
      }
    }

    var g = outer();
    g.next();
    assert.equal(g.throw(new Error('foo')).value, 1);
  });

  it("should propagate errors unhandled inside a delegate", function() {
    function *outer() {
      try {
        yield* inner();
      } catch (err) {
        return -1;
      }
      return 1;
    }

    function *inner() {
      yield void 0;
    }

    var g = outer();
    g.next();
    assert.equal(g.throw(new Error('foo')).value, -1);
  });
});

describe("unqualified function calls", function() {
  it("should have a global `this` object", function() {
    function getThis() {
      return this;
    }

    // This is almost certainly the global object, but there's a chance it
    // might be null or undefined (in strict mode).
    var unqualifiedThis = getThis();

    function *invoke() {
      // It seems like a bug in the ES6 spec that we have to yield an
      // argument instead of just calling (yield)().
      return (yield "dummy")();
    }

    var g = invoke();
    var info = g.next();

    assert.deepEqual(info, { value: "dummy", done: false });

    info = g.next(getThis);

    // Avoid using assert.strictEqual when the arguments might equal the
    // global object, since JSON.stringify chokes on circular structures.
    assert.ok(info.value === unqualifiedThis);

    assert.strictEqual(info.done, true);
  });
});

describe("yield* expression results", function () {
  it("have correct values", function () {
    function* foo() {
      yield 0;
      return yield* bar();
    }

    function* bar() {
      yield 1;
      return 2;
    }

    check(foo(), [0, 1], 2);
  });

  it("can be used in complex expressions", function () {
    function pumpNumber(gen) {
      var n = 0;

      while (true) {
        var res = n > 0 ? gen.next(n) : gen.next();
        n = res.value;
        if (res.done) {
          return n;
        }
      }
    }

    function* foo() {
      return (yield* bar()) + (yield* bar());
    }

    function* bar() {
      return (yield 2) + (yield 3);
    }

    assert.strictEqual(pumpNumber(bar()), 5);
    assert.strictEqual(pumpNumber(foo()), 10);
  });
});

describe("isGeneratorFunction", function() {
  it("should work for function declarations", function() {
    // Do the assertions up here to make sure the generator function is
    // marked at the beginning of the block the function is declared in.
    assert.strictEqual(
      regeneratorRuntime.isGeneratorFunction(genFun),
      true
    );

    assert.strictEqual(
      regeneratorRuntime.isGeneratorFunction(normalFun),
      false
    );

    function normalFun() {
      return 0;
    }

    function *genFun() {
      yield 0;
    }
  });

  it("should work for function expressions", function() {
    assert.strictEqual(
      regeneratorRuntime.isGeneratorFunction(function *genFun() {
        yield 0;
      }),
      true
    );

    assert.strictEqual(
      regeneratorRuntime.isGeneratorFunction(function normalFun() {
        return 0;
      }),
      false
    );
  });
});

describe("new expressions", function() {
  it("should be able to contain yield sub-expressions", function() {
    function A(first, second) {
      this.first = first;
      this.second = second;
    }

    function *gen() {
      return yield new (yield 0)(yield 1, yield 2);
    }

    var g = gen();

    assert.deepEqual(g.next(), { value: 0, done: false });
    assert.deepEqual(g.next(A), { value: 1, done: false });
    assert.deepEqual(g.next("asdf"), { value: 2, done: false });

    var info = g.next("zxcv");
    assert.strictEqual(info.done, false);
    assert.ok(info.value instanceof A);
    assert.strictEqual(info.value.first, "asdf");
    assert.strictEqual(info.value.second, "zxcv");

    assert.deepEqual(g.next("qwer"), { value: "qwer", done: true });
  });
});

describe("block binding", function() {
  it("should translate block binding correctly", function() {
    "use strict";

    function *gen() {
      var a$0 = 0, a$1 = 1;

      let a = 3;

      {
        let a = 1;
        yield a + a$0;
      }

      {
        let a = 2;
        yield a - 1 + a$1;
      }

      yield a;
    }

    var g = gen();

    assert.deepEqual(g.next(), { value: 1, done: false });
    assert.deepEqual(g.next(), { value: 2, done: false });
    assert.deepEqual(g.next(), { value: 3, done: false });
    assert.deepEqual(g.next(), { value: void 0, done: true });
  });

  it("should translate block binding with iife correctly", function() {
    "use strict";

    function *gen() {
      let arr = [];

      for (let x = 0; x < 3; x++) {
        let y = x;
        arr.push(function() { return y; });
      }

      {
        let x;
        while( x = arr.pop() ) {
          yield x;
        }
      }
    }

    var g = gen();

    assert.equal(g.next().value(), 2);
    assert.equal(g.next().value(), 1);
    assert.equal(g.next().value(), 0);
    assert.deepEqual(g.next(), { value: void 0, done: true });
  });
});

describe("newborn generators", function() {
  it("should be able to yield* non-newborn generators", function() {
    function *inner() {
      return [yield 1, yield 2];
    }

    function *outer(delegate) {
      return yield* delegate;
    }

    var n = inner();

    assert.deepEqual(n.next(), {
      value: 1,
      done: false
    });

    var g = outer(n);

    // I would really like to be able to pass 3 to g.next here, but V8
    // ignores values sent to newborn generators, and SpiderMonkey throws
    // a TypeError.
    assert.deepEqual(g.next(), {
      value: 2,
      done: false
    });

    assert.deepEqual(g.next(4), {
      value: [void 0, 4],
      done: true
    });
  });

  it("should support the ignore-initial-yield wrapper idiom", function() {
    var markers = [];

    function *inner() {
      markers.push(0);
      var sent1 = yield 1;
      markers.push(2);
      var sent2 = yield 2;
      markers.push(3);
      return [sent1, sent2];
    }

    function wrapper(delegate) {
      var gen = (function*() {
        // This yield is the "initial yield" whose argument we ignore.
        var sent = yield "ignored", info;

        markers.push(1);

        while (!(info = delegate.next(sent)).done) {
          sent = yield info.value;
        }

        markers.push(4);

        return info.value;
      })();

      // Ensure that gen is not newborn and that the next invocation of
      // gen.next(value) can send value to the initial yield expression.
      gen.next();

      return gen;
    }

    var n = inner();

    assert.deepEqual(n.next(), {
      value: 1,
      done: false
    });

    var g = wrapper(n);

    // Unlike in the previous spec, it's fine to pass 3 to g.next here,
    // because g is not newborn, because g.next was already called once
    // before g was returned from the wrapper function.
    assert.deepEqual(g.next(3), {
      value: 2,
      done: false
    });

    assert.deepEqual(g.next(4), {
      value: [3, 4],
      done: true
    });

    // Ensure we encountered the marker points in the expected order.
    assert.deepEqual(markers, [0, 1, 2, 3, 4]);
  });

  it("should allow chaining newborn and non-newborn generators", function() {
    function *range(n) {
      for (var i = 0; i < n; ++i) {
        yield i;
      }
    }

    function *chain(a, b) {
      yield* a;
      yield* b;
    }

    check(chain(range(3), range(5)), [0, 1, 2, 0, 1, 2, 3, 4]);

    function *y3(x) {
      return yield yield yield x;
    }

    function *y5(x) {
      return yield yield yield yield yield x;
    }

    check(
      chain(y3("foo"), y5("bar")),
      ["foo", 1, 2, "bar", 4, 5, 6, 7]
    );

    var g3 = y3("three");
    assert.deepEqual(g3.next(), {
      value: "three",
      done: false
    });

    var g5 = y5("five");
    assert.deepEqual(g5.next(), {
      value: "five",
      done: false
    });

    var undef; // A little easier to read than void 0.
    check(chain(g3, g5), [undef, 1, undef, 3, 4, 5]);
  });
});

describe("labeled break and continue statements", function() {
  it("should be able to exit multiple try statements", function() {
    var e1 = "first";
    var e2 = "second";
    var e3 = "third";
    var e4 = "fourth";

    function *gen(n, which) {
      try {
        yield 0;
        raise(e1);

      } finally {
        yield 1;

        loop:
        for (var i = 0; i < n; ++i) {
          yield i;

          try {
            raise(e2);
          } finally {
            yield 2;

            try {
              raise(e3);
            } finally {
              yield 3;

              try {
                raise(e4);
              } finally {
                yield 4;

                if (which === "break") {
                  yield "breaking";
                  break loop;
                }

                if (which === "continue") {
                  yield "continuing";
                  continue loop;
                }

                yield 5;
              }
            }
          }
        }

        yield 6;
      }
    }

    try {
      check(gen(1, "break"), [
        0, 1, 0, 2, 3, 4, "breaking", 6
      ]);
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, e1);
    }

    try {
      check(gen(3, "continue"), [
        0, 1, 0, 2, 3, 4, "continuing",
        1, 2, 3, 4, "continuing",
        2, 2, 3, 4, "continuing",
        6 // Loop finished naturally.
      ]);
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, e1);
    }

    try {
      check(gen(3, "neither"), [
        0, 1, 0, 2, 3, 4, 5
      ]);
      assert.ok(false, "should have thrown an exception");
    } catch (err) {
      assert.strictEqual(err, e4);
    }
  });

  it("should allow breaking from any labeled statement", function() {
    function* gen(limit) {
      yield 0;

      for (var i = 0; i < limit; ++i) {
        yield 1;

        label1: {
          yield 2;
          break label1;
          yield 3;
        }

        label2:
        if (limit === 3) label3: {
          yield 4;
          if (i === 0) break label2;
          yield 5;
          if (i === 1) break label3;
          label4: yield 6;
          // This should break from the for-loop.
          if (i === 2) xxx: break;
          yield 7;
        }

        // This should be a no-op.
        xxx: break xxx;

        yield 8
      }

      yield 9;
    }

    check(gen(0), [0, 9]);
    check(gen(1), [0, 1, 2, 8, 9]);
    check(gen(2), [0, 1, 2, 8, 1, 2, 8, 9]);
    check(gen(3), [0, 1, 2, 4, 8, 1, 2, 4, 5, 8, 1, 2, 4, 5, 6, 9]);
  });
});

describe("for loop with var decl and no update expression", function() {
  // https://github.com/facebook/regenerator/issues/103
  function *range() {
    for (var i = 0; false; ) {
    }
  }

  it("should compile and run", function() {
    check(range(), []);
  });
});

describe("generator function prototype", function() {
  function getProto(obj) {
    return Object.getPrototypeOf
      ? Object.getPrototypeOf(obj)
      : obj.__proto__;
  }

  it("should follow the expected object model", function() {
    var GeneratorFunctionPrototype = getProto(f);
    var GeneratorFunction = GeneratorFunctionPrototype.constructor;

    assert.strictEqual(GeneratorFunction.name, 'GeneratorFunction');
    assert.strictEqual(GeneratorFunction.prototype,
                       GeneratorFunctionPrototype);
    assert.strictEqual(GeneratorFunctionPrototype.prototype.constructor,
                       GeneratorFunctionPrototype);
    assert.strictEqual(GeneratorFunctionPrototype.prototype,
                       getProto(f.prototype));
    assert.strictEqual(getProto(GeneratorFunctionPrototype),
                       Function.prototype);

    if (typeof process === "undefined" ||
        process.version.slice(1, 3) === "0.") {
      // Node version strings start with 0.
      assert.strictEqual(GeneratorFunctionPrototype.name,
                         "GeneratorFunctionPrototype");
    } else if (process.version.slice(1, 3) === "1.") {
      // iojs version strings start with 1., and iojs gets this .name
      // property wrong. TODO report this?
      assert.strictEqual(GeneratorFunctionPrototype.name, "");
    }

    assert.strictEqual(typeof f2, "function");
    assert.strictEqual(f2.constructor, GeneratorFunction);
    assert.ok(f2 instanceof GeneratorFunction);
    assert.strictEqual(f2.name, "f2");

    var g = f();
    assert.ok(g instanceof f);
    assert.strictEqual(getProto(g), f.prototype);

    assert.deepEqual([], Object.getOwnPropertyNames(f.prototype));
    // assert.deepEqual([], Object.getOwnPropertyNames(g));

    f.prototype.x = 42;

    var g2 = f();
    assert.strictEqual(g2.x, 42);

    var g3 = new f();
    assert.strictEqual(g3.x, 42);

    function* f2() {
      yield 1;
    }

    assert.strictEqual(getProto(f), getProto(f2));
    assert.strictEqual(f.hasOwnProperty('constructor'), false);
    assert.strictEqual(getProto(f).constructor.name, 'GeneratorFunction');

    // Intentionally at the end to test hoisting.
    function* f() {
      yield this;
    }

    function* f() {
      yield 1;
    }

    var f2 = f;
    f = 42;
    var g = f2();

    assert.deepEqual(g.next(), { value: 1, done: false });
    assert.deepEqual(g.next(), { value: void 0, done: true });
    assert.ok(g instanceof f2);
  });
});

describe("for-of loops", function() {
  (runningInTranslation ? it : xit)
  ("should work for Arrays", function() {
    var sum = 0;
    for (var x of [1, 2].concat(3)) {
      sum += x;
    }
    assert.strictEqual(sum, 6);
  });

  it("should work for generators", function() {
    var value, values = [];
    for (value of range(3))
      values.push(value);
    assert.deepEqual(values, [0, 1, 2]);
  });

  it("should work inside of generators", function() {
    function *yieldPermutations(list) {
      if (list.length < 2) {
        yield list;
        return 1;
      }

      var count = 0;
      var first = list.slice(0, 1);
      var genRest = yieldPermutations(list.slice(1));

      for (var perm of genRest) {
        for (var i = 0; i < list.length; ++i) {
          var prefix = perm.slice(0, i);
          var suffix = perm.slice(i);
          yield prefix.concat(first, suffix);
        }

        count += i;
      }

      return count;
    }

    var count = 0;
    for (var perm of yieldPermutations([])) {
      assert.deepEqual(perm, []);
      ++count;
    }
    assert.strictEqual(count, 1);

    check(yieldPermutations([1]), [[1]], 1);

    check(yieldPermutations([2, 1]), [
      [2, 1],
      [1, 2]
    ], 2);

    check(yieldPermutations([1,3,2]), [
      [1, 3, 2],
      [3, 1, 2],
      [3, 2, 1],
      [1, 2, 3],
      [2, 1, 3],
      [2, 3, 1]
    ], 6);
  });
});

describe("generator return method", function() {
  if (!runningInTranslation) {
    // The return method has not been specified or implemented natively,
    // yet, so these tests need only pass in translation.
    return;
  }

  it("should work with newborn generators", function() {
    function *gen() {
      yield 0;
    }

    var g = gen();

    assert.deepEqual(g.return("argument"), {
      value: "argument",
      done: true
    });

    assertAlreadyFinished(g);
  });

  it("should behave as if generator actually returned", function() {
    var executedFinally = false;

    function *gen() {
      try {
        yield 0;
      } catch (err) {
        assert.ok(false, "should not have executed the catch handler");
      } finally {
        executedFinally = true;
      }
    }

    var g = gen();
    assert.deepEqual(g.next(), { value: 0, done: false });

    assert.deepEqual(g.return("argument"), {
      value: "argument",
      done: true
    });

    assert.strictEqual(executedFinally, true);
    assertAlreadyFinished(g);
  });

  it("should return both delegate and delegator", function() {
    var checkpoints = [];

    function* callee(errorToThrow) {
      try {
        yield 1;
        yield 2;
      } finally {
        checkpoints.push("callee finally");
        if (errorToThrow) {
          throw errorToThrow;
        }
      }
    }

    function* caller(errorToThrow) {
      try {
        yield 0;
        yield* callee(errorToThrow);
        yield 3;
      } finally {
        checkpoints.push("caller finally");
      }
    }

    var g1 = caller();

    assert.deepEqual(g1.next(), { value: 0, done: false });
    assert.deepEqual(g1.next(), { value: 1, done: false });
    assert.deepEqual(g1.return(-1), { value: -1, done: true });
    assert.deepEqual(checkpoints, [
      "callee finally",
      "caller finally"
    ]);

    var error = new Error("thrown from callee");
    var g2 = caller(error);

    assert.deepEqual(g2.next(), { value: 0, done: false });
    assert.deepEqual(g2.next(), { value: 1, done: false });

    try {
      g2.return(-1);
      assert.ok(false, "should have thrown an exception");
    } catch (thrown) {
      assert.strictEqual(thrown, error);
    }

    assert.deepEqual(checkpoints, [
      "callee finally",
      "caller finally",
      "callee finally",
      "caller finally"
    ]);
  });
});

describe("expressions containing yield subexpressions", function() {
  it("should evaluate all subexpressions before yielding", function() {
    function *gen(x) {
      return x * (yield (function(y) { x = y }));
    }

    var g = gen(2);
    var result = g.next();
    assert.strictEqual(result.done, false);

    result.value(5);

    assert.deepEqual(g.next(5), {
      value: 10,
      done: true
    });
  });

  it("should work even with getter member expressions", function() {
    function *gen() {
      return a.b + (yield "asdf");
    }

    var a = {};
    var b = 0;

    Object.defineProperty(a, "b", {
      get: function() {
        return ++b;
      }
    });

    var g = gen();

    assert.strictEqual(a.b, 1);

    assert.deepEqual(g.next(), {
      value: "asdf",
      done: false
    });

    assert.strictEqual(a.b, 3);

    assert.deepEqual(g.next(2), {
      value: 4,
      done: true
    });
  });

  it("should evaluate all array elements before yielding", function() {
    function *gen() {
      return [a, yield "asdf", a];
    }

    var a = 1;
    var g = gen();

    assert.deepEqual(g.next(), {
      value: "asdf",
      done: false
    });

    a = 3;

    assert.deepEqual(g.next(2), {
      value: [1, 2, 3],
      done: true
    });
  });

  it("should handle callee member expressions correctly", function() {
    function *gen() {
      a = a.slice(0).concat(yield "asdf");
      return a;
    }

    var a = [];
    var g = gen();

    assert.deepEqual(g.next(), {
      value: "asdf",
      done: false
    });

    a.push(1);

    assert.deepEqual(g.next(2), {
      value: [2],
      done: true
    });
  });

  it("should handle implicit stringification correctly", function() {
    function *gen() {
      return a + (yield "asdf");
    }

    var a = [1, 2];
    var g = gen();

    assert.deepEqual(g.next(), {
      value: "asdf",
      done: false
    });

    a = [4,5];

    assert.deepEqual(g.next(",3"), {
      value: "1,2,3",
      done: true
    });
  });
});
