// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax --harmony-promise-all-settled

class MyError extends Error {}

const descriptor = Object.getOwnPropertyDescriptor(Promise, "allSettled");
assertTrue(descriptor.configurable);
assertTrue(descriptor.writable);
assertFalse(descriptor.enumerable);

// 1. Let C be the this value.
// 2. If Type(C) is not Object, throw a TypeError exception.
assertThrows(() => Promise.allSettled.call(1), TypeError);

{
  // 3. Let promiseCapability be ? NewPromiseCapability(C).
  let called = false;
  class MyPromise extends Promise {
    constructor(...args) {
      called = true;
      super(...args);
    }
  }

  MyPromise.allSettled([]);
  assertTrue(called);
}

{
  // 4. Let iteratorRecord be GetIterator(iterable).
  // 5. IfAbruptRejectPromise(iteratorRecord, promiseCapability).
  let caught = false;
  (async function() {
    class MyError extends Error {}
    let err;

    try {
      await Promise.allSettled({
        [Symbol.iterator]() {
          throw new MyError();
        }
      });
    } catch (e) {
      assertTrue(e instanceof MyError);
      caught = true;
    }
  })();

  %PerformMicrotaskCheckpoint();
  assertTrue(caught);
}

{
  // 6. a. Let next be IteratorStep(iteratorRecord).
  let iteratorStep = false;
  (async function() {
    try {
      await Promise.allSettled({
        [Symbol.iterator]() {
          return {
            next() {
              iteratorStep = true;
              return { done: true }
            }
          };
        }
      });
    } catch (e) {
      %AbortJS(e.stack);
    }
  })();

  %PerformMicrotaskCheckpoint();
  assertTrue(iteratorStep);
}

{
  // 6. a. Let next be IteratorStep(iteratorRecord).
  // 6. b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
  // 6. c. ReturnIfAbrupt(next).
  let caught = false;
  (async function() {
    try {
      await Promise.allSettled({
        [Symbol.iterator]() {
          return {
            next() {
              throw new MyError();
            }
          };
        }
      });
    } catch (e) {
      assertTrue(e instanceof MyError);
      caught = true;
    }
  })();

  %PerformMicrotaskCheckpoint();
  assertTrue(caught);
}

{
  // 6. e. Let nextValue be IteratorValue(next).
  let iteratorValue = false;
  (async function() {
    try {
      await Promise.allSettled({
        [Symbol.iterator]() {
          let done = false;

          return {
            next() {
              let result = { value: 1, done };
              iteratorValue = true;
              done = true;
              return result;
            }
          };
        }
      });
    } catch (e) {
      %AbortJS(e.stack);
    }
  })();

  %PerformMicrotaskCheckpoint();
  assertTrue(iteratorValue);
}

{
  // 6. e. Let nextValue be IteratorValue(next).
  // 6. f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
  // 6. g. ReturnIfAbrupt(nextValue).
  let caught = false;
  (async function() {
    try {
      await Promise.allSettled({
        [Symbol.iterator]() {
          let done = false;

          return {
            next() {
              return result = {
                get value() {throw new MyError(''); },
                done: false
              };
            }
          };
        }
      });
    } catch (e) {
      assertTrue(e instanceof MyError);
      caught = true;
    }
  })();

  %PerformMicrotaskCheckpoint();
  assertTrue(caught);
}

{
  // TODO(mathias): https://github.com/tc39/proposal-promise-allSettled/pull/40
  // 6. i. Let nextPromise be ? Invoke(constructor, "resolve", « nextValue »).
  let called = false;
  class MyPromise extends Promise {
    static resolve(...args) {
      called = true;
      super.resolve(...args);
    }
  }

  MyPromise.allSettled([1]);
  assertTrue(called);
}

{
  // 6. z. Perform ? Invoke(nextPromise, "then", « resolveElement, rejectElement »).
  let called = false;
  class MyPromise extends Promise {
    then(...args) {
      called = true;
      super.resolve(...args);
    }
  }

  MyPromise.allSettled([1]);
  assertTrue(called);
}

{
  let called = false;
  let result;
  Promise.allSettled([]).then(x => {
    called = true;
    result = x;
  });

  %PerformMicrotaskCheckpoint();
  assertTrue(called);
  assertEquals(result, []);
}

{
  let called = false;
  Promise.allSettled([Promise.resolve("foo")]).then(v => {
    assertEquals(v.length, 1);
    const [x] = v;

    assertSame(Object.getPrototypeOf(x), Object.getPrototypeOf({}));
    const descs = Object.getOwnPropertyDescriptors(x);
    assertEquals(Object.keys(descs).length, 2);

    const { value: desc } = descs;
    assertTrue(desc.writable);
    assertTrue(desc.enumerable);
    assertTrue(desc.configurable);

    assertEquals(x.value, "foo");
    assertEquals(x.status, "fulfilled");
    called = true;
  });

  %PerformMicrotaskCheckpoint();
  assertTrue(called);
}

{
  let called = false;
  Promise.allSettled([Promise.reject("foo")]).then(v => {
    assertEquals(v.length, 1);
    const [x] = v;
    assertEquals(x.reason, "foo");
    assertEquals(x.status, "rejected");
    called = true;
  });

  %PerformMicrotaskCheckpoint();
  assertTrue(called);
}

{
  let called = false;
  Promise.allSettled([Promise.resolve("bar"), Promise.reject("foo")]).then(v => {
    assertEquals(v.length, 2);
    const [x, y] = v;
    assertEquals(x.value, "bar");
    assertEquals(x.status, "fulfilled");
    assertEquals(y.reason, "foo");
    assertEquals(y.status, "rejected");
    called = true;
  });

  %PerformMicrotaskCheckpoint();
  assertTrue(called);
}
