<!doctype html>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>

'use strict';

// In this test, the promises should resolve in the execution order
// (setLocalDescription, setLocalDescription, addIceCandidate) as is ensured by
// the Operations Chain; if an operation is pending, executing another operation
// will queue it. This test will fail if an Operations Chain is not implemented,
// but it gives the implementation some slack: it only ensures that
// addIceCandidate() is not resolved first, allowing timing issues in resolving
// promises where the test still passes even if addIceCandidate() is resolved
// *before* the second setLocalDescription().
//
// This test covers Chrome issue (https://crbug.com/1019222), but does not
// require setLocalDescription-promises to resolve immediately which is another
// Chrome bug (https://crbug.com/1019232). The true order is covered by the next
// test.
// TODO(https://crbug.com/1019232): Delete this test when the next test passes
// in Chrome.
promise_test(async t => {
  const caller = new RTCPeerConnection();
  t.add_cleanup(() => caller.close());
  const callee = new RTCPeerConnection();
  t.add_cleanup(() => callee.close());
  caller.addTransceiver('audio');

  const candidatePromise = new Promise(resolve => {
    caller.onicecandidate = e => resolve(e.candidate);
  });
  await caller.setLocalDescription(await caller.createOffer());
  await callee.setRemoteDescription(caller.localDescription);
  const candidate = await candidatePromise;

  // Chain setLocalDescription(), setLocalDescription() and addIceCandidate()
  // without performing await between the calls.
  const pendingPromises = [];
  const resolveOrder = [];
  pendingPromises.push(callee.setLocalDescription().then(() => {
    resolveOrder.push('setLocalDescription 1');
  }));
  pendingPromises.push(callee.setLocalDescription().then(() => {
    resolveOrder.push('setLocalDescription 2');
  }));
  pendingPromises.push(callee.addIceCandidate(candidate).then(() => {
    resolveOrder.push('addIceCandidate');
  }));
  await Promise.all(pendingPromises);

  assert_equals(resolveOrder[0], 'setLocalDescription 1');
}, 'addIceCandidate is not resolved first if 2x setLocalDescription ' +
   'operations are pending');

promise_test(async t => {
  const caller = new RTCPeerConnection();
  t.add_cleanup(() => caller.close());
  const callee = new RTCPeerConnection();
  t.add_cleanup(() => callee.close());
  caller.addTransceiver('audio');

  const candidatePromise = new Promise(resolve => {
    caller.onicecandidate = e => resolve(e.candidate);
  });
  await caller.setLocalDescription(await caller.createOffer());
  await callee.setRemoteDescription(caller.localDescription);
  const candidate = await candidatePromise;

  // Chain setLocalDescription(), setLocalDescription() and addIceCandidate()
  // without performing await between the calls.
  const pendingPromises = [];
  const resolveOrder = [];
  pendingPromises.push(callee.setLocalDescription().then(() => {
    resolveOrder.push('setLocalDescription 1');
  }));
  pendingPromises.push(callee.setLocalDescription().then(() => {
    resolveOrder.push('setLocalDescription 2');
  }));
  pendingPromises.push(callee.addIceCandidate(candidate).then(() => {
    resolveOrder.push('addIceCandidate');
  }));
  await Promise.all(pendingPromises);

  // This test verifies that both issues described in https://crbug.com/1019222
  // and https://crbug.com/1019232 are fixed. If this test passes in Chrome, the
  // ICE candidate exchange issues described in
  // https://github.com/web-platform-tests/wpt/issues/19866 should be resolved.
  assert_array_equals(
      resolveOrder,
      ['setLocalDescription 1', 'setLocalDescription 2', 'addIceCandidate']);
}, 'addIceCandidate and setLocalDescription are resolved in the correct ' +
   'order, as defined by the operations chain specification');

promise_test(async t => {
  const caller = new RTCPeerConnection();
  t.add_cleanup(() => caller.close());
  const callee = new RTCPeerConnection();
  t.add_cleanup(() => callee.close());
  caller.addTransceiver('audio');
  let events = [];
  let pendingPromises = [];

  const onCandidatePromise = new Promise(resolve => {
    caller.onicecandidate = () => {
      events.push('candidate generated');
      resolve();
    }
  });
  pendingPromises.push(onCandidatePromise);
  pendingPromises.push(caller.setLocalDescription().then(() => {
    events.push('setLocalDescription');
  }));
  await Promise.all(pendingPromises);
  assert_array_equals(events, ['setLocalDescription', 'candidate generated']);
}, 'onicecandidate fires after resolving setLocalDescription in offerer');

promise_test(async t => {
  const caller = new RTCPeerConnection();
  t.add_cleanup(() => caller.close());
  const callee = new RTCPeerConnection();
  t.add_cleanup(() => callee.close());
  caller.addTransceiver('audio');
  let events = [];
  let pendingPromises = [];

  caller.onicecandidate = (ev) => {
    if (ev.candidate) {
      callee.addIceCandidate(ev.candidate);
    }
  }
  const offer = await caller.createOffer();
  const onCandidatePromise = new Promise(resolve => {
    callee.onicecandidate = () => {
      events.push('candidate generated');
      resolve();
    }
  });
  await callee.setRemoteDescription(offer);
  const answer = await callee.createAnswer();
  pendingPromises.push(onCandidatePromise);
  pendingPromises.push(callee.setLocalDescription(answer).then(() => {
    events.push('setLocalDescription');
  }));
  await Promise.all(pendingPromises);
  assert_array_equals(events, ['setLocalDescription', 'candidate generated']);
}, 'onicecandidate fires after resolving setLocalDescription in answerer');

</script>
