217
expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
218
document.body.removeChild(container);
219
});
220
+
221
+ // Regression test for https://github.com/facebook/react/pull/12877
222
+ it('should not fire form events twice', () => {
223
+ const container = document.createElement('div');
224
+ document.body.appendChild(container);
225
+
226
+ const formRef = React.createRef();
227
+ const inputRef = React.createRef();
228
+
229
+ const handleInvalid = jest.fn();
230
+ const handleReset = jest.fn();
231
+ const handleSubmit = jest.fn();
232
+ ReactDOM.render(
233
+ <form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
234
+ <input ref={inputRef} onInvalid={handleInvalid} />
235
+ </form>,
236
+ container,
237
+ );
238
+
239
+ inputRef.current.dispatchEvent(
240
+ new Event('invalid', {
241
+ // https://developer.mozilla.org/en-US/docs/Web/Events/invalid
242
+ bubbles: false,
243
+ }),
244
+ );
245
+ expect(handleInvalid).toHaveBeenCalledTimes(1);
246
+
247
+ formRef.current.dispatchEvent(
248
+ new Event('reset', {
249
+ // https://developer.mozilla.org/en-US/docs/Web/Events/reset
250
+ bubbles: true,
251
+ }),
252
+ );
253
+ expect(handleReset).toHaveBeenCalledTimes(1);
254
+
255
+ formRef.current.dispatchEvent(
256
+ new Event('submit', {
257
+ // https://developer.mozilla.org/en-US/docs/Web/Events/submit
258
+ bubbles: true,
259
+ }),
260
+ );
261
+ expect(handleSubmit).toHaveBeenCalledTimes(1);
262
+
263
+ formRef.current.dispatchEvent(
264
+ new Event('submit', {
265
+ // Might happen on older browsers.
266
+ bubbles: true,
267
+ }),
268
+ );
269
+ expect(handleSubmit).toHaveBeenCalledTimes(2); // It already fired in this test.
270
+
271
+ document.body.removeChild(container);
272
+ });
273
+
274
+ it('should dispatch loadstart only for media elements', () => {
275
+ const container = document.createElement('div');
276
+ document.body.appendChild(container);
277
+
278
+ const imgRef = React.createRef();
279
+ const videoRef = React.createRef();
280
+
281
+ const handleImgLoadStart = jest.fn();
282
+ const handleVideoLoadStart = jest.fn();
283
+ ReactDOM.render(
284
+ <div>
285
+ <img ref={imgRef} onLoadStart={handleImgLoadStart} />
286
+ <video ref={videoRef} onLoadStart={handleVideoLoadStart} />
287
+ </div>,
288
+ container,
289
+ );
290
+
291
+ // Note for debugging: loadstart currently doesn't fire in Chrome.
292
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=458851
293
+ imgRef.current.dispatchEvent(
294
+ new ProgressEvent('loadstart', {
295
+ bubbles: false,
296
+ }),
297
+ );
298
+ // Historically, we happened to not support onLoadStart
299
+ // on <img>, and this test documents that lack of support.
300
+ // If we decide to support it in the future, we should change
301
+ // this line to expect 1 call. Note that fixing this would
302
+ // be simple but would require attaching a handler to each
303
+ // <img>. So far nobody asked us for it.
304
+ expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
305
+
306
+ videoRef.current.dispatchEvent(
307
+ new ProgressEvent('loadstart', {
308
+ bubbles: false,
309
+ }),
310
+ );
311
+ expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
312
+
313
+ document.body.removeChild(container);
314
+ });
315
+
316
+ it('should not attempt to listen to unnecessary events on the top level', () => {
317
+ const container = document.createElement('div');
318
+ document.body.appendChild(container);
319
+
320
+ const videoRef = React.createRef();
321
+ const handleVideoPlay = jest.fn(); // We'll test this one.
322
+ const mediaEvents = {
323
+ onAbort() {},
324
+ onCanPlay() {},
325
+ onCanPlayThrough() {},
326
+ onDurationChange() {},
327
+ onEmptied() {},
328
+ onEncrypted() {},
329
+ onEnded() {},
330
+ onError() {},
331
+ onLoadedData() {},
332
+ onLoadedMetadata() {},
333
+ onLoadStart() {},
334
+ onPause() {},
335
+ onPlay() {},
336
+ onPlaying() {},
337
+ onProgress() {},
338
+ onRateChange() {},
339
+ onSeeked() {},
340
+ onSeeking() {},
341
+ onStalled() {},
342
+ onSuspend() {},
343
+ onTimeUpdate() {},
344
+ onVolumeChange() {},
345
+ onWaiting() {},
346
+ };
347
+
348
+ const originalAddEventListener = document.addEventListener;
349
+ document.addEventListener = function(type) {
350
+ throw new Error(
351
+ `Did not expect to add a top-level listener for the "${type}" event.`,
352
+ );
353
+ };
354
+
355
+ try {
356
+ // We expect that mounting this tree will
357
+ // *not* attach handlers for any top-level events.
358
+ ReactDOM.render(
359
+ <div>
360
+ <video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
361
+ <audio {...mediaEvents}>
362
+ <source {...mediaEvents} />
363
+ </audio>
364
+ <form onReset={() => {}} onSubmit={() => {}} />
365
+ </div>,
366
+ container,
367
+ );
368
+
369
+ // Also verify dispatching one of them works
370
+ videoRef.current.dispatchEvent(
371
+ new Event('play', {
372
+ bubbles: false,
373
+ }),
374
+ );
375
+ expect(handleVideoPlay).toHaveBeenCalledTimes(1);
376
+ } finally {
377
+ document.addEventListener = originalAddEventListener;
378
+ document.body.removeChild(container);
379
+ }
380
+ });
381
});