text: // Flags: --expose-internals --no-warnings
  'use strict';
  
  const common = require('../common');
  const {
    Event,
    EventTarget,
    defineEventHandler
  } = require('internal/event_target');
  
  const {
    ok,
    deepStrictEqual,
    strictEqual,
    throws,
  } = require('assert');
  
  const { once } = require('events');
  
  const { promisify } = require('util');
  const delay = promisify(setTimeout);
  
  // The globals are defined.
  ok(Event);
  ok(EventTarget);
  
  // The warning event has special behavior regarding attaching listeners
  let lastWarning;
  process.on('warning', (e) => {
    lastWarning = e;
  });
  
  // Utility promise for parts of the test that need to wait for eachother -
  // Namely tests for warning events
  /* eslint-disable no-unused-vars */
  let asyncTest = Promise.resolve();
  
  // First, test Event
  {
    const ev = new Event('foo');
    strictEqual(ev.type, 'foo');
    strictEqual(ev.cancelable, false);
    strictEqual(ev.defaultPrevented, false);
    strictEqual(typeof ev.timeStamp, 'number');
  
    // Compatibility properties with the DOM
    deepStrictEqual(ev.composedPath(), []);
    strictEqual(ev.returnValue, true);
    strictEqual(ev.bubbles, false);
    strictEqual(ev.composed, false);
    strictEqual(ev.isTrusted, false);
    strictEqual(ev.eventPhase, 0);
    strictEqual(ev.cancelBubble, false);
  
    // Not cancelable
    ev.preventDefault();
    strictEqual(ev.defaultPrevented, false);
  }
  {
    [
      'foo',
      1,
      false,
    ].forEach((i) => (
      throws(() => new Event('foo', i), {
        code: 'ERR_INVALID_ARG_TYPE',
        name: 'TypeError',
        message: 'The "options" argument must be of type object.' +
                 common.invalidArgTypeHelper(i)
      })
    ));
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.cancelBubble = true;
    strictEqual(ev.cancelBubble, true);
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.stopPropagation();
    strictEqual(ev.cancelBubble, true);
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.cancelBubble = 'some-truthy-value';
    strictEqual(ev.cancelBubble, true);
  }
  {
    // No argument behavior - throw TypeError
    throws(() => {
      new Event();
    }, TypeError);
    // Too many arguments passed behavior - ignore additional arguments
    const ev = new Event('foo', {}, {});
    strictEqual(ev.type, 'foo');
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.cancelBubble = true;
    strictEqual(ev.cancelBubble, true);
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.stopPropagation();
    strictEqual(ev.cancelBubble, true);
  }
  {
    const ev = new Event('foo');
    strictEqual(ev.cancelBubble, false);
    ev.cancelBubble = 'some-truthy-value';
    strictEqual(ev.cancelBubble, true);
  }
  {
    const ev = new Event('foo', { cancelable: true });
    strictEqual(ev.type, 'foo');
    strictEqual(ev.cancelable, true);
    strictEqual(ev.defaultPrevented, false);
  
    ev.preventDefault();
    strictEqual(ev.defaultPrevented, true);
    throws(() => new Event(Symbol()), TypeError);
  }
  {
    const ev = new Event('foo');
    deepStrictEqual(Object.keys(ev), ['isTrusted']);
  }
  {
    const eventTarget = new EventTarget();
  
    const ev1 = common.mustCall(function(event) {
      strictEqual(event.type, 'foo');
      strictEqual(this, eventTarget);
      strictEqual(event.eventPhase, 2);
    }, 2);
  
    const ev2 = {
      handleEvent: common.mustCall(function(event) {
        strictEqual(event.type, 'foo');
        strictEqual(this, ev2);
      })
    };
  
    eventTarget.addEventListener('foo', ev1);
    eventTarget.addEventListener('foo', ev2, { once: true });
    ok(eventTarget.dispatchEvent(new Event('foo')));
    eventTarget.dispatchEvent(new Event('foo'));
  
    eventTarget.removeEventListener('foo', ev1);
    eventTarget.dispatchEvent(new Event('foo'));
  }
  {
    // event subclassing
    const SubEvent = class extends Event {};
    const ev = new SubEvent('foo');
    const eventTarget = new EventTarget();
    const fn = common.mustCall((event) => strictEqual(event, ev));
    eventTarget.addEventListener('foo', fn, { once: true });
    eventTarget.dispatchEvent(ev);
  }
  
  {
    const eventTarget = new EventTarget();
    const event = new Event('foo', { cancelable: true });
    eventTarget.addEventListener('foo', (event) => event.preventDefault());
    ok(!eventTarget.dispatchEvent(event));
  }
  {
    // Adding event listeners with a boolean useCapture
    const eventTarget = new EventTarget();
    const event = new Event('foo');
    const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
    eventTarget.addEventListener('foo', fn, false);
    eventTarget.dispatchEvent(event);
  }
  
  {
    // The `options` argument can be `null`.
    const eventTarget = new EventTarget();
    const event = new Event('foo');
    const fn = common.mustCall((event) => strictEqual(event.type, 'foo'));
    eventTarget.addEventListener('foo', fn, null);
    eventTarget.dispatchEvent(event);
  }
  
  {
    const uncaughtException = common.mustCall((err, event) => {
      strictEqual(err.message, 'boom');
      strictEqual(event.type, 'foo');
    }, 4);
  
    // Whether or not the handler function is async or not, errors
    // are routed to uncaughtException
    process.on('error', uncaughtException);
  
    const eventTarget = new EventTarget();
  
    const ev1 = async () => { throw new Error('boom'); };
    const ev2 = () => { throw new Error('boom'); };
    const ev3 = { handleEvent() { throw new Error('boom'); } };
    const ev4 = { async handleEvent() { throw new Error('boom'); } };
  
    // Errors in a handler won't stop calling the others.
    eventTarget.addEventListener('foo', ev1, { once: true });
    eventTarget.addEventListener('foo', ev2, { once: true });
    eventTarget.addEventListener('foo', ev3, { once: true });
    eventTarget.addEventListener('foo', ev4, { once: true });
  
    eventTarget.dispatchEvent(new Event('foo'));
  }
  
  {
    const eventTarget = new EventTarget();
  
    // Once handler only invoked once
    const ev = common.mustCall((event) => {
      // Can invoke the same event name recursively
      eventTarget.dispatchEvent(new Event('foo'));
    });
  
    // Errors in a handler won't stop calling the others.
    eventTarget.addEventListener('foo', ev, { once: true });
  
    eventTarget.dispatchEvent(new Event('foo'));
  }
  
  {
    // Coercion to string works
    strictEqual((new Event(1)).type, '1');
    strictEqual((new Event(false)).type, 'false');
    strictEqual((new Event({})).type, String({}));
  
    const target = new EventTarget();
  
    [
      'foo',
      {},  // No type event
      undefined,
      1,
      false,
    ].forEach((i) => {
      throws(() => target.dispatchEvent(i), {
        code: 'ERR_INVALID_ARG_TYPE',
        name: 'TypeError',
        message: 'The "event" argument must be an instance of Event.' +
                 common.invalidArgTypeHelper(i)
      });
    });
  
    const err = (arg) => ({
      code: 'ERR_INVALID_ARG_TYPE',
      name: 'TypeError',
      message: 'The "listener" argument must be an instance of EventListener.' +
               common.invalidArgTypeHelper(arg)
    });
  
    [
      'foo',
      1,
      {},  // No handleEvent function
      false,
    ].forEach((i) => throws(() => target.addEventListener('foo', i), err(i)));
  }
  
  {
    const target = new EventTarget();
    once(target, 'foo').then(common.mustCall());
    target.dispatchEvent(new Event('foo'));
  }
  
  {
    const target = new EventTarget();
    const event = new Event('foo');
    event.stopImmediatePropagation();
    target.addEventListener('foo', common.mustNotCall());
    target.dispatchEvent(event);
  }
  
  {
    const target = new EventTarget();
    const event = new Event('foo');
    target.addEventListener('foo', common.mustCall((event) => {
      event.stopImmediatePropagation();
    }));
    target.addEventListener('foo', common.mustNotCall());
    target.dispatchEvent(event);
  }
  
  {
    const target = new EventTarget();
    const event = new Event('foo');
    target.addEventListener('foo', common.mustCall((event) => {
      event.stopImmediatePropagation();
    }));
    target.addEventListener('foo', common.mustNotCall());
    target.dispatchEvent(event);
  }
  
  {
    const target = new EventTarget();
    const event = new Event('foo');
    strictEqual(event.target, null);
    target.addEventListener('foo', common.mustCall((event) => {
      strictEqual(event.target, target);
      strictEqual(event.currentTarget, target);
      strictEqual(event.srcElement, target);
    }));
    target.dispatchEvent(event);
  }
  
  {
    const target1 = new EventTarget();
    const target2 = new EventTarget();
    const event = new Event('foo');
    target1.addEventListener('foo', common.mustCall((event) => {
      throws(() => target2.dispatchEvent(event), {
        code: 'ERR_EVENT_RECURSION'
      });
    }));
    target1.dispatchEvent(event);
  }
  
  {
    const target = new EventTarget();
    const a = common.mustCall(() => target.removeEventListener('foo', a));
    const b = common.mustCall(2);
  
    target.addEventListener('foo', a);
    target.addEventListener('foo', b);
  
    target.dispatchEvent(new Event('foo'));
    target.dispatchEvent(new Event('foo'));
  }
  
  {
    const target = new EventTarget();
    const a = common.mustCall(3);
  
    target.addEventListener('foo', a, { capture: true });
    target.addEventListener('foo', a, { capture: false });
  
    target.dispatchEvent(new Event('foo'));
    target.removeEventListener('foo', a, { capture: true });
    target.dispatchEvent(new Event('foo'));
    target.removeEventListener('foo', a, { capture: false });
    target.dispatchEvent(new Event('foo'));
  }
  {
    const target = new EventTarget();
    strictEqual(target.toString(), '[object EventTarget]');
    const event = new Event('');
    strictEqual(event.toString(), '[object Event]');
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    target.onfoo = common.mustCall();
    target.dispatchEvent(new Event('foo'));
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    let count = 0;
    target.onfoo = () => count++;
    target.onfoo = common.mustCall(() => count++);
    target.dispatchEvent(new Event('foo'));
    strictEqual(count, 1);
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    let count = 0;
    target.addEventListener('foo', () => count++);
    target.onfoo = common.mustCall(() => count++);
    target.dispatchEvent(new Event('foo'));
    strictEqual(count, 2);
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    const fn = common.mustNotCall();
    target.onfoo = fn;
    strictEqual(target.onfoo, fn);
    target.onfoo = null;
    target.dispatchEvent(new Event('foo'));
  }
  
  {
    // `this` value of dispatchEvent
    const target = new EventTarget();
    const target2 = new EventTarget();
    const event = new Event('foo');
  
    ok(target.dispatchEvent.call(target2, event));
  
    [
      'foo',
      {},
      [],
      1,
      null,
      undefined,
      false,
      Symbol(),
      /a/,
    ].forEach((i) => {
      throws(() => target.dispatchEvent.call(i, event), {
        code: 'ERR_INVALID_THIS'
      });
    });
  }
  
  {
    // Event Statics
    strictEqual(Event.NONE, 0);
    strictEqual(Event.CAPTURING_PHASE, 1);
    strictEqual(Event.AT_TARGET, 2);
    strictEqual(Event.BUBBLING_PHASE, 3);
    strictEqual(new Event('foo').eventPhase, Event.NONE);
    const target = new EventTarget();
    target.addEventListener('foo', common.mustCall((e) => {
      strictEqual(e.eventPhase, Event.AT_TARGET);
    }), { once: true });
    target.dispatchEvent(new Event('foo'));
    // Event is a function
    strictEqual(Event.length, 1);
  }
  
  {
    const target = new EventTarget();
    const ev = new Event('toString');
    const fn = common.mustCall((event) => strictEqual(event.type, 'toString'));
    target.addEventListener('toString', fn);
    target.dispatchEvent(ev);
  }
  {
    const target = new EventTarget();
    const ev = new Event('__proto__');
    const fn = common.mustCall((event) => strictEqual(event.type, '__proto__'));
    target.addEventListener('__proto__', fn);
    target.dispatchEvent(ev);
  }
  
  {
    const eventTarget = new EventTarget();
    // Single argument throws
    throws(() => eventTarget.addEventListener('foo'), TypeError);
    // Null events - does not throw
    eventTarget.addEventListener('foo', null);
    eventTarget.removeEventListener('foo', null);
    eventTarget.addEventListener('foo', undefined);
    eventTarget.removeEventListener('foo', undefined);
    // Strings, booleans
    throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
    throws(() => eventTarget.addEventListener('foo', false), TypeError);
    throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
    asyncTest = asyncTest.then(async () => {
      const eventTarget = new EventTarget();
      // Single argument throws
      throws(() => eventTarget.addEventListener('foo'), TypeError);
      // Null events - does not throw
  
      eventTarget.addEventListener('foo', null);
      eventTarget.removeEventListener('foo', null);
  
      // Warnings always happen after nextTick, so wait for a timer of 0
      await delay(0);
      strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
      strictEqual(lastWarning.target, eventTarget);
      lastWarning = null;
      eventTarget.addEventListener('foo', undefined);
      await delay(0);
      strictEqual(lastWarning.name, 'AddEventListenerArgumentTypeWarning');
      strictEqual(lastWarning.target, eventTarget);
      eventTarget.removeEventListener('foo', undefined);
      // Strings, booleans
      throws(() => eventTarget.addEventListener('foo', 'hello'), TypeError);
      throws(() => eventTarget.addEventListener('foo', false), TypeError);
      throws(() => eventTarget.addEventListener('foo', Symbol()), TypeError);
    });
  }
  {
    const eventTarget = new EventTarget();
    const event = new Event('foo');
    eventTarget.dispatchEvent(event);
    strictEqual(event.target, eventTarget);
  }
  {
    // Event target exported keys
    const eventTarget = new EventTarget();
    deepStrictEqual(Object.keys(eventTarget), []);
    deepStrictEqual(Object.getOwnPropertyNames(eventTarget), []);
    const parentKeys = Object.keys(Object.getPrototypeOf(eventTarget)).sort();
    const keys = ['addEventListener', 'dispatchEvent', 'removeEventListener'];
    deepStrictEqual(parentKeys, keys);
  }
  {
    // Subclassing
    class SubTarget extends EventTarget {}
    const target = new SubTarget();
    target.addEventListener('foo', common.mustCall());
    target.dispatchEvent(new Event('foo'));
  }
  {
    // Test event order
    const target = new EventTarget();
    let state = 0;
    target.addEventListener('foo', common.mustCall(() => {
      strictEqual(state, 0);
      state++;
    }));
    target.addEventListener('foo', common.mustCall(() => {
      strictEqual(state, 1);
    }));
    target.dispatchEvent(new Event('foo'));
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    const descriptor = Object.getOwnPropertyDescriptor(target, 'onfoo');
    strictEqual(descriptor.configurable, true);
    strictEqual(descriptor.enumerable, true);
  }
  {
    const target = new EventTarget();
    defineEventHandler(target, 'foo');
    const output = [];
    target.addEventListener('foo', () => output.push(1));
    target.onfoo = common.mustNotCall();
    target.addEventListener('foo', () => output.push(3));
    target.onfoo = () => output.push(2);
    target.addEventListener('foo', () => output.push(4));
    target.dispatchEvent(new Event('foo'));
    deepStrictEqual(output, [1, 2, 3, 4]);
  }
  