// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
"use strict";
function assert(cond) {
  if (!cond) {
    throw Error("assert");
  }
}

function addEventListenerTest() {
  const document = new EventTarget();

  assert(document.addEventListener("x", null, false) === undefined);
  assert(document.addEventListener("x", null, true) === undefined);
  assert(document.addEventListener("x", null) === undefined);
}

function constructedEventTargetCanBeUsedAsExpected() {
  const target = new EventTarget();
  const event = new Event("foo", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = (e) => {
    assert(e === event);
    ++callCount;
  };

  target.addEventListener("foo", listener);

  target.dispatchEvent(event);
  assert(callCount === 1);

  target.dispatchEvent(event);
  assert(callCount === 2);

  target.removeEventListener("foo", listener);
  target.dispatchEvent(event);
  assert(callCount === 2);
}

function anEventTargetCanBeSubclassed() {
  class NicerEventTarget extends EventTarget {
    on(
      type,
      callback,
      options,
    ) {
      this.addEventListener(type, callback, options);
    }

    off(
      type,
      callback,
      options,
    ) {
      this.removeEventListener(type, callback, options);
    }
  }

  const target = new NicerEventTarget();
  new Event("foo", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = () => {
    ++callCount;
  };

  target.on("foo", listener);
  assert(callCount === 0);

  target.off("foo", listener);
  assert(callCount === 0);
}

function removingNullEventListenerShouldSucceed() {
  const document = new EventTarget();
  assert(document.removeEventListener("x", null, false) === undefined);
  assert(document.removeEventListener("x", null, true) === undefined);
  assert(document.removeEventListener("x", null) === undefined);
}

function constructedEventTargetUseObjectPrototype() {
  const target = new EventTarget();
  const event = new Event("toString", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = (e) => {
    assert(e === event);
    ++callCount;
  };

  target.addEventListener("toString", listener);

  target.dispatchEvent(event);
  assert(callCount === 1);

  target.dispatchEvent(event);
  assert(callCount === 2);

  target.removeEventListener("toString", listener);
  target.dispatchEvent(event);
  assert(callCount === 2);
}

function toStringShouldBeWebCompatible() {
  const target = new EventTarget();
  assert(target.toString() === "[object EventTarget]");
}

function dispatchEventShouldNotThrowError() {
  let hasThrown = false;

  try {
    const target = new EventTarget();
    const event = new Event("hasOwnProperty", {
      bubbles: true,
      cancelable: false,
    });
    const listener = () => {};
    target.addEventListener("hasOwnProperty", listener);
    target.dispatchEvent(event);
  } catch {
    hasThrown = true;
  }

  assert(hasThrown === false);
}

function eventTargetThisShouldDefaultToWindow() {
  const {
    addEventListener,
    dispatchEvent,
    removeEventListener,
  } = EventTarget.prototype;
  let n = 1;
  const event = new Event("hello");
  const listener = () => {
    n = 2;
  };

  addEventListener("hello", listener);
  globalThis.dispatchEvent(event);
  assert(n === 2);
  n = 1;
  removeEventListener("hello", listener);
  globalThis.dispatchEvent(event);
  assert(n === 1);

  globalThis.addEventListener("hello", listener);
  dispatchEvent(event);
  assert(n === 2);
  n = 1;
  globalThis.removeEventListener("hello", listener);
  dispatchEvent(event);
  assert(n === 1);
}

function eventTargetShouldAcceptEventListenerObject() {
  const target = new EventTarget();
  const event = new Event("foo", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = {
    handleEvent(e) {
      assert(e === event);
      ++callCount;
    },
  };

  target.addEventListener("foo", listener);

  target.dispatchEvent(event);
  assert(callCount === 1);

  target.dispatchEvent(event);
  assert(callCount === 2);

  target.removeEventListener("foo", listener);
  target.dispatchEvent(event);
  assert(callCount === 2);
}

function eventTargetShouldAcceptAsyncFunction() {
  const target = new EventTarget();
  const event = new Event("foo", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = (e) => {
    assert(e === event);
    ++callCount;
  };

  target.addEventListener("foo", listener);

  target.dispatchEvent(event);
  assert(callCount === 1);

  target.dispatchEvent(event);
  assert(callCount === 2);

  target.removeEventListener("foo", listener);
  target.dispatchEvent(event);
  assert(callCount === 2);
}

function eventTargetShouldAcceptAsyncFunctionForEventListenerObject() {
  const target = new EventTarget();
  const event = new Event("foo", { bubbles: true, cancelable: false });
  let callCount = 0;

  const listener = {
    handleEvent(e) {
      assert(e === event);
      ++callCount;
    },
  };

  target.addEventListener("foo", listener);

  target.dispatchEvent(event);
  assert(callCount === 1);

  target.dispatchEvent(event);
  assert(callCount === 2);

  target.removeEventListener("foo", listener);
  target.dispatchEvent(event);
  assert(callCount === 2);
}

function main() {
  globalThis.__bootstrap.eventTarget.setEventTargetData(globalThis);
  addEventListenerTest();
  constructedEventTargetCanBeUsedAsExpected();
  anEventTargetCanBeSubclassed();
  removingNullEventListenerShouldSucceed();
  constructedEventTargetUseObjectPrototype();
  toStringShouldBeWebCompatible();
  dispatchEventShouldNotThrowError();
  eventTargetThisShouldDefaultToWindow();
  eventTargetShouldAcceptEventListenerObject();
  eventTargetShouldAcceptAsyncFunction();
  eventTargetShouldAcceptAsyncFunctionForEventListenerObject();
}

main();