// Flags: --expose-internals --no-warnings 'use strict'; const common = require('../common'); const { Event, EventTarget, NodeEventTarget, defineEventHandler } = require('internal/event_target'); const { ok, deepStrictEqual, strictEqual, throws, } = require('assert'); const { once } = require('events'); // The globals are defined. ok(Event); ok(EventTarget); // 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); } { 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', { 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 NodeEventTarget(); strictEqual(eventTarget.listenerCount('foo'), 0); deepStrictEqual(eventTarget.eventNames(), []); const ev1 = common.mustCall(function(event) { strictEqual(event.type, 'foo'); strictEqual(this, eventTarget); }, 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 }); strictEqual(eventTarget.listenerCount('foo'), 2); ok(eventTarget.dispatchEvent(new Event('foo'))); strictEqual(eventTarget.listenerCount('foo'), 1); eventTarget.dispatchEvent(new Event('foo')); eventTarget.removeEventListener('foo', ev1); strictEqual(eventTarget.listenerCount('foo'), 0); eventTarget.dispatchEvent(new Event('foo')); } { 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); } { const eventTarget = new NodeEventTarget(); strictEqual(eventTarget.listenerCount('foo'), 0); deepStrictEqual(eventTarget.eventNames(), []); const ev1 = common.mustCall((event) => { strictEqual(event.type, 'foo'); }, 2); const ev2 = { handleEvent: common.mustCall((event) => { strictEqual(event.type, 'foo'); }) }; strictEqual(eventTarget.on('foo', ev1), eventTarget); strictEqual(eventTarget.once('foo', ev2, { once: true }), eventTarget); strictEqual(eventTarget.listenerCount('foo'), 2); eventTarget.dispatchEvent(new Event('foo')); strictEqual(eventTarget.listenerCount('foo'), 1); eventTarget.dispatchEvent(new Event('foo')); strictEqual(eventTarget.off('foo', ev1), eventTarget); strictEqual(eventTarget.listenerCount('foo'), 0); eventTarget.dispatchEvent(new Event('foo')); } { const eventTarget = new NodeEventTarget(); strictEqual(eventTarget.listenerCount('foo'), 0); deepStrictEqual(eventTarget.eventNames(), []); const ev1 = common.mustCall((event) => { strictEqual(event.type, 'foo'); }, 2); const ev2 = { handleEvent: common.mustCall((event) => { strictEqual(event.type, 'foo'); }) }; eventTarget.addListener('foo', ev1); eventTarget.once('foo', ev2, { once: true }); strictEqual(eventTarget.listenerCount('foo'), 2); eventTarget.dispatchEvent(new Event('foo')); strictEqual(eventTarget.listenerCount('foo'), 1); eventTarget.dispatchEvent(new Event('foo')); eventTarget.removeListener('foo', ev1); strictEqual(eventTarget.listenerCount('foo'), 0); eventTarget.dispatchEvent(new Event('foo')); } { const eventTarget = new NodeEventTarget(); strictEqual(eventTarget.listenerCount('foo'), 0); deepStrictEqual(eventTarget.eventNames(), []); // Won't actually be called. const ev1 = () => {}; // Won't actually be called. const ev2 = { handleEvent() {} }; eventTarget.addListener('foo', ev1); eventTarget.addEventListener('foo', ev1); eventTarget.once('foo', ev2, { once: true }); eventTarget.once('foo', ev2, { once: false }); eventTarget.on('bar', ev1); strictEqual(eventTarget.listenerCount('foo'), 2); strictEqual(eventTarget.listenerCount('bar'), 1); deepStrictEqual(eventTarget.eventNames(), ['foo', 'bar']); eventTarget.removeAllListeners('foo'); strictEqual(eventTarget.listenerCount('foo'), 0); strictEqual(eventTarget.listenerCount('bar'), 1); deepStrictEqual(eventTarget.eventNames(), ['bar']); eventTarget.removeAllListeners(); strictEqual(eventTarget.listenerCount('foo'), 0); strictEqual(eventTarget.listenerCount('bar'), 0); deepStrictEqual(eventTarget.eventNames(), []); } { 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) => { throws(() => eventTarget.dispatchEvent(new Event('foo')), { code: 'ERR_EVENT_RECURSION' }); }); // Errors in a handler won't stop calling the others. eventTarget.addEventListener('foo', ev); 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' }); }); [ 'foo', 1, {}, // No handleEvent function false, undefined ].forEach((i) => { throws(() => target.addEventListener('foo', i), { code: 'ERR_INVALID_ARG_TYPE' }); }); [ 'foo', 1, {}, // No handleEvent function false, undefined ].forEach((i) => { throws(() => target.removeEventListener('foo', i), { code: 'ERR_INVALID_ARG_TYPE' }); }); } { const target = new EventTarget(); once(target, 'foo').then(common.mustCall()); target.dispatchEvent(new Event('foo')); } { const target = new NodeEventTarget(); process.on('warning', common.mustCall((warning) => { ok(warning instanceof Error); strictEqual(warning.name, 'MaxListenersExceededWarning'); strictEqual(warning.target, target); strictEqual(warning.count, 2); strictEqual(warning.type, 'foo'); ok(warning.message.includes( '2 foo listeners added to NodeEventTarget')); })); strictEqual(target.getMaxListeners(), NodeEventTarget.defaultMaxListeners); target.setMaxListeners(1); target.on('foo', () => {}); target.on('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' }); }); } { 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')); }