/* global describe, specify, assert, Promise */

var failIfThrows = function (done) {
  'use strict';

  return function (e) {
    done(e || new Error());
  };
};

describe('Promise', function () {
  'use strict';

  specify('sanity check: a fulfilled promise calls its fulfillment handler', function (done) {
    Promise.resolve(5).then(function (value) {
      assert.strictEqual(value, 5);
    }).then(done, failIfThrows(done));
  });

  specify('directly resolving the promise with itself', function (done) {
    var resolvePromise;
    var promise = new Promise(function (resolve) { resolvePromise = resolve; });

    resolvePromise(promise);

    promise.then(
      function () {
        assert(false, 'Should not be fulfilled');
      },
      function (err) {
        assert(err instanceof TypeError);
      }
    ).then(done, failIfThrows(done));
  });

  specify('Stealing a resolver and using it to trigger possible reentrancy bug (#83)', function () {
    var stolenResolver;
    var StealingPromiseConstructor = function StealingPromiseConstructor(resolver) {
      stolenResolver = resolver;
      resolver(function () { }, function () { });
    };

    var iterable = {};
    var atAtIterator = '@@iterator'; // on firefox, at least.
    iterable[atAtIterator] = function () {
      stolenResolver(null, null);
      throw new Error(0);
    };

    assert.doesNotThrow(function () {
      Promise.all.call(StealingPromiseConstructor, iterable);
    });
  });

  specify('resolve with a thenable calls it once', function () {
    var resolve;
    var p = new Promise(function (r) { resolve = r; });
    var count = 0;
    resolve({
      then: function () {
        count += 1;
        throw new RangeError('reject the promise');
      }
    });
    var a = p.then(function () {})['catch'](function (err) {
      assert.equal(count, 1);
      assert.ok(err instanceof RangeError);
    });
    var b = p.then(function () {})['catch'](function (err) {
      assert.equal(count, 1);
      assert.ok(err instanceof RangeError);
    });
    return Promise.all([a, b]);
  });

  specify('resolve with a thenable that throws on .then, rejects the promise synchronously', function () {
    var resolve;
    var p = new Promise(function (r) { resolve = r; });
    var count = 0;
    var thenable = Object.defineProperty({}, 'then', {
      get: function () {
        count += 1;
        throw new RangeError('no then for you');
      }
    });
    resolve(thenable);
    assert.equal(count, 1);
    var a = p.then(function () {})['catch'](function (err) {
      assert.equal(count, 1);
      assert.ok(err instanceof RangeError);
    });
    var b = p.then(function () {})['catch'](function (err) {
      assert.equal(count, 1);
      assert.ok(err instanceof RangeError);
    });
    return Promise.all([a, b]);
  });
});
